[{"data":1,"prerenderedAt":7012},["ShallowReactive",2],{"search-sections-astql":3,"nav-astql":1676,"content-tree-astql":1733,"footer-resources":1759,"content-/v1.0.10/reference/operators":4296,"surround-/v1.0.10/reference/operators":7010},[4,10,14,20,25,30,35,41,46,51,56,59,64,69,74,79,84,89,93,98,103,108,113,118,123,128,133,138,143,148,153,158,163,168,173,178,182,186,191,195,200,205,210,215,220,225,230,235,240,245,250,255,260,265,270,275,279,284,289,294,298,302,307,312,316,321,325,329,334,338,343,348,352,357,361,366,371,375,380,385,389,394,399,403,407,412,415,420,425,430,435,438,443,448,453,458,462,466,471,476,481,486,490,495,500,505,510,514,519,524,528,533,538,543,548,553,558,563,568,573,578,582,586,591,596,601,606,611,616,621,626,631,636,641,646,651,656,661,665,670,675,680,685,690,695,699,703,708,713,717,722,726,731,736,740,745,749,754,759,764,768,773,778,783,788,793,797,802,806,811,816,821,825,830,835,840,845,848,853,858,863,868,873,877,882,887,892,897,902,907,912,917,922,927,932,937,940,945,950,955,960,964,969,974,979,984,989,994,999,1004,1007,1012,1017,1022,1027,1032,1036,1041,1046,1051,1056,1061,1066,1071,1076,1081,1086,1091,1096,1101,1106,1111,1116,1120,1124,1129,1133,1138,1143,1148,1153,1158,1163,1168,1173,1178,1183,1188,1193,1198,1203,1208,1213,1218,1223,1228,1232,1237,1242,1247,1252,1257,1261,1265,1270,1275,1280,1285,1290,1295,1300,1305,1310,1315,1320,1325,1330,1335,1340,1345,1350,1355,1360,1365,1370,1375,1380,1385,1390,1395,1400,1404,1409,1414,1419,1424,1428,1432,1437,1442,1446,1450,1455,1460,1464,1469,1474,1478,1482,1486,1491,1496,1500,1504,1509,1514,1519,1524,1528,1533,1537,1542,1547,1552,1557,1562,1566,1570,1574,1578,1582,1587,1591,1595,1599,1604,1609,1613,1618,1622,1627,1631,1636,1640,1645,1649,1654,1659,1663,1667,1671],{"id":5,"title":6,"titles":7,"content":8,"level":9},"/v1.0.10/overview","Overview",[],"Type-safe SQL query builder for PostgreSQL",1,{"id":11,"title":6,"titles":12,"content":13,"level":9},"/v1.0.10/overview#overview",[],"SQL query builders in Go typically trust developers to provide valid identifiers. ASTQL takes a different approach: validate everything against a schema before rendering. import \"github.com/zoobzio/astql/postgres\"\n\n// Define your schema\nproject := dbml.NewProject(\"myapp\")\nusers := dbml.NewTable(\"users\")\nusers.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\nusers.AddColumn(dbml.NewColumn(\"username\", \"varchar\"))\nusers.AddColumn(dbml.NewColumn(\"email\", \"varchar\"))\nproject.AddTable(users)\n\n// Create a validated instance\ninstance, _ := astql.NewFromDBML(project)\n\n// Build queries - tables and fields validated against schema\nresult, _ := astql.Select(instance.T(\"users\")).\n    Fields(instance.F(\"username\"), instance.F(\"email\")).\n    Where(instance.C(instance.F(\"id\"), astql.EQ, instance.P(\"user_id\"))).\n    Render(postgres.New())\n\n// result.SQL: SELECT \"username\", \"email\" FROM \"users\" WHERE \"id\" = :user_id\n// result.RequiredParams: []string{\"user_id\"} Type-safe, schema-validated, injection-resistant.",{"id":15,"title":16,"titles":17,"content":18,"level":19},"/v1.0.10/overview#architecture","Architecture",[6],"┌─────────────────────────────────────────────────────────────┐\n│                         ASTQL                               │\n│                                                             │\n│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │\n│  │    DBML     │    │   Builder   │    │   Render    │     │\n│  │   Schema    │───▶│     API     │───▶│   Engine    │     │\n│  │             │    │             │    │             │     │\n│  │  Validates  │    │  Constructs │    │  Generates  │     │\n│  │  Tables     │    │  AST Nodes  │    │  SQL + Params│    │\n│  │  Fields     │    │             │    │             │     │\n│  └─────────────┘    └─────────────┘    └─────────────┘     │\n│                                                             │\n│  ┌─────────────────────────────────────────────────────┐   │\n│  │                  Internal AST Types                  │   │\n│  │  Table, Field, Param, Condition, OrderBy, Join...   │   │\n│  └─────────────────────────────────────────────────────┘   │\n└─────────────────────────────────────────────────────────────┘ The DBML schema validates identifiers. The Builder API constructs an Abstract Syntax Tree. The Render engine produces parameterized SQL.",2,{"id":21,"title":22,"titles":23,"content":24,"level":19},"/v1.0.10/overview#philosophy","Philosophy",[6],"ASTQL draws inspiration from the principle of defense in depth. Rather than trusting developers to avoid SQL injection, the library makes injection structurally difficult: Schema validation — Tables and fields must exist in your DBML schemaInstance-based API — Types cannot be constructed directlyParameterized output — Values are never interpolated into SQLQuoted identifiers — All identifiers are properly escaped // Injection attempt blocked at construction\ninstance.T(\"users; DROP TABLE users--\")  // Panics: table not in schema\ninstance.F(\"id' OR '1'='1\")              // Panics: field not in schema\ninstance.P(\"id; DELETE FROM users\")      // Panics: invalid parameter name The schema acts as an allowlist. If it's not in the schema, it can't be in the query.",{"id":26,"title":27,"titles":28,"content":29,"level":19},"/v1.0.10/overview#capabilities","Capabilities",[6],"A schema-validated query builder enables: Type Safety — Fields, tables, and parameters are validated at construction time, not at execution time. Complex Queries — JOINs, subqueries, aggregates, window functions, CASE expressions—all with the same validation guarantees. PostgreSQL Features — RETURNING, ON CONFLICT, DISTINCT ON, row locking, pgvector operators. Composability — Build queries programmatically. Combine conditions. Nest subqueries. ASTQL provides the query building layer. Execution is handled by sqlx or your database driver of choice.",{"id":31,"title":32,"titles":33,"content":34,"level":19},"/v1.0.10/overview#priorities","Priorities",[6],"",{"id":36,"title":37,"titles":38,"content":39,"level":40},"/v1.0.10/overview#security","Security",[6,32],"SQL injection is prevented through multiple layers: LayerProtectionSchema validationTables/fields must exist in DBMLIdentifier validationAlphanumeric + underscore onlyKeyword blockingRejects ;, --, ', SQL keywordsQuoted identifiersPostgreSQL double-quote escapingParameterized queriesNamed parameters, never interpolation",3,{"id":42,"title":43,"titles":44,"content":45,"level":40},"/v1.0.10/overview#ergonomics","Ergonomics",[6,32],"The fluent builder API reads naturally: astql.Select(table).\n    Fields(field1, field2).\n    Where(condition).\n    OrderBy(field1, astql.ASC).\n    Limit(10).\n    Render(postgres.New()) Method chaining. Compile-time errors for invalid operations. Clear separation between building and rendering.",{"id":47,"title":48,"titles":49,"content":50,"level":40},"/v1.0.10/overview#multi-provider-architecture","Multi-Provider Architecture",[6,32],"ASTQL supports multiple SQL dialects through providers: import (\n    \"github.com/zoobzio/astql/postgres\"\n    \"github.com/zoobzio/astql/sqlite\"\n)\n\n// PostgreSQL\nresult, _ := query.Render(postgres.New())\n\n// SQLite\nresult, _ := query.Render(sqlite.New()) Each provider handles dialect-specific syntax: identifier quoting, date functions, string concatenation, and unsupported feature rejection. PostgreSQL supports RETURNING, ON CONFLICT, DISTINCT ON, and pgvector operators. SQLite gracefully rejects unsupported features with clear error messages. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}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 .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 .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":52,"title":53,"titles":54,"content":55,"level":9},"/v1.0.10/learn/quickstart","Quickstart",[],"Get started with astql in minutes",{"id":57,"title":53,"titles":58,"content":34,"level":9},"/v1.0.10/learn/quickstart#quickstart",[],{"id":60,"title":61,"titles":62,"content":63,"level":19},"/v1.0.10/learn/quickstart#requirements","Requirements",[53],"Go 1.24 or later.",{"id":65,"title":66,"titles":67,"content":68,"level":19},"/v1.0.10/learn/quickstart#installation","Installation",[53],"go get github.com/zoobzio/astql\ngo get github.com/zoobzio/dbml",{"id":70,"title":71,"titles":72,"content":73,"level":19},"/v1.0.10/learn/quickstart#basic-usage","Basic Usage",[53],"package main\n\nimport (\n    \"fmt\"\n    \"github.com/zoobzio/astql\"\n    \"github.com/zoobzio/astql/postgres\"\n    \"github.com/zoobzio/dbml\"\n)\n\nfunc main() {\n    // 1. Define your schema\n    project := dbml.NewProject(\"myapp\")\n\n    users := dbml.NewTable(\"users\")\n    users.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\n    users.AddColumn(dbml.NewColumn(\"username\", \"varchar\"))\n    users.AddColumn(dbml.NewColumn(\"email\", \"varchar\"))\n    users.AddColumn(dbml.NewColumn(\"active\", \"boolean\"))\n    project.AddTable(users)\n\n    // 2. Create an ASTQL instance\n    instance, err := astql.NewFromDBML(project)\n    if err != nil {\n        panic(err)\n    }\n\n    // 3. Build a query\n    result, err := astql.Select(instance.T(\"users\")).\n        Fields(instance.F(\"username\"), instance.F(\"email\")).\n        Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\"))).\n        OrderBy(instance.F(\"username\"), astql.ASC).\n        Limit(10).\n        Render(postgres.New())\n\n    if err != nil {\n        panic(err)\n    }\n\n    fmt.Println(result.SQL)\n    // SELECT \"username\", \"email\" FROM \"users\" WHERE \"active\" = :is_active ORDER BY \"username\" ASC LIMIT 10\n\n    fmt.Println(result.RequiredParams)\n    // [is_active]\n}",{"id":75,"title":76,"titles":77,"content":78,"level":19},"/v1.0.10/learn/quickstart#whats-happening","What's Happening",[53],"dbml.NewProject creates a schema definitiondbml.NewTable and AddColumn define your table structureastql.NewFromDBML creates a validated instance bound to your schemainstance.T, instance.F, instance.P create validated referencesastql.Select starts a query builder chain.Render(provider) produces SQL and a list of required parameters using the specified provider",{"id":80,"title":81,"titles":82,"content":83,"level":19},"/v1.0.10/learn/quickstart#using-with-sqlx","Using with sqlx",[53],"The output is designed for use with sqlx named queries: result, _ := astql.Select(instance.T(\"users\")).\n    Fields(instance.F(\"username\"), instance.F(\"email\")).\n    Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\"))).\n    Render(postgres.New())\n\n// Execute with sqlx\nparams := map[string]any{\"is_active\": true}\nrows, err := db.NamedQuery(result.SQL, params) 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 .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 .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 .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 .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":85,"title":86,"titles":87,"content":88,"level":9},"/v1.0.10/learn/concepts","Core Concepts",[],"Tables, fields, params, conditions, and builders - the building blocks of astql",{"id":90,"title":86,"titles":91,"content":92,"level":9},"/v1.0.10/learn/concepts#core-concepts",[],"ASTQL has five primitives: tables, fields, params, conditions, and builders. Understanding these unlocks the full API.",{"id":94,"title":95,"titles":96,"content":97,"level":19},"/v1.0.10/learn/concepts#tables","Tables",[86],"A table represents a database table. Create tables through an ASTQL instance: users := instance.T(\"users\") Tables can have single-letter aliases for JOINs: users := instance.T(\"users\", \"u\")  // \"users\" aliased as \"u\"\nposts := instance.T(\"posts\", \"p\")  // \"posts\" aliased as \"p\" Aliases must be single lowercase letters (a-z). This restriction prevents injection through alias names. instance.T(\"users\", \"u\")           // Valid\ninstance.T(\"users\", \"users_alias\") // Panics: invalid alias",{"id":99,"title":100,"titles":101,"content":102,"level":19},"/v1.0.10/learn/concepts#fields","Fields",[86],"A field represents a column. Create fields through an ASTQL instance: email := instance.F(\"email\")\nusername := instance.F(\"username\") Fields can be prefixed with a table alias: userEmail := instance.WithTable(instance.F(\"email\"), \"u\")\n// Renders as: u.\"email\"",{"id":104,"title":105,"titles":106,"content":107,"level":40},"/v1.0.10/learn/concepts#field-validation","Field Validation",[86,100],"Fields are validated against the schema. Any field name must exist in at least one table: instance.F(\"email\")           // Valid if \"email\" exists in any table\ninstance.F(\"nonexistent\")     // Panics: field not found in schema",{"id":109,"title":110,"titles":111,"content":112,"level":19},"/v1.0.10/learn/concepts#params","Params",[86],"A param represents a query parameter. All user values flow through params: emailParam := instance.P(\"email_value\")\nactiveParam := instance.P(\"is_active\") Params are rendered as named placeholders: // In SQL: WHERE \"email\" = :email_value",{"id":114,"title":115,"titles":116,"content":117,"level":40},"/v1.0.10/learn/concepts#parameter-validation","Parameter Validation",[86,110],"Parameter names must be valid SQL identifiers: instance.P(\"user_id\")                    // Valid\ninstance.P(\"id; DROP TABLE users--\")     // Panics: invalid parameter",{"id":119,"title":120,"titles":121,"content":122,"level":19},"/v1.0.10/learn/concepts#conditions","Conditions",[86],"Conditions represent WHERE clause predicates. Create conditions with instance.C: condition := instance.C(\n    instance.F(\"email\"),     // Field\n    astql.EQ,                // Operator\n    instance.P(\"email_val\"), // Param\n)",{"id":124,"title":125,"titles":126,"content":127,"level":40},"/v1.0.10/learn/concepts#operators","Operators",[86,120],"ConstantSQLDescriptionEQ=EqualsNE!=Not equalsGT>Greater thanGE>=Greater than or equalLT\u003CLess thanLE\u003C=Less than or equalLIKELIKEPattern matchILIKEILIKECase-insensitive pattern matchIN= ANY()In arrayNotIn!= ALL()Not in arrayIsNullIS NULLNull checkIsNotNullIS NOT NULLNot null check See Operators Reference for the complete list.",{"id":129,"title":130,"titles":131,"content":132,"level":40},"/v1.0.10/learn/concepts#combining-conditions","Combining Conditions",[86,120],"Use And and Or to combine conditions: // AND: both must be true\ninstance.And(\n    instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\")),\n    instance.C(instance.F(\"verified\"), astql.EQ, instance.P(\"is_verified\")),\n)\n// Renders: (\"active\" = :is_active AND \"verified\" = :is_verified)\n\n// OR: either must be true\ninstance.Or(\n    instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"admin_role\")),\n    instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"mod_role\")),\n)\n// Renders: (\"role\" = :admin_role OR \"role\" = :mod_role)",{"id":134,"title":135,"titles":136,"content":137,"level":40},"/v1.0.10/learn/concepts#nested-conditions","Nested Conditions",[86,120],"Conditions can be nested arbitrarily: instance.And(\n    instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\")),\n    instance.Or(\n        instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"admin\")),\n        instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"mod\")),\n    ),\n)\n// Renders: (\"active\" = :is_active AND (\"role\" = :admin OR \"role\" = :mod))",{"id":139,"title":140,"titles":141,"content":142,"level":40},"/v1.0.10/learn/concepts#null-conditions","NULL Conditions",[86,120],"Use Null and NotNull for NULL checks: instance.Null(instance.F(\"deleted_at\"))     // \"deleted_at\" IS NULL\ninstance.NotNull(instance.F(\"verified_at\")) // \"verified_at\" IS NOT NULL",{"id":144,"title":145,"titles":146,"content":147,"level":40},"/v1.0.10/learn/concepts#field-comparisons","Field Comparisons",[86,120],"Compare two fields directly with CF: astql.CF(\n    instance.F(\"created_at\"),\n    astql.LT,\n    instance.F(\"updated_at\"),\n)\n// Renders: \"created_at\" \u003C \"updated_at\"",{"id":149,"title":150,"titles":151,"content":152,"level":19},"/v1.0.10/learn/concepts#builders","Builders",[86],"Builders construct queries through method chaining. Each operation type has its own entry point: astql.Select(table)   // SELECT query\nastql.Insert(table)   // INSERT query\nastql.Update(table)   // UPDATE query\nastql.Delete(table)   // DELETE query\nastql.Count(table)    // SELECT COUNT(*) query",{"id":154,"title":155,"titles":156,"content":157,"level":40},"/v1.0.10/learn/concepts#fluent-api","Fluent API",[86,150],"Methods return the builder for chaining: result, err := astql.Select(instance.T(\"users\")).\n    Fields(instance.F(\"username\"), instance.F(\"email\")).\n    Where(condition).\n    OrderBy(instance.F(\"username\"), astql.ASC).\n    Limit(10).\n    Offset(20).\n    Render(postgres.New())",{"id":159,"title":160,"titles":161,"content":162,"level":40},"/v1.0.10/learn/concepts#build-vs-render","Build vs Render",[86,150],"Build() returns the AST for inspection or modificationRender(provider) produces the final SQL and parameter list using the specified provider // Get the AST\nast, err := query.Build()\n\n// Get SQL output\nresult, err := query.Render(postgres.New())",{"id":164,"title":165,"titles":166,"content":167,"level":40},"/v1.0.10/learn/concepts#must-variants","Must Variants",[86,150],"MustBuild and MustRender panic on error instead of returning it: result := query.MustRender(postgres.New())  // Panics if invalid",{"id":169,"title":170,"titles":171,"content":172,"level":19},"/v1.0.10/learn/concepts#try-variants","Try Variants",[86],"All instance methods have Try variants that return errors instead of panicking: // Panics on invalid input\ntable := instance.T(\"users\")\nfield := instance.F(\"email\")\nparam := instance.P(\"value\")\n\n// Returns error on invalid input\ntable, err := instance.TryT(\"users\")\nfield, err := instance.TryF(\"email\")\nparam, err := instance.TryP(\"value\") Use Try variants when handling user input or dynamic field names.",{"id":174,"title":175,"titles":176,"content":177,"level":19},"/v1.0.10/learn/concepts#query-result","Query Result",[86],"Render() returns a QueryResult: type QueryResult struct {\n    SQL            string   // The rendered SQL query\n    RequiredParams []string // Parameter names that must be provided\n} The RequiredParams slice lists all parameters in order of appearance. Use this to validate that all required values are provided before execution. 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 .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 .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}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 .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}",{"id":179,"title":16,"titles":180,"content":181,"level":9},"/v1.0.10/learn/architecture",[],"AST structure, render pipeline, and security layers",{"id":183,"title":16,"titles":184,"content":185,"level":9},"/v1.0.10/learn/architecture#architecture",[],"ASTQL uses a three-stage pipeline: validation, AST construction, and rendering.",{"id":187,"title":188,"titles":189,"content":190,"level":19},"/v1.0.10/learn/architecture#pipeline-overview","Pipeline Overview",[16],"┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐\n│    Input     │    │  Validation  │    │     AST      │    │    Output    │\n│              │    │              │    │              │    │              │\n│  Table name  │───▶│ DBML schema  │───▶│  AST nodes   │───▶│  SQL string  │\n│  Field name  │    │ Identifier   │    │  constructed │    │  Param list  │\n│  Param name  │    │ SQL keywords │    │              │    │              │\n└──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘ Each stage has a specific responsibility: Validation — Reject invalid identifiers before they enter the systemAST Construction — Build a tree of query componentsRendering — Convert the AST to SQL with proper escaping",{"id":192,"title":193,"titles":194,"content":34,"level":19},"/v1.0.10/learn/architecture#validation-layer","Validation Layer",[16],{"id":196,"title":197,"titles":198,"content":199,"level":40},"/v1.0.10/learn/architecture#schema-validation","Schema Validation",[16,193],"Tables and fields are validated against the DBML schema: instance, _ := astql.NewFromDBML(project)\n\ninstance.T(\"users\")       // Checks: does \"users\" table exist?\ninstance.F(\"email\")       // Checks: does \"email\" field exist in any table? The schema acts as an allowlist. If a table or field isn't defined, it can't be used in queries.",{"id":201,"title":202,"titles":203,"content":204,"level":40},"/v1.0.10/learn/architecture#identifier-validation","Identifier Validation",[16,193],"All identifiers (tables, fields, params, aliases) pass through identifier validation: func isValidSQLIdentifier(s string) bool {\n    // Must start with letter or underscore\n    // Rest must be alphanumeric or underscore\n    // No SQL keywords or suspicious patterns\n} Blocked patterns include: SQL comment markers: --, /*, */String delimiters: ', \", `Statement terminators: ;SQL keywords in context: OR, AND, DROP, DELETE, etc.",{"id":206,"title":207,"titles":208,"content":209,"level":40},"/v1.0.10/learn/architecture#alias-restrictions","Alias Restrictions",[16,193],"Table aliases are restricted to single lowercase letters: instance.T(\"users\", \"u\")  // Valid\ninstance.T(\"users\", \"ab\") // Invalid: must be single letter This prevents injection through alias names while providing enough aliases for complex joins.",{"id":211,"title":212,"titles":213,"content":214,"level":19},"/v1.0.10/learn/architecture#ast-structure","AST Structure",[16],"The Abstract Syntax Tree represents a query as nested Go structs: type AST struct {\n    Operation    Operation              // SELECT, INSERT, UPDATE, DELETE, COUNT\n    Target       Table                  // Main table\n    Fields       []Field                // Selected fields\n    WhereClause  ConditionItem          // WHERE conditions\n    Joins        []Join                 // JOIN clauses\n    GroupBy      []Field                // GROUP BY fields\n    Having       []ConditionItem        // HAVING conditions\n    Ordering     []OrderBy              // ORDER BY clauses\n    Limit        *PaginationValue       // LIMIT (static or parameterized)\n    Offset       *PaginationValue       // OFFSET (static or parameterized)\n    // ... additional fields for INSERT, UPDATE, etc.\n}",{"id":216,"title":217,"titles":218,"content":219,"level":40},"/v1.0.10/learn/architecture#internal-types","Internal Types",[16,212],"Core types are defined in internal/types: internal/types/\n├── ast.go        # AST struct and validation\n├── table.go      # Table type\n├── field.go      # Field type\n├── param.go      # Param type\n├── condition.go  # Condition types (simple, group, comparison)\n└── operator.go   # Operator constants These types are internal to prevent direct construction. All access goes through the instance API.",{"id":221,"title":222,"titles":223,"content":224,"level":40},"/v1.0.10/learn/architecture#condition-types","Condition Types",[16,212],"Conditions support multiple patterns: // Simple condition: field op param\ntype Condition struct {\n    Field    Field\n    Operator Operator\n    Value    Param\n}\n\n// Condition group: AND/OR of conditions\ntype ConditionGroup struct {\n    Logic      Logic           // AND or OR\n    Conditions []ConditionItem\n}\n\n// Field comparison: field op field\ntype FieldComparison struct {\n    LeftField  Field\n    Operator   Operator\n    RightField Field\n}\n\n// Subquery condition: field IN (subquery)\ntype SubqueryCondition struct {\n    Field    *Field\n    Operator Operator\n    Subquery Subquery\n}",{"id":226,"title":227,"titles":228,"content":229,"level":19},"/v1.0.10/learn/architecture#render-engine","Render Engine",[16],"The render engine converts AST nodes to SQL strings.",{"id":231,"title":232,"titles":233,"content":234,"level":40},"/v1.0.10/learn/architecture#identifier-quoting","Identifier Quoting",[16,227],"All identifiers are quoted using PostgreSQL double-quote syntax: func quoteIdentifier(name string) string {\n    escaped := strings.ReplaceAll(name, `\"`, `\"\"`)\n    return `\"` + escaped + `\"`\n} This handles reserved words and special characters safely.",{"id":236,"title":237,"titles":238,"content":239,"level":40},"/v1.0.10/learn/architecture#parameter-placeholders","Parameter Placeholders",[16,227],"Parameters are rendered as named placeholders: func (ctx *renderContext) addParam(param Param) string {\n    placeholder := \":\" + param.Name\n    // Track for RequiredParams list\n    return placeholder\n} The output uses :param_name syntax compatible with sqlx.",{"id":241,"title":242,"titles":243,"content":244,"level":40},"/v1.0.10/learn/architecture#parameter-namespacing","Parameter Namespacing",[16,227],"Parameters are prefixed to prevent collisions in complex queries. Nested subqueries (IN, EXISTS, etc.): // Main query: :user_id\n// First subquery (depth 1): :sq1_user_id\n// Nested subquery (depth 2): :sq2_user_id\n// Nested subquery (depth 3): :sq3_user_id Maximum subquery depth is 3 levels. Compound queries (UNION, INTERSECT, EXCEPT): // Base query: :q0_user_id\n// Second query: :q1_user_id\n// Third query: :q2_user_id Each query in a compound operation gets its own q{n}_ prefix. Subqueries within compound queries still use sq{n}_ prefixes relative to their containing query.",{"id":246,"title":247,"titles":248,"content":249,"level":19},"/v1.0.10/learn/architecture#provider-architecture","Provider Architecture",[16],"Rendering is handled by dialect-specific providers that implement the Renderer interface: type Renderer interface {\n    Render(ast *types.AST) (*types.QueryResult, error)\n    RenderCompound(query *types.CompoundQuery) (*types.QueryResult, error)\n    Capabilities() render.Capabilities\n} The Capabilities() method allows external services to query what features a dialect supports before execution. See the API Reference for details.",{"id":251,"title":252,"titles":253,"content":254,"level":40},"/v1.0.10/learn/architecture#available-providers","Available Providers",[16,247],"ProviderImportNotesPostgreSQLgithub.com/zoobzio/astql/postgresFull feature supportSQLitegithub.com/zoobzio/astql/sqliteRejects unsupported featuresMariaDBgithub.com/zoobzio/astql/mariadbON DUPLICATE KEY UPDATE, RETURNING (10.5+)SQL Servergithub.com/zoobzio/astql/mssqlOUTPUT clause, OFFSET/FETCH syntax",{"id":256,"title":257,"titles":258,"content":259,"level":40},"/v1.0.10/learn/architecture#usage","Usage",[16,247],"import \"github.com/zoobzio/astql/sqlite\"\n\n// Use specific provider\nresult, err := query.Render(sqlite.New())",{"id":261,"title":262,"titles":263,"content":264,"level":40},"/v1.0.10/learn/architecture#dialect-differences","Dialect Differences",[16,247],"Providers handle syntax differences automatically: FeaturePostgreSQLSQLiteMariaDBSQL ServerIdentifier quoting\"name\"\"name\"`name`[name]Param placeholder:name:name:name:nameString concatCONCAT()||CONCAT()CONCAT()String lengthLENGTH()LENGTH()LENGTH()LEN()Current timeNOW()DATETIME('now')NOW()GETDATE()Extract yearEXTRACT(YEAR FROM d)STRFTIME('%Y', d)EXTRACT(YEAR FROM d)DATEPART(YEAR, d)LIMIT/OFFSETLIMIT n OFFSET mLIMIT n OFFSET mLIMIT n OFFSET mOFFSET m ROWS FETCH NEXT n ROWS ONLYRETURNINGRETURNINGRETURNINGRETURNINGOUTPUTUpsertON CONFLICTON CONFLICTON DUPLICATE KEY UPDATEUnsupported Each provider rejects unsupported features with clear errors rather than generating invalid SQL.",{"id":266,"title":267,"titles":268,"content":269,"level":19},"/v1.0.10/learn/architecture#file-structure","File Structure",[16],"astql/\n├── api.go           # Public types and package docs\n├── builder.go       # Query builders (Select, Insert, Update, Delete)\n├── expressions.go   # Expression helpers (Sum, Case, Window, String, Date, etc.)\n├── instance.go      # ASTQL instance and validation\n├── renderer.go      # Renderer interface\n├── internal/\n│   ├── types/       # Internal AST types\n│   │   ├── ast.go\n│   │   ├── condition.go\n│   │   ├── field.go\n│   │   ├── operator.go\n│   │   ├── param.go\n│   │   └── table.go\n│   └── render/      # Shared render utilities\n│       └── errors.go\n└── pkg/\n    ├── postgres/    # PostgreSQL provider\n    │   └── postgres.go\n    ├── sqlite/      # SQLite provider\n    │   └── sqlite.go\n    ├── mariadb/     # MariaDB provider\n    │   └── mariadb.go\n    └── mssql/       # SQL Server provider\n        └── mssql.go",{"id":271,"title":272,"titles":273,"content":274,"level":19},"/v1.0.10/learn/architecture#security-layers","Security Layers",[16],"Defense in depth through multiple layers: LayerWhat It BlocksSchema validationUnknown tables/fieldsIdentifier validationSpecial characters, SQL keywordsAlias restrictionsMulti-character aliasesQuoted identifiersReserved words, special charsParameterized queriesValue injectionSubquery depth limitsRecursive attacks Each layer provides independent protection. An attacker would need to bypass all layers to inject SQL.",{"id":276,"title":277,"titles":278,"content":34,"level":19},"/v1.0.10/learn/architecture#extension-points","Extension Points",[16],{"id":280,"title":281,"titles":282,"content":283,"level":40},"/v1.0.10/learn/architecture#custom-expressions","Custom Expressions",[16,277],"Add field expressions for aggregates, math functions, window functions: astql.Sum(field)                    // SUM(\"field\")\nastql.Round(field)                  // ROUND(\"field\")\nastql.Round(field, precision)       // ROUND(\"field\", :precision)\nastql.RowNumber().Over(spec).As(\"rank\")  // ROW_NUMBER() OVER (...) AS \"rank\"",{"id":285,"title":286,"titles":287,"content":288,"level":40},"/v1.0.10/learn/architecture#compound-queries","Compound Queries",[16,277],"Set operations combine queries: query1.Union(query2).OrderBy(field, astql.ASC)\n// (SELECT ...) UNION (SELECT ...) ORDER BY \"field\" ASC",{"id":290,"title":291,"titles":292,"content":293,"level":40},"/v1.0.10/learn/architecture#direct-ast-access","Direct AST Access",[16,277],"For advanced use cases, access the AST directly: ast, _ := query.Build()\n// Inspect or modify ast\nresult, _ := postgres.New().Render(ast) 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 .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 .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}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 .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}",{"id":295,"title":197,"titles":296,"content":297,"level":9},"/v1.0.10/guides/schema-validation",[],"DBML integration and validation patterns",{"id":299,"title":197,"titles":300,"content":301,"level":9},"/v1.0.10/guides/schema-validation#schema-validation",[],"ASTQL validates queries against a DBML schema. This guide covers schema setup, validation behavior, and error handling.",{"id":303,"title":304,"titles":305,"content":306,"level":19},"/v1.0.10/guides/schema-validation#defining-a-schema","Defining a Schema",[197],"Create a DBML project with tables and columns: import \"github.com/zoobzio/dbml\"\n\nproject := dbml.NewProject(\"myapp\")\n\n// Users table\nusers := dbml.NewTable(\"users\")\nusers.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\nusers.AddColumn(dbml.NewColumn(\"username\", \"varchar\"))\nusers.AddColumn(dbml.NewColumn(\"email\", \"varchar\"))\nusers.AddColumn(dbml.NewColumn(\"active\", \"boolean\"))\nusers.AddColumn(dbml.NewColumn(\"created_at\", \"timestamp\"))\nproject.AddTable(users)\n\n// Posts table\nposts := dbml.NewTable(\"posts\")\nposts.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\nposts.AddColumn(dbml.NewColumn(\"user_id\", \"bigint\"))\nposts.AddColumn(dbml.NewColumn(\"title\", \"varchar\"))\nposts.AddColumn(dbml.NewColumn(\"content\", \"text\"))\nposts.AddColumn(dbml.NewColumn(\"published\", \"boolean\"))\nproject.AddTable(posts)",{"id":308,"title":309,"titles":310,"content":311,"level":19},"/v1.0.10/guides/schema-validation#creating-an-instance","Creating an Instance",[197],"Bind the schema to an ASTQL instance: instance, err := astql.NewFromDBML(project)\nif err != nil {\n    return err\n} The instance caches table and field lookups for fast validation.",{"id":313,"title":314,"titles":315,"content":34,"level":19},"/v1.0.10/guides/schema-validation#validation-behavior","Validation Behavior",[197],{"id":317,"title":318,"titles":319,"content":320,"level":40},"/v1.0.10/guides/schema-validation#table-validation","Table Validation",[197,314],"Tables must exist in the schema: instance.T(\"users\")        // Valid: table exists\ninstance.T(\"nonexistent\")  // Panics: table not found",{"id":322,"title":105,"titles":323,"content":324,"level":40},"/v1.0.10/guides/schema-validation#field-validation",[197,314],"Fields must exist in at least one table: instance.F(\"email\")        // Valid: exists in users table\ninstance.F(\"title\")        // Valid: exists in posts table\ninstance.F(\"foo\")          // Panics: field not found Fields are validated by name only, not by table association. This allows using the same field name across tables in JOINs.",{"id":326,"title":115,"titles":327,"content":328,"level":40},"/v1.0.10/guides/schema-validation#parameter-validation",[197,314],"Parameters must be valid SQL identifiers: instance.P(\"user_id\")      // Valid\ninstance.P(\"email123\")     // Valid\ninstance.P(\"invalid-name\") // Panics: contains hyphen\ninstance.P(\"1starts\")      // Panics: starts with number",{"id":330,"title":331,"titles":332,"content":333,"level":19},"/v1.0.10/guides/schema-validation#panic-vs-error","Panic vs Error",[197],"By default, validation failures panic. This catches errors early during development: // These panic on invalid input\ntable := instance.T(\"users\")\nfield := instance.F(\"email\")\nparam := instance.P(\"value\")",{"id":335,"title":170,"titles":336,"content":337,"level":40},"/v1.0.10/guides/schema-validation#try-variants",[197,331],"For runtime validation with user input, use Try variants: table, err := instance.TryT(tableName)\nif err != nil {\n    return fmt.Errorf(\"invalid table: %w\", err)\n}\n\nfield, err := instance.TryF(fieldName)\nif err != nil {\n    return fmt.Errorf(\"invalid field: %w\", err)\n}\n\nparam, err := instance.TryP(paramName)\nif err != nil {\n    return fmt.Errorf(\"invalid param: %w\", err)\n}",{"id":339,"title":340,"titles":341,"content":342,"level":40},"/v1.0.10/guides/schema-validation#when-to-use-each","When to Use Each",[197,331],"ScenarioUseStatic field names in codeinstance.F(\"email\")User-provided field namesinstance.TryF(userInput)Configuration-driven queriesinstance.TryT(config.TableName)TestsEither works",{"id":344,"title":345,"titles":346,"content":347,"level":19},"/v1.0.10/guides/schema-validation#table-aliases","Table Aliases",[197],"Table aliases enable JOINs with field disambiguation: users := instance.T(\"users\", \"u\")  // Alias: u\nposts := instance.T(\"posts\", \"p\")  // Alias: p",{"id":349,"title":207,"titles":350,"content":351,"level":40},"/v1.0.10/guides/schema-validation#alias-restrictions",[197,345],"Aliases must be single lowercase letters (a-z): instance.T(\"users\", \"u\")     // Valid\ninstance.T(\"users\", \"x\")     // Valid\ninstance.T(\"users\", \"ab\")    // Panics: not single letter\ninstance.T(\"users\", \"U\")     // Panics: not lowercase\ninstance.T(\"users\", \"1\")     // Panics: not a letter This restriction prevents SQL injection through alias names.",{"id":353,"title":354,"titles":355,"content":356,"level":40},"/v1.0.10/guides/schema-validation#using-aliases-with-fields","Using Aliases with Fields",[197,345],"Prefix fields with their table alias: users := instance.T(\"users\", \"u\")\nposts := instance.T(\"posts\", \"p\")\n\n// Fields with table prefix\nuserID := instance.WithTable(instance.F(\"id\"), \"u\")      // u.\"id\"\npostUserID := instance.WithTable(instance.F(\"user_id\"), \"p\") // p.\"user_id\"",{"id":358,"title":359,"titles":360,"content":34,"level":19},"/v1.0.10/guides/schema-validation#schema-organization","Schema Organization",[197],{"id":362,"title":363,"titles":364,"content":365,"level":40},"/v1.0.10/guides/schema-validation#single-schema-instance","Single Schema Instance",[197,359],"Create one instance per schema and reuse it: // In a package-level variable or dependency injection\nvar db *astql.ASTQL\n\nfunc init() {\n    project := buildSchema()\n    instance, err := astql.NewFromDBML(project)\n    if err != nil {\n        panic(err)\n    }\n    db = instance\n}\n\nfunc GetUsers() (*astql.QueryResult, error) {\n    return astql.Select(db.T(\"users\")).\n        Fields(db.F(\"username\"), db.F(\"email\")).\n        Render(postgres.New())\n}",{"id":367,"title":368,"titles":369,"content":370,"level":40},"/v1.0.10/guides/schema-validation#multiple-schemas","Multiple Schemas",[197,359],"For microservices with different database schemas: var (\n    usersDB    *astql.ASTQL  // User service schema\n    ordersDB   *astql.ASTQL  // Order service schema\n    analyticsDB *astql.ASTQL // Analytics schema\n) Each instance validates against its own schema independently.",{"id":372,"title":373,"titles":374,"content":34,"level":19},"/v1.0.10/guides/schema-validation#dynamic-queries","Dynamic Queries",[197],{"id":376,"title":377,"titles":378,"content":379,"level":40},"/v1.0.10/guides/schema-validation#safe-dynamic-field-selection","Safe Dynamic Field Selection",[197,373],"func SelectFields(instance *astql.ASTQL, tableName string, fieldNames []string) (*astql.QueryResult, error) {\n    table, err := instance.TryT(tableName)\n    if err != nil {\n        return nil, err\n    }\n\n    // Use instance.Fields() to get a typed slice\n    fields := instance.Fields()\n    for _, name := range fieldNames {\n        field, err := instance.TryF(name)\n        if err != nil {\n            return nil, fmt.Errorf(\"invalid field %q: %w\", name, err)\n        }\n        fields = append(fields, field)\n    }\n\n    return astql.Select(table).Fields(fields...).Render(postgres.New())\n}",{"id":381,"title":382,"titles":383,"content":384,"level":40},"/v1.0.10/guides/schema-validation#dynamic-conditions","Dynamic Conditions",[197,373],"func BuildFilter(instance *astql.ASTQL, filters map[string]string) (astql.ConditionItem, error) {\n    // Use instance.ConditionItems() to get a typed slice\n    conditions := instance.ConditionItems()\n\n    for fieldName, paramName := range filters {\n        field, err := instance.TryF(fieldName)\n        if err != nil {\n            return nil, err\n        }\n        param, err := instance.TryP(paramName)\n        if err != nil {\n            return nil, err\n        }\n        conditions = append(conditions, instance.C(field, astql.EQ, param))\n    }\n\n    if len(conditions) == 0 {\n        return nil, nil\n    }\n    if len(conditions) == 1 {\n        return conditions[0], nil\n    }\n    return instance.And(conditions...), nil\n}",{"id":386,"title":387,"titles":388,"content":34,"level":19},"/v1.0.10/guides/schema-validation#validation-errors","Validation Errors",[197],{"id":390,"title":391,"titles":392,"content":393,"level":40},"/v1.0.10/guides/schema-validation#error-types","Error Types",[197,387],"ErrorCausetable 'X' not found in schemaTable doesn't exist in DBMLfield 'X' not found in schemaField doesn't exist in any tableinvalid parameter name: XParameter contains invalid charactersalias must be single lowercase letterTable alias is not a-z",{"id":395,"title":396,"titles":397,"content":398,"level":40},"/v1.0.10/guides/schema-validation#handling-errors","Handling Errors",[197,387],"result, err := query.Render(postgres.New())\nif err != nil {\n    switch {\n    case strings.Contains(err.Error(), \"not found in schema\"):\n        // Schema mismatch - likely a bug or migration issue\n        log.Error(\"schema validation failed\", \"error\", err)\n    case strings.Contains(err.Error(), \"invalid\"):\n        // Invalid input\n        return nil, fmt.Errorf(\"invalid query: %w\", err)\n    default:\n        return nil, err\n    }\n} html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}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 .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 .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 .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":400,"title":120,"titles":401,"content":402,"level":9},"/v1.0.10/guides/conditions",[],"WHERE clauses, AND/OR logic, subqueries, and BETWEEN",{"id":404,"title":120,"titles":405,"content":406,"level":9},"/v1.0.10/guides/conditions#conditions",[],"Conditions define the WHERE clause of your queries. ASTQL provides type-safe condition building with support for complex logic.",{"id":408,"title":409,"titles":410,"content":411,"level":19},"/v1.0.10/guides/conditions#basic-conditions","Basic Conditions",[120],"Create a condition with field, operator, and parameter: condition := instance.C(\n    instance.F(\"email\"),     // Field\n    astql.EQ,                // Operator\n    instance.P(\"email_val\"), // Parameter\n)\n\nresult, _ := astql.Select(instance.T(\"users\")).\n    Where(condition).\n    Render()\n// SELECT * FROM \"users\" WHERE \"email\" = :email_val",{"id":413,"title":125,"titles":414,"content":34,"level":19},"/v1.0.10/guides/conditions#operators",[120],{"id":416,"title":417,"titles":418,"content":419,"level":40},"/v1.0.10/guides/conditions#comparison-operators","Comparison Operators",[120,125],"ConstantSQLExampleEQ=\"age\" = :ageNE!=\"status\" != :statusGT>\"price\" > :minGE>=\"score\" >= :thresholdLT\u003C\"date\" \u003C :cutoffLE\u003C=\"count\" \u003C= :max",{"id":421,"title":422,"titles":423,"content":424,"level":40},"/v1.0.10/guides/conditions#pattern-matching","Pattern Matching",[120,125],"ConstantSQLExampleLIKELIKE\"name\" LIKE :patternNotLikeNOT LIKE\"name\" NOT LIKE :patternILIKEILIKE\"name\" ILIKE :pattern (case-insensitive)NotILikeNOT ILIKE\"name\" NOT ILIKE :pattern",{"id":426,"title":427,"titles":428,"content":429,"level":40},"/v1.0.10/guides/conditions#null-checks","NULL Checks",[120,125],"// IS NULL\ninstance.Null(instance.F(\"deleted_at\"))\n// \"deleted_at\" IS NULL\n\n// IS NOT NULL\ninstance.NotNull(instance.F(\"verified_at\"))\n// \"verified_at\" IS NOT NULL",{"id":431,"title":432,"titles":433,"content":434,"level":40},"/v1.0.10/guides/conditions#array-operators","Array Operators",[120,125],"// IN (uses PostgreSQL ANY)\ninstance.C(instance.F(\"id\"), astql.IN, instance.P(\"ids\"))\n// \"id\" = ANY(:ids)\n\n// NOT IN (uses PostgreSQL ALL)\ninstance.C(instance.F(\"status\"), astql.NotIn, instance.P(\"excluded\"))\n// \"status\" != ALL(:excluded) When executing with sqlx, wrap slices with pq.Array(): params := map[string]any{\n    \"ids\": pq.Array([]int{1, 2, 3}),\n}",{"id":436,"title":130,"titles":437,"content":34,"level":19},"/v1.0.10/guides/conditions#combining-conditions",[120],{"id":439,"title":440,"titles":441,"content":442,"level":40},"/v1.0.10/guides/conditions#and","AND",[120,130],"All conditions must be true: instance.And(\n    instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\")),\n    instance.C(instance.F(\"verified\"), astql.EQ, instance.P(\"is_verified\")),\n)\n// (\"active\" = :is_active AND \"verified\" = :is_verified)",{"id":444,"title":445,"titles":446,"content":447,"level":40},"/v1.0.10/guides/conditions#or","OR",[120,130],"At least one condition must be true: instance.Or(\n    instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"admin\")),\n    instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"moderator\")),\n)\n// (\"role\" = :admin OR \"role\" = :moderator)",{"id":449,"title":450,"titles":451,"content":452,"level":40},"/v1.0.10/guides/conditions#nested-logic","Nested Logic",[120,130],"Combine AND and OR for complex conditions: instance.And(\n    instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\")),\n    instance.Or(\n        instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"admin\")),\n        instance.And(\n            instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"user\")),\n            instance.C(instance.F(\"verified\"), astql.EQ, instance.P(\"is_verified\")),\n        ),\n    ),\n)\n// (\"active\" = :is_active AND (\"role\" = :admin OR (\"role\" = :user AND \"verified\" = :is_verified)))",{"id":454,"title":455,"titles":456,"content":457,"level":19},"/v1.0.10/guides/conditions#between","BETWEEN",[120],"Use Between and NotBetween for range conditions: // BETWEEN\nastql.Between(\n    instance.F(\"price\"),\n    instance.P(\"min_price\"),\n    instance.P(\"max_price\"),\n)\n// \"price\" BETWEEN :min_price AND :max_price\n\n// NOT BETWEEN\nastql.NotBetween(\n    instance.F(\"date\"),\n    instance.P(\"start\"),\n    instance.P(\"end\"),\n)\n// \"date\" NOT BETWEEN :start AND :end",{"id":459,"title":145,"titles":460,"content":461,"level":19},"/v1.0.10/guides/conditions#field-comparisons",[120],"Compare two fields directly: astql.CF(\n    instance.F(\"updated_at\"),\n    astql.GT,\n    instance.F(\"created_at\"),\n)\n// \"updated_at\" > \"created_at\" Useful for: Comparing timestampsSelf-referential conditionsCross-table comparisons in JOINs",{"id":463,"title":464,"titles":465,"content":34,"level":19},"/v1.0.10/guides/conditions#subqueries","Subqueries",[120],{"id":467,"title":468,"titles":469,"content":470,"level":40},"/v1.0.10/guides/conditions#in-subquery","IN Subquery",[120,464],"subquery := astql.Sub(\n    astql.Select(instance.T(\"orders\")).\n        Fields(instance.F(\"user_id\")).\n        Where(instance.C(instance.F(\"total\"), astql.GT, instance.P(\"min_total\"))),\n)\n\nresult, _ := astql.Select(instance.T(\"users\")).\n    Where(astql.CSub(instance.F(\"id\"), astql.IN, subquery)).\n    Render()\n\n// SELECT * FROM \"users\"\n// WHERE \"id\" IN (SELECT \"user_id\" FROM \"orders\" WHERE \"total\" > :sq1_min_total)",{"id":472,"title":473,"titles":474,"content":475,"level":40},"/v1.0.10/guides/conditions#not-in-subquery","NOT IN Subquery",[120,464],"astql.CSub(instance.F(\"id\"), astql.NotIn, subquery)\n// \"id\" NOT IN (SELECT ...)",{"id":477,"title":478,"titles":479,"content":480,"level":40},"/v1.0.10/guides/conditions#exists","EXISTS",[120,464],"subquery := astql.Sub(\n    astql.Select(instance.T(\"orders\")).\n        Fields(instance.F(\"id\")).\n        Where(astql.CF(\n            instance.WithTable(instance.F(\"user_id\"), \"o\"),\n            astql.EQ,\n            instance.WithTable(instance.F(\"id\"), \"u\"),\n        )),\n)\n\nresult, _ := astql.Select(instance.T(\"users\", \"u\")).\n    Where(astql.CSubExists(astql.EXISTS, subquery)).\n    Render()\n\n// SELECT * FROM \"users\" u\n// WHERE EXISTS (SELECT \"id\" FROM \"orders\" WHERE o.\"user_id\" = u.\"id\")",{"id":482,"title":483,"titles":484,"content":485,"level":40},"/v1.0.10/guides/conditions#not-exists","NOT EXISTS",[120,464],"astql.CSubExists(astql.NotExists, subquery)\n// NOT EXISTS (SELECT ...)",{"id":487,"title":242,"titles":488,"content":489,"level":40},"/v1.0.10/guides/conditions#parameter-namespacing",[120,464],"Subquery parameters are automatically prefixed to prevent collisions: // Main query: :user_id\n// First subquery: :sq1_user_id\n// Nested subquery: :sq2_user_id Maximum subquery depth is 3 levels by default.",{"id":491,"title":492,"titles":493,"content":494,"level":19},"/v1.0.10/guides/conditions#multiple-where-clauses","Multiple WHERE Clauses",[120],"Calling Where multiple times combines conditions with AND: query := astql.Select(instance.T(\"users\")).\n    Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\"))).\n    Where(instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"role\")))\n\n// WHERE (\"active\" = :is_active AND \"role\" = :role)",{"id":496,"title":497,"titles":498,"content":499,"level":19},"/v1.0.10/guides/conditions#wherefield-shorthand","WhereField Shorthand",[120],"For simple conditions, use WhereField: // These are equivalent:\nquery.Where(instance.C(instance.F(\"id\"), astql.EQ, instance.P(\"id\")))\nquery.WhereField(instance.F(\"id\"), astql.EQ, instance.P(\"id\"))",{"id":501,"title":502,"titles":503,"content":504,"level":19},"/v1.0.10/guides/conditions#building-conditions-dynamically","Building Conditions Dynamically",[120],"func BuildConditions(instance *astql.ASTQL, filters []Filter) astql.ConditionItem {\n    if len(filters) == 0 {\n        return nil\n    }\n\n    // Use instance.ConditionItems() to get a typed slice\n    conditions := instance.ConditionItems()\n    for _, f := range filters {\n        field := instance.F(f.Field)\n        param := instance.P(f.Param)\n        conditions = append(conditions, instance.C(field, f.Operator, param))\n    }\n\n    if len(conditions) == 1 {\n        return conditions[0]\n    }\n    return instance.And(conditions...)\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 .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 .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 .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}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)}",{"id":506,"title":507,"titles":508,"content":509,"level":9},"/v1.0.10/guides/joins","Joins",[],"INNER, LEFT, RIGHT, and CROSS joins",{"id":511,"title":507,"titles":512,"content":513,"level":9},"/v1.0.10/guides/joins#joins",[],"ASTQL supports all standard SQL join types with type-safe ON conditions.",{"id":515,"title":516,"titles":517,"content":518,"level":19},"/v1.0.10/guides/joins#join-types","Join Types",[507],"MethodSQLJoin()INNER JOINInnerJoin()INNER JOINLeftJoin()LEFT JOINRightJoin()RIGHT JOINFullOuterJoin()FULL OUTER JOINCrossJoin()CROSS JOIN",{"id":520,"title":521,"titles":522,"content":523,"level":19},"/v1.0.10/guides/joins#basic-join","Basic Join",[507],"users := instance.T(\"users\", \"u\")\nposts := instance.T(\"posts\", \"p\")\n\nresult, _ := astql.Select(users).\n    Fields(\n        instance.WithTable(instance.F(\"username\"), \"u\"),\n        instance.WithTable(instance.F(\"title\"), \"p\"),\n    ).\n    Join(posts, astql.CF(\n        instance.WithTable(instance.F(\"id\"), \"u\"),\n        astql.EQ,\n        instance.WithTable(instance.F(\"user_id\"), \"p\"),\n    )).\n    Render()\n\n// SELECT u.\"username\", p.\"title\"\n// FROM \"users\" u\n// INNER JOIN \"posts\" p ON u.\"id\" = p.\"user_id\"",{"id":525,"title":345,"titles":526,"content":527,"level":19},"/v1.0.10/guides/joins#table-aliases",[507],"Aliases are required when the same field name exists in multiple tables: users := instance.T(\"users\", \"u\")     // Alias: u\norders := instance.T(\"orders\", \"o\")   // Alias: o\n\n// Disambiguate \"id\" field\nuserID := instance.WithTable(instance.F(\"id\"), \"u\")      // u.\"id\"\norderUserID := instance.WithTable(instance.F(\"user_id\"), \"o\") // o.\"user_id\"",{"id":529,"title":530,"titles":531,"content":532,"level":19},"/v1.0.10/guides/joins#left-join","LEFT JOIN",[507],"Returns all rows from the left table, with NULL for non-matching right rows: result, _ := astql.Select(instance.T(\"users\", \"u\")).\n    Fields(\n        instance.WithTable(instance.F(\"username\"), \"u\"),\n        instance.WithTable(instance.F(\"title\"), \"p\"),\n    ).\n    LeftJoin(instance.T(\"posts\", \"p\"), astql.CF(\n        instance.WithTable(instance.F(\"id\"), \"u\"),\n        astql.EQ,\n        instance.WithTable(instance.F(\"user_id\"), \"p\"),\n    )).\n    Render()\n\n// SELECT u.\"username\", p.\"title\"\n// FROM \"users\" u\n// LEFT JOIN \"posts\" p ON u.\"id\" = p.\"user_id\"",{"id":534,"title":535,"titles":536,"content":537,"level":19},"/v1.0.10/guides/joins#right-join","RIGHT JOIN",[507],"Returns all rows from the right table, with NULL for non-matching left rows: result, _ := astql.Select(instance.T(\"users\", \"u\")).\n    RightJoin(instance.T(\"posts\", \"p\"), joinCondition).\n    Render()\n\n// ... RIGHT JOIN \"posts\" p ON ...",{"id":539,"title":540,"titles":541,"content":542,"level":19},"/v1.0.10/guides/joins#full-outer-join","FULL OUTER JOIN",[507],"Returns all rows from both tables, with NULL for non-matches: result, _ := astql.Select(instance.T(\"users\", \"u\")).\n    FullOuterJoin(instance.T(\"posts\", \"p\"), joinCondition).\n    Render()\n\n// ... FULL OUTER JOIN \"posts\" p ON ...",{"id":544,"title":545,"titles":546,"content":547,"level":19},"/v1.0.10/guides/joins#cross-join","CROSS JOIN",[507],"Returns the Cartesian product (no ON clause): result, _ := astql.Select(instance.T(\"sizes\", \"s\")).\n    Fields(\n        instance.WithTable(instance.F(\"name\"), \"c\"),\n        instance.WithTable(instance.F(\"size\"), \"s\"),\n    ).\n    CrossJoin(instance.T(\"colors\", \"c\")).\n    Render()\n\n// SELECT c.\"name\", s.\"size\"\n// FROM \"sizes\" s\n// CROSS JOIN \"colors\" c",{"id":549,"title":550,"titles":551,"content":552,"level":19},"/v1.0.10/guides/joins#multiple-joins","Multiple Joins",[507],"Chain multiple joins: result, _ := astql.Select(instance.T(\"orders\", \"o\")).\n    Fields(\n        instance.WithTable(instance.F(\"id\"), \"o\"),\n        instance.WithTable(instance.F(\"username\"), \"u\"),\n        instance.WithTable(instance.F(\"name\"), \"p\"),\n    ).\n    Join(instance.T(\"users\", \"u\"), astql.CF(\n        instance.WithTable(instance.F(\"user_id\"), \"o\"),\n        astql.EQ,\n        instance.WithTable(instance.F(\"id\"), \"u\"),\n    )).\n    Join(instance.T(\"products\", \"p\"), astql.CF(\n        instance.WithTable(instance.F(\"product_id\"), \"o\"),\n        astql.EQ,\n        instance.WithTable(instance.F(\"id\"), \"p\"),\n    )).\n    Render()\n\n// SELECT o.\"id\", u.\"username\", p.\"name\"\n// FROM \"orders\" o\n// INNER JOIN \"users\" u ON o.\"user_id\" = u.\"id\"\n// INNER JOIN \"products\" p ON o.\"product_id\" = p.\"id\"",{"id":554,"title":555,"titles":556,"content":557,"level":19},"/v1.0.10/guides/joins#complex-on-conditions","Complex ON Conditions",[507],"Join conditions can use AND/OR logic: onCondition := instance.And(\n    astql.CF(\n        instance.WithTable(instance.F(\"id\"), \"u\"),\n        astql.EQ,\n        instance.WithTable(instance.F(\"user_id\"), \"p\"),\n    ),\n    instance.C(\n        instance.WithTable(instance.F(\"published\"), \"p\"),\n        astql.EQ,\n        instance.P(\"is_published\"),\n    ),\n)\n\nresult, _ := astql.Select(instance.T(\"users\", \"u\")).\n    LeftJoin(instance.T(\"posts\", \"p\"), onCondition).\n    Render()\n\n// LEFT JOIN \"posts\" p ON (u.\"id\" = p.\"user_id\" AND p.\"published\" = :is_published)",{"id":559,"title":560,"titles":561,"content":562,"level":19},"/v1.0.10/guides/joins#self-joins","Self Joins",[507],"Join a table to itself using different aliases: employees := instance.T(\"employees\", \"e\")\nmanagers := instance.T(\"employees\", \"m\")\n\nresult, _ := astql.Select(employees).\n    Fields(\n        instance.WithTable(instance.F(\"name\"), \"e\"),\n        instance.WithTable(instance.F(\"name\"), \"m\"),\n    ).\n    LeftJoin(managers, astql.CF(\n        instance.WithTable(instance.F(\"manager_id\"), \"e\"),\n        astql.EQ,\n        instance.WithTable(instance.F(\"id\"), \"m\"),\n    )).\n    Render()\n\n// SELECT e.\"name\", m.\"name\"\n// FROM \"employees\" e\n// LEFT JOIN \"employees\" m ON e.\"manager_id\" = m.\"id\"",{"id":564,"title":565,"titles":566,"content":567,"level":19},"/v1.0.10/guides/joins#joins-with-count","Joins with COUNT",[507],"Joins work with COUNT queries: result, _ := astql.Count(instance.T(\"users\", \"u\")).\n    Join(instance.T(\"orders\", \"o\"), astql.CF(\n        instance.WithTable(instance.F(\"id\"), \"u\"),\n        astql.EQ,\n        instance.WithTable(instance.F(\"user_id\"), \"o\"),\n    )).\n    Where(instance.C(\n        instance.WithTable(instance.F(\"status\"), \"o\"),\n        astql.EQ,\n        instance.P(\"status\"),\n    )).\n    Render()\n\n// SELECT COUNT(*) FROM \"users\" u\n// INNER JOIN \"orders\" o ON u.\"id\" = o.\"user_id\"\n// WHERE o.\"status\" = :status",{"id":569,"title":570,"titles":571,"content":572,"level":19},"/v1.0.10/guides/joins#join-validation","Join Validation",[507],"Tables in joins are validated against the schema: // Valid: both tables exist in schema\nastql.Select(instance.T(\"users\", \"u\")).\n    Join(instance.T(\"posts\", \"p\"), condition)\n\n// Invalid: unknown table\nastql.Select(instance.T(\"users\", \"u\")).\n    Join(instance.T(\"nonexistent\", \"n\"), condition)  // Panics 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 .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);}",{"id":574,"title":575,"titles":576,"content":577,"level":9},"/v1.0.10/guides/aggregates","Aggregates",[],"GROUP BY, HAVING, aggregate functions, and window functions",{"id":579,"title":575,"titles":580,"content":581,"level":9},"/v1.0.10/guides/aggregates#aggregates",[],"ASTQL supports SQL aggregate functions, GROUP BY, HAVING, and window functions.",{"id":583,"title":584,"titles":585,"content":34,"level":19},"/v1.0.10/guides/aggregates#aggregate-functions","Aggregate Functions",[575],{"id":587,"title":588,"titles":589,"content":590,"level":40},"/v1.0.10/guides/aggregates#basic-aggregates","Basic Aggregates",[575,584],"FunctionSQLDescriptionSum(field)SUM(\"field\")Sum of valuesAvg(field)AVG(\"field\")Average of valuesMin(field)MIN(\"field\")Minimum valueMax(field)MAX(\"field\")Maximum valueCountField(field)COUNT(\"field\")Count of non-null valuesCountDistinct(field)COUNT(DISTINCT \"field\")Count of unique values",{"id":592,"title":593,"titles":594,"content":595,"level":40},"/v1.0.10/guides/aggregates#using-aggregates","Using Aggregates",[575,584],"result, _ := astql.Select(instance.T(\"orders\")).\n    Fields(instance.F(\"user_id\")).\n    SelectExpr(astql.As(astql.Sum(instance.F(\"total\")), \"total_spent\")).\n    SelectExpr(astql.As(astql.CountField(instance.F(\"id\")), \"order_count\")).\n    GroupBy(instance.F(\"user_id\")).\n    Render()\n\n// SELECT \"user_id\", SUM(\"total\") AS \"total_spent\", COUNT(\"id\") AS \"order_count\"\n// FROM \"orders\"\n// GROUP BY \"user_id\"",{"id":597,"title":598,"titles":599,"content":600,"level":40},"/v1.0.10/guides/aggregates#aliases","Aliases",[575,584],"Use As() to add an alias: astql.As(astql.Sum(instance.F(\"amount\")), \"total_amount\")\n// SUM(\"amount\") AS \"total_amount\"",{"id":602,"title":603,"titles":604,"content":605,"level":19},"/v1.0.10/guides/aggregates#group-by","GROUP BY",[575],"Group results by one or more fields: result, _ := astql.Select(instance.T(\"orders\")).\n    Fields(instance.F(\"user_id\"), instance.F(\"status\")).\n    SelectExpr(astql.As(astql.CountField(instance.F(\"id\")), \"count\")).\n    GroupBy(instance.F(\"user_id\"), instance.F(\"status\")).\n    Render()\n\n// SELECT \"user_id\", \"status\", COUNT(\"id\") AS \"count\"\n// FROM \"orders\"\n// GROUP BY \"user_id\", \"status\"",{"id":607,"title":608,"titles":609,"content":610,"level":19},"/v1.0.10/guides/aggregates#having","HAVING",[575],"Filter grouped results:",{"id":612,"title":613,"titles":614,"content":615,"level":40},"/v1.0.10/guides/aggregates#simple-having","Simple HAVING",[575,608],"result, _ := astql.Select(instance.T(\"orders\")).\n    Fields(instance.F(\"user_id\")).\n    SelectExpr(astql.As(astql.Sum(instance.F(\"total\")), \"sum\")).\n    GroupBy(instance.F(\"user_id\")).\n    Having(instance.C(instance.F(\"total\"), astql.GT, instance.P(\"min_total\"))).\n    Render()\n\n// SELECT \"user_id\", SUM(\"total\") AS \"sum\"\n// FROM \"orders\"\n// GROUP BY \"user_id\"\n// HAVING \"total\" > :min_total",{"id":617,"title":618,"titles":619,"content":620,"level":40},"/v1.0.10/guides/aggregates#aggregate-having","Aggregate HAVING",[575,608],"For conditions on aggregate functions, use HavingAgg: result, _ := astql.Select(instance.T(\"orders\")).\n    Fields(instance.F(\"user_id\")).\n    SelectExpr(astql.As(astql.CountField(instance.F(\"id\")), \"order_count\")).\n    GroupBy(instance.F(\"user_id\")).\n    HavingAgg(astql.HavingCount(astql.GT, instance.P(\"min_orders\"))).\n    Render()\n\n// SELECT \"user_id\", COUNT(\"id\") AS \"order_count\"\n// FROM \"orders\"\n// GROUP BY \"user_id\"\n// HAVING COUNT(*) > :min_orders",{"id":622,"title":623,"titles":624,"content":625,"level":40},"/v1.0.10/guides/aggregates#having-helpers","HAVING Helpers",[575,608],"FunctionSQLHavingCount(op, param)COUNT(*) op :paramHavingCountField(field, op, param)COUNT(\"field\") op :paramHavingCountDistinct(field, op, param)COUNT(DISTINCT \"field\") op :paramHavingSum(field, op, param)SUM(\"field\") op :paramHavingAvg(field, op, param)AVG(\"field\") op :paramHavingMin(field, op, param)MIN(\"field\") op :paramHavingMax(field, op, param)MAX(\"field\") op :param",{"id":627,"title":628,"titles":629,"content":630,"level":19},"/v1.0.10/guides/aggregates#filter-clause","FILTER Clause",[575],"PostgreSQL's FILTER clause for conditional aggregation: result, _ := astql.Select(instance.T(\"orders\")).\n    Fields(instance.F(\"user_id\")).\n    SelectExpr(astql.As(\n        astql.SumFilter(\n            instance.F(\"total\"),\n            instance.C(instance.F(\"status\"), astql.EQ, instance.P(\"completed\")),\n        ),\n        \"completed_total\",\n    )).\n    GroupBy(instance.F(\"user_id\")).\n    Render()\n\n// SELECT \"user_id\",\n//        SUM(\"total\") FILTER (WHERE \"status\" = :completed) AS \"completed_total\"\n// FROM \"orders\"\n// GROUP BY \"user_id\" Available filter variants: SumFilter(field, condition)AvgFilter(field, condition)MinFilter(field, condition)MaxFilter(field, condition)CountFieldFilter(field, condition)CountDistinctFilter(field, condition)",{"id":632,"title":633,"titles":634,"content":635,"level":19},"/v1.0.10/guides/aggregates#window-functions","Window Functions",[575],"Window functions compute values across related rows.",{"id":637,"title":638,"titles":639,"content":640,"level":40},"/v1.0.10/guides/aggregates#basic-window-functions","Basic Window Functions",[575,633],"result, _ := astql.Select(instance.T(\"employees\")).\n    Fields(instance.F(\"name\"), instance.F(\"department\"), instance.F(\"salary\")).\n    SelectExpr(\n        astql.RowNumber().\n            OverBuilder(astql.Window().\n                PartitionBy(instance.F(\"department\")).\n                OrderBy(instance.F(\"salary\"), astql.DESC)).\n            As(\"rank\"),\n    ).\n    Render()\n\n// SELECT \"name\", \"department\", \"salary\",\n//        ROW_NUMBER() OVER (PARTITION BY \"department\" ORDER BY \"salary\" DESC) AS \"rank\"\n// FROM \"employees\"",{"id":642,"title":643,"titles":644,"content":645,"level":40},"/v1.0.10/guides/aggregates#window-function-types","Window Function Types",[575,633],"FunctionSQLRowNumber()ROW_NUMBER()Rank()RANK()DenseRank()DENSE_RANK()Ntile(param)NTILE(:param)Lag(field, offset) or Lag(field, offset, default)LAG(field, :offset) or LAG(field, :offset, :default)Lead(field, offset) or Lead(field, offset, default)LEAD(field, :offset) or LEAD(field, :offset, :default)FirstValue(field)FIRST_VALUE(field)LastValue(field)LAST_VALUE(field)",{"id":647,"title":648,"titles":649,"content":650,"level":40},"/v1.0.10/guides/aggregates#aggregate-window-functions","Aggregate Window Functions",[575,633],"// Running total\nastql.SumOver(instance.F(\"amount\")).\n    OverBuilder(astql.Window().\n        PartitionBy(instance.F(\"account_id\")).\n        OrderBy(instance.F(\"date\"), astql.ASC)).\n    As(\"running_total\")\n\n// SUM(\"amount\") OVER (PARTITION BY \"account_id\" ORDER BY \"date\" ASC) AS \"running_total\" Available: SumOver, AvgOver, CountOver, MinOver, MaxOver",{"id":652,"title":653,"titles":654,"content":655,"level":40},"/v1.0.10/guides/aggregates#window-specification","Window Specification",[575,633],"Build window specs with Window(): spec := astql.Window().\n    PartitionBy(instance.F(\"department\")).\n    OrderBy(instance.F(\"hire_date\"), astql.ASC).\n    OrderByNulls(instance.F(\"salary\"), astql.DESC, astql.NullsLast).\n    Rows(astql.FrameUnboundedPreceding, astql.FrameCurrentRow).\n    Build()",{"id":657,"title":658,"titles":659,"content":660,"level":40},"/v1.0.10/guides/aggregates#frame-bounds","Frame Bounds",[575,633],"ConstantSQLFrameUnboundedPrecedingUNBOUNDED PRECEDINGFrameCurrentRowCURRENT ROWFrameUnboundedFollowingUNBOUNDED FOLLOWING",{"id":662,"title":663,"titles":664,"content":34,"level":19},"/v1.0.10/guides/aggregates#math-functions","Math Functions",[575],{"id":666,"title":667,"titles":668,"content":669,"level":40},"/v1.0.10/guides/aggregates#available-functions","Available Functions",[575,663],"FunctionSQLRound(field) or Round(field, precision)ROUND(\"field\") or ROUND(\"field\", :precision)Floor(field)FLOOR(\"field\")Ceil(field)CEIL(\"field\")Abs(field)ABS(\"field\")Power(field, exponent)POWER(\"field\", :exponent)Sqrt(field)SQRT(\"field\")",{"id":671,"title":672,"titles":673,"content":674,"level":40},"/v1.0.10/guides/aggregates#example","Example",[575,663],"result, _ := astql.Select(instance.T(\"products\")).\n    SelectExpr(astql.As(astql.Round(instance.F(\"price\"), instance.P(\"decimals\")), \"rounded\")).\n    SelectExpr(astql.As(astql.Floor(instance.F(\"rating\")), \"floor_rating\")).\n    Render()\n\n// SELECT ROUND(\"price\", :decimals) AS \"rounded\",\n//        FLOOR(\"rating\") AS \"floor_rating\"\n// FROM \"products\"",{"id":676,"title":677,"titles":678,"content":679,"level":19},"/v1.0.10/guides/aggregates#case-expressions","CASE Expressions",[575],"Conditional logic in SELECT: caseExpr := astql.Case().\n    When(\n        instance.C(instance.F(\"score\"), astql.GE, instance.P(\"high\")),\n        instance.P(\"grade_a\"),\n    ).\n    When(\n        instance.C(instance.F(\"score\"), astql.GE, instance.P(\"mid\")),\n        instance.P(\"grade_b\"),\n    ).\n    Else(instance.P(\"grade_c\")).\n    As(\"grade\").\n    Build()\n\nresult, _ := astql.Select(instance.T(\"students\")).\n    Fields(instance.F(\"name\")).\n    SelectExpr(caseExpr).\n    Render()\n\n// SELECT \"name\",\n//        CASE WHEN \"score\" >= :high THEN :grade_a\n//             WHEN \"score\" >= :mid THEN :grade_b\n//             ELSE :grade_c END AS \"grade\"\n// FROM \"students\"",{"id":681,"title":682,"titles":683,"content":684,"level":19},"/v1.0.10/guides/aggregates#type-casting","Type Casting",[575],"Cast fields to different types: astql.Cast(instance.F(\"created_at\"), astql.CastDate)\n// CAST(\"created_at\" AS DATE) Available cast types: CastText, CastInteger, CastBigint, CastNumeric, CastBoolean, CastDate, CastTimestamp, CastTimestampTZ, CastUUID, CastJSON, CastJSONB, and more.",{"id":686,"title":687,"titles":688,"content":689,"level":19},"/v1.0.10/guides/aggregates#coalesce-and-nullif","COALESCE and NULLIF",[575],"// COALESCE - first non-null value\nastql.Coalesce(instance.P(\"preferred\"), instance.P(\"default\"))\n// COALESCE(:preferred, :default)\n\n// NULLIF - returns NULL if values are equal\nastql.NullIf(instance.P(\"value\"), instance.P(\"sentinel\"))\n// NULLIF(:value, :sentinel) 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 .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);}",{"id":691,"title":692,"titles":693,"content":694,"level":9},"/v1.0.10/guides/testing","Testing",[],"Testing patterns for query builders",{"id":696,"title":692,"titles":697,"content":698,"level":9},"/v1.0.10/guides/testing#testing",[],"ASTQL queries are testable without a database connection. This guide covers testing patterns and best practices.",{"id":700,"title":701,"titles":702,"content":34,"level":19},"/v1.0.10/guides/testing#testing-query-output","Testing Query Output",[692],{"id":704,"title":705,"titles":706,"content":707,"level":40},"/v1.0.10/guides/testing#basic-output-testing","Basic Output Testing",[692,701],"func TestUserQuery(t *testing.T) {\n    instance := setupTestInstance()\n\n    result, err := astql.Select(instance.T(\"users\")).\n        Fields(instance.F(\"username\"), instance.F(\"email\")).\n        Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\"))).\n        Render(postgres.New())\n\n    if err != nil {\n        t.Fatalf(\"unexpected error: %v\", err)\n    }\n\n    expected := `SELECT \"username\", \"email\" FROM \"users\" WHERE \"active\" = :is_active`\n    if result.SQL != expected {\n        t.Errorf(\"SQL mismatch\\ngot:  %s\\nwant: %s\", result.SQL, expected)\n    }\n\n    if len(result.RequiredParams) != 1 || result.RequiredParams[0] != \"is_active\" {\n        t.Errorf(\"params mismatch: got %v\", result.RequiredParams)\n    }\n}",{"id":709,"title":710,"titles":711,"content":712,"level":40},"/v1.0.10/guides/testing#test-instance-setup","Test Instance Setup",[692,701],"Create a dedicated schema for tests: func setupTestInstance() *astql.ASTQL {\n    project := dbml.NewProject(\"test\")\n\n    users := dbml.NewTable(\"users\")\n    users.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\n    users.AddColumn(dbml.NewColumn(\"username\", \"varchar\"))\n    users.AddColumn(dbml.NewColumn(\"email\", \"varchar\"))\n    users.AddColumn(dbml.NewColumn(\"active\", \"boolean\"))\n    project.AddTable(users)\n\n    posts := dbml.NewTable(\"posts\")\n    posts.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\n    posts.AddColumn(dbml.NewColumn(\"user_id\", \"bigint\"))\n    posts.AddColumn(dbml.NewColumn(\"title\", \"varchar\"))\n    posts.AddColumn(dbml.NewColumn(\"published\", \"boolean\"))\n    project.AddTable(posts)\n\n    instance, err := astql.NewFromDBML(project)\n    if err != nil {\n        panic(err)\n    }\n    return instance\n}",{"id":714,"title":715,"titles":716,"content":34,"level":19},"/v1.0.10/guides/testing#table-driven-tests","Table-Driven Tests",[692],{"id":718,"title":719,"titles":720,"content":721,"level":40},"/v1.0.10/guides/testing#testing-multiple-queries","Testing Multiple Queries",[692,715],"func TestQueries(t *testing.T) {\n    instance := setupTestInstance()\n\n    tests := []struct {\n        name     string\n        query    func() (*astql.QueryResult, error)\n        wantSQL  string\n        wantParams []string\n    }{\n        {\n            name: \"simple select\",\n            query: func() (*astql.QueryResult, error) {\n                return astql.Select(instance.T(\"users\")).\n                    Fields(instance.F(\"username\")).\n                    Render(postgres.New())\n            },\n            wantSQL:    `SELECT \"username\" FROM \"users\"`,\n            wantParams: nil,\n        },\n        {\n            name: \"select with where\",\n            query: func() (*astql.QueryResult, error) {\n                return astql.Select(instance.T(\"users\")).\n                    Where(instance.C(instance.F(\"id\"), astql.EQ, instance.P(\"id\"))).\n                    Render(postgres.New())\n            },\n            wantSQL:    `SELECT * FROM \"users\" WHERE \"id\" = :id`,\n            wantParams: []string{\"id\"},\n        },\n        {\n            name: \"select with order and limit\",\n            query: func() (*astql.QueryResult, error) {\n                return astql.Select(instance.T(\"users\")).\n                    OrderBy(instance.F(\"username\"), astql.ASC).\n                    Limit(10).\n                    Render(postgres.New())\n            },\n            wantSQL:    `SELECT * FROM \"users\" ORDER BY \"username\" ASC LIMIT 10`,\n            wantParams: nil,\n        },\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            result, err := tt.query()\n            if err != nil {\n                t.Fatalf(\"unexpected error: %v\", err)\n            }\n            if result.SQL != tt.wantSQL {\n                t.Errorf(\"SQL mismatch\\ngot:  %s\\nwant: %s\", result.SQL, tt.wantSQL)\n            }\n            if !slicesEqual(result.RequiredParams, tt.wantParams) {\n                t.Errorf(\"params mismatch\\ngot:  %v\\nwant: %v\", result.RequiredParams, tt.wantParams)\n            }\n        })\n    }\n}\n\nfunc slicesEqual(a, b []string) bool {\n    if len(a) != len(b) {\n        return false\n    }\n    for i := range a {\n        if a[i] != b[i] {\n            return false\n        }\n    }\n    return true\n}",{"id":723,"title":724,"titles":725,"content":34,"level":19},"/v1.0.10/guides/testing#testing-validation","Testing Validation",[692],{"id":727,"title":728,"titles":729,"content":730,"level":40},"/v1.0.10/guides/testing#testing-invalid-input","Testing Invalid Input",[692,724],"func TestInvalidTable(t *testing.T) {\n    instance := setupTestInstance()\n\n    defer func() {\n        if r := recover(); r == nil {\n            t.Error(\"expected panic for invalid table\")\n        }\n    }()\n\n    instance.T(\"nonexistent\")\n}\n\nfunc TestInvalidField(t *testing.T) {\n    instance := setupTestInstance()\n\n    defer func() {\n        if r := recover(); r == nil {\n            t.Error(\"expected panic for invalid field\")\n        }\n    }()\n\n    instance.F(\"nonexistent\")\n}",{"id":732,"title":733,"titles":734,"content":735,"level":40},"/v1.0.10/guides/testing#testing-try-variants","Testing Try Variants",[692,724],"func TestTryT(t *testing.T) {\n    instance := setupTestInstance()\n\n    // Valid table\n    table, err := instance.TryT(\"users\")\n    if err != nil {\n        t.Errorf(\"unexpected error for valid table: %v\", err)\n    }\n    if table.Name != \"users\" {\n        t.Errorf(\"wrong table name: %s\", table.Name)\n    }\n\n    // Invalid table\n    _, err = instance.TryT(\"nonexistent\")\n    if err == nil {\n        t.Error(\"expected error for invalid table\")\n    }\n}",{"id":737,"title":738,"titles":739,"content":34,"level":19},"/v1.0.10/guides/testing#testing-error-cases","Testing Error Cases",[692],{"id":741,"title":742,"titles":743,"content":744,"level":40},"/v1.0.10/guides/testing#builder-errors","Builder Errors",[692,738],"func TestBuilderErrors(t *testing.T) {\n    instance := setupTestInstance()\n\n    // Fields on non-SELECT query\n    _, err := astql.Insert(instance.T(\"users\")).\n        Fields(instance.F(\"username\")).\n        Render(postgres.New())\n\n    if err == nil {\n        t.Error(\"expected error for Fields on INSERT\")\n    }\n\n    // SET on non-UPDATE query\n    _, err = astql.Select(instance.T(\"users\")).\n        Set(instance.F(\"username\"), instance.P(\"value\")).\n        Render(postgres.New())\n\n    if err == nil {\n        t.Error(\"expected error for Set on SELECT\")\n    }\n}",{"id":746,"title":747,"titles":748,"content":34,"level":19},"/v1.0.10/guides/testing#testing-complex-queries","Testing Complex Queries",[692],{"id":750,"title":751,"titles":752,"content":753,"level":40},"/v1.0.10/guides/testing#join-tests","Join Tests",[692,747],"func TestJoinQuery(t *testing.T) {\n    instance := setupTestInstance()\n\n    result, err := astql.Select(instance.T(\"users\", \"u\")).\n        Fields(\n            instance.WithTable(instance.F(\"username\"), \"u\"),\n            instance.WithTable(instance.F(\"title\"), \"p\"),\n        ).\n        LeftJoin(instance.T(\"posts\", \"p\"), astql.CF(\n            instance.WithTable(instance.F(\"id\"), \"u\"),\n            astql.EQ,\n            instance.WithTable(instance.F(\"user_id\"), \"p\"),\n        )).\n        Render(postgres.New())\n\n    if err != nil {\n        t.Fatalf(\"unexpected error: %v\", err)\n    }\n\n    // Check key parts of the query\n    if !strings.Contains(result.SQL, \"LEFT JOIN\") {\n        t.Error(\"missing LEFT JOIN\")\n    }\n    if !strings.Contains(result.SQL, `u.\"id\" = p.\"user_id\"`) {\n        t.Error(\"missing join condition\")\n    }\n}",{"id":755,"title":756,"titles":757,"content":758,"level":40},"/v1.0.10/guides/testing#subquery-tests","Subquery Tests",[692,747],"func TestSubquery(t *testing.T) {\n    instance := setupTestInstance()\n\n    subquery := astql.Sub(\n        astql.Select(instance.T(\"posts\")).\n            Fields(instance.F(\"user_id\")).\n            Where(instance.C(instance.F(\"published\"), astql.EQ, instance.P(\"is_pub\"))),\n    )\n\n    result, err := astql.Select(instance.T(\"users\")).\n        Where(astql.CSub(instance.F(\"id\"), astql.IN, subquery)).\n        Render(postgres.New())\n\n    if err != nil {\n        t.Fatalf(\"unexpected error: %v\", err)\n    }\n\n    // Check parameter namespacing\n    if !strings.Contains(result.SQL, \":sq1_is_pub\") {\n        t.Error(\"subquery parameter not namespaced\")\n    }\n}",{"id":760,"title":761,"titles":762,"content":763,"level":19},"/v1.0.10/guides/testing#snapshot-testing","Snapshot Testing",[692],"For complex queries, consider snapshot testing: func TestComplexQuery_Snapshot(t *testing.T) {\n    instance := setupTestInstance()\n\n    result, err := buildComplexQuery(instance)\n    if err != nil {\n        t.Fatalf(\"unexpected error: %v\", err)\n    }\n\n    golden := filepath.Join(\"testdata\", t.Name()+\".golden.sql\")\n\n    if *update {\n        os.WriteFile(golden, []byte(result.SQL), 0644)\n        return\n    }\n\n    expected, err := os.ReadFile(golden)\n    if err != nil {\n        t.Fatalf(\"failed to read golden file: %v\", err)\n    }\n\n    if result.SQL != string(expected) {\n        t.Errorf(\"SQL mismatch with golden file\\ngot:\\n%s\", result.SQL)\n    }\n}",{"id":765,"title":766,"titles":767,"content":34,"level":19},"/v1.0.10/guides/testing#best-practices","Best Practices",[692],{"id":769,"title":770,"titles":771,"content":772,"level":40},"/v1.0.10/guides/testing#_1-test-query-structure-not-exact-strings","1. Test Query Structure, Not Exact Strings",[692,766],"For complex queries, test for required components: func TestQueryContains(t *testing.T) {\n    result, _ := buildQuery()\n\n    checks := []string{\n        `FROM \"users\"`,\n        `WHERE \"active\"`,\n        `ORDER BY`,\n        `LIMIT`,\n    }\n\n    for _, check := range checks {\n        if !strings.Contains(result.SQL, check) {\n            t.Errorf(\"missing: %s\", check)\n        }\n    }\n}",{"id":774,"title":775,"titles":776,"content":777,"level":40},"/v1.0.10/guides/testing#_2-test-parameter-lists","2. Test Parameter Lists",[692,766],"Always verify required parameters: func TestParams(t *testing.T) {\n    result, _ := buildQuery()\n\n    expected := []string{\"user_id\", \"status\", \"limit\"}\n    for _, param := range expected {\n        found := false\n        for _, p := range result.RequiredParams {\n            if p == param {\n                found = true\n                break\n            }\n        }\n        if !found {\n            t.Errorf(\"missing param: %s\", param)\n        }\n    }\n}",{"id":779,"title":780,"titles":781,"content":782,"level":40},"/v1.0.10/guides/testing#_3-isolate-test-schemas","3. Isolate Test Schemas",[692,766],"Each test file or package should have its own schema setup: // users_test.go\nfunc setupUsersTestInstance() *astql.ASTQL { ... }\n\n// orders_test.go\nfunc setupOrdersTestInstance() *astql.ASTQL { ... }",{"id":784,"title":785,"titles":786,"content":787,"level":40},"/v1.0.10/guides/testing#_4-test-edge-cases","4. Test Edge Cases",[692,766],"func TestEdgeCases(t *testing.T) {\n    instance := setupTestInstance()\n\n    // Empty fields (SELECT *)\n    result, _ := astql.Select(instance.T(\"users\")).Render(postgres.New())\n    if !strings.Contains(result.SQL, \"SELECT *\") {\n        t.Error(\"empty fields should produce SELECT *\")\n    }\n\n    // Multiple WHERE calls\n    result, _ = astql.Select(instance.T(\"users\")).\n        Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"a\"))).\n        Where(instance.C(instance.F(\"id\"), astql.EQ, instance.P(\"b\"))).\n        Render(postgres.New())\n    if !strings.Contains(result.SQL, \"AND\") {\n        t.Error(\"multiple WHERE should combine with AND\")\n    }\n} 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 .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 .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}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)}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 .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}",{"id":789,"title":790,"titles":791,"content":792,"level":9},"/v1.0.10/cookbook/pagination","Pagination",[],"LIMIT/OFFSET and cursor-based pagination patterns",{"id":794,"title":790,"titles":795,"content":796,"level":9},"/v1.0.10/cookbook/pagination#pagination",[],"Recipe: Implement efficient pagination with ASTQL. Pagination is essential for handling large result sets. ASTQL supports both offset-based and cursor-based pagination.",{"id":798,"title":799,"titles":800,"content":801,"level":19},"/v1.0.10/cookbook/pagination#offset-pagination","Offset Pagination",[790],"The simplest approach using LIMIT and OFFSET: func GetUsersPage(instance *astql.ASTQL, page, pageSize int) (*astql.QueryResult, error) {\n    offset := (page - 1) * pageSize\n\n    return astql.Select(instance.T(\"users\")).\n        Fields(instance.F(\"id\"), instance.F(\"username\"), instance.F(\"email\")).\n        OrderBy(instance.F(\"id\"), astql.ASC).\n        Limit(pageSize).\n        Offset(offset).\n        Render(postgres.New())\n}",{"id":803,"title":257,"titles":804,"content":805,"level":40},"/v1.0.10/cookbook/pagination#usage",[790,799],"// Page 1: LIMIT 20 OFFSET 0\nresult, _ := GetUsersPage(instance, 1, 20)\n\n// Page 2: LIMIT 20 OFFSET 20\nresult, _ := GetUsersPage(instance, 2, 20)\n\n// Page 3: LIMIT 20 OFFSET 40\nresult, _ := GetUsersPage(instance, 3, 20)",{"id":807,"title":808,"titles":809,"content":810,"level":40},"/v1.0.10/cookbook/pagination#with-total-count","With Total Count",[790,799],"Get total count alongside paginated results: type PagedResult struct {\n    DataSQL    string\n    CountSQL   string\n    Params     []string\n}\n\nfunc GetPagedUsers(instance *astql.ASTQL, page, pageSize int) (*PagedResult, error) {\n    offset := (page - 1) * pageSize\n\n    // Data query\n    dataResult, err := astql.Select(instance.T(\"users\")).\n        Fields(instance.F(\"id\"), instance.F(\"username\")).\n        Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\"))).\n        OrderBy(instance.F(\"id\"), astql.ASC).\n        Limit(pageSize).\n        Offset(offset).\n        Render(postgres.New())\n    if err != nil {\n        return nil, err\n    }\n\n    // Count query (same WHERE, no LIMIT/OFFSET)\n    countResult, err := astql.Count(instance.T(\"users\")).\n        Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\"))).\n        Render(postgres.New())\n    if err != nil {\n        return nil, err\n    }\n\n    return &PagedResult{\n        DataSQL:  dataResult.SQL,\n        CountSQL: countResult.SQL,\n        Params:   dataResult.RequiredParams,\n    }, nil\n}",{"id":812,"title":813,"titles":814,"content":815,"level":40},"/v1.0.10/cookbook/pagination#limitations-of-offset-pagination","Limitations of Offset Pagination",[790,799],"Performance degrades with large offsets (database must skip rows)Inconsistent results if data changes between pagesNot suitable for infinite scroll or real-time feeds",{"id":817,"title":818,"titles":819,"content":820,"level":19},"/v1.0.10/cookbook/pagination#cursor-pagination","Cursor Pagination",[790],"For large datasets, use cursor-based pagination (keyset pagination): func GetUsersAfter(instance *astql.ASTQL, cursor int64, limit int) (*astql.QueryResult, error) {\n    query := astql.Select(instance.T(\"users\")).\n        Fields(instance.F(\"id\"), instance.F(\"username\"), instance.F(\"email\")).\n        OrderBy(instance.F(\"id\"), astql.ASC).\n        Limit(limit)\n\n    if cursor > 0 {\n        query = query.Where(instance.C(instance.F(\"id\"), astql.GT, instance.P(\"cursor\")))\n    }\n\n    return query.Render(postgres.New())\n}",{"id":822,"title":257,"titles":823,"content":824,"level":40},"/v1.0.10/cookbook/pagination#usage-1",[790,818],"// First page: no cursor\nresult, _ := GetUsersAfter(instance, 0, 20)\n// SELECT \"id\", \"username\", \"email\" FROM \"users\" ORDER BY \"id\" ASC LIMIT 20\n\n// Next page: use last ID as cursor\nresult, _ := GetUsersAfter(instance, lastID, 20)\n// SELECT ... WHERE \"id\" > :cursor ORDER BY \"id\" ASC LIMIT 20",{"id":826,"title":827,"titles":828,"content":829,"level":40},"/v1.0.10/cookbook/pagination#bidirectional-cursor","Bidirectional Cursor",[790,818],"Support both forward and backward navigation: type Direction string\n\nconst (\n    Forward  Direction = \"forward\"\n    Backward Direction = \"backward\"\n)\n\nfunc GetUsersWithCursor(\n    instance *astql.ASTQL,\n    cursor int64,\n    direction Direction,\n    limit int,\n) (*astql.QueryResult, error) {\n    query := astql.Select(instance.T(\"users\")).\n        Fields(instance.F(\"id\"), instance.F(\"username\"))\n\n    if cursor > 0 {\n        if direction == Forward {\n            query = query.\n                Where(instance.C(instance.F(\"id\"), astql.GT, instance.P(\"cursor\"))).\n                OrderBy(instance.F(\"id\"), astql.ASC)\n        } else {\n            query = query.\n                Where(instance.C(instance.F(\"id\"), astql.LT, instance.P(\"cursor\"))).\n                OrderBy(instance.F(\"id\"), astql.DESC)\n        }\n    } else {\n        query = query.OrderBy(instance.F(\"id\"), astql.ASC)\n    }\n\n    return query.Limit(limit).Render(postgres.New())\n}",{"id":831,"title":832,"titles":833,"content":834,"level":40},"/v1.0.10/cookbook/pagination#multi-column-cursor","Multi-Column Cursor",[790,818],"For sorting by non-unique columns, use composite cursors: func GetPostsByDate(\n    instance *astql.ASTQL,\n    cursorDate string,\n    cursorID int64,\n    limit int,\n) (*astql.QueryResult, error) {\n    query := astql.Select(instance.T(\"posts\")).\n        Fields(instance.F(\"id\"), instance.F(\"title\"), instance.F(\"created_at\")).\n        Limit(limit)\n\n    if cursorDate != \"\" {\n        // Composite cursor: (created_at, id)\n        query = query.Where(instance.Or(\n            // Same date, higher ID\n            instance.And(\n                instance.C(instance.F(\"created_at\"), astql.EQ, instance.P(\"cursor_date\")),\n                instance.C(instance.F(\"id\"), astql.GT, instance.P(\"cursor_id\")),\n            ),\n            // Later date\n            instance.C(instance.F(\"created_at\"), astql.GT, instance.P(\"cursor_date\")),\n        ))\n    }\n\n    return query.\n        OrderBy(instance.F(\"created_at\"), astql.ASC).\n        OrderBy(instance.F(\"id\"), astql.ASC).\n        Render(postgres.New())\n}",{"id":836,"title":837,"titles":838,"content":839,"level":19},"/v1.0.10/cookbook/pagination#filtering-with-pagination","Filtering with Pagination",[790],"Combine filters with pagination: func SearchUsers(\n    instance *astql.ASTQL,\n    filters UserFilters,\n    cursor int64,\n    limit int,\n) (*astql.QueryResult, error) {\n    query := astql.Select(instance.T(\"users\")).\n        Fields(instance.F(\"id\"), instance.F(\"username\"), instance.F(\"email\"))\n\n    // Build filter conditions using instance.ConditionItems()\n    conditions := instance.ConditionItems()\n\n    if filters.Active != nil {\n        conditions = append(conditions,\n            instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\")))\n    }\n\n    if filters.Role != \"\" {\n        conditions = append(conditions,\n            instance.C(instance.F(\"role\"), astql.EQ, instance.P(\"role\")))\n    }\n\n    // Add cursor condition\n    if cursor > 0 {\n        conditions = append(conditions,\n            instance.C(instance.F(\"id\"), astql.GT, instance.P(\"cursor\")))\n    }\n\n    // Apply conditions\n    if len(conditions) > 0 {\n        query = query.Where(instance.And(conditions...))\n    }\n\n    return query.\n        OrderBy(instance.F(\"id\"), astql.ASC).\n        Limit(limit).\n        Render(postgres.New())\n}",{"id":841,"title":842,"titles":843,"content":844,"level":19},"/v1.0.10/cookbook/pagination#pagination-with-joins","Pagination with JOINs",[790],"Paginate joined results: func GetUserPosts(\n    instance *astql.ASTQL,\n    userID int64,\n    cursor int64,\n    limit int,\n) (*astql.QueryResult, error) {\n    query := astql.Select(instance.T(\"posts\", \"p\")).\n        Fields(\n            instance.WithTable(instance.F(\"id\"), \"p\"),\n            instance.WithTable(instance.F(\"title\"), \"p\"),\n            instance.WithTable(instance.F(\"username\"), \"u\"),\n        ).\n        Join(instance.T(\"users\", \"u\"), astql.CF(\n            instance.WithTable(instance.F(\"user_id\"), \"p\"),\n            astql.EQ,\n            instance.WithTable(instance.F(\"id\"), \"u\"),\n        )).\n        Where(instance.C(\n            instance.WithTable(instance.F(\"user_id\"), \"p\"),\n            astql.EQ,\n            instance.P(\"user_id\"),\n        ))\n\n    if cursor > 0 {\n        query = query.Where(instance.C(\n            instance.WithTable(instance.F(\"id\"), \"p\"),\n            astql.GT,\n            instance.P(\"cursor\"),\n        ))\n    }\n\n    return query.\n        OrderBy(instance.WithTable(instance.F(\"id\"), \"p\"), astql.ASC).\n        Limit(limit).\n        Render(postgres.New())\n}",{"id":846,"title":766,"titles":847,"content":34,"level":19},"/v1.0.10/cookbook/pagination#best-practices",[790],{"id":849,"title":850,"titles":851,"content":852,"level":40},"/v1.0.10/cookbook/pagination#_1-always-include-order-by","1. Always Include ORDER BY",[790,766],"Pagination without ORDER BY produces unpredictable results: // Bad: no ordering\nastql.Select(table).Limit(10).Render(postgres.New())\n\n// Good: explicit ordering\nastql.Select(table).OrderBy(field, astql.ASC).Limit(10).Render(postgres.New())",{"id":854,"title":855,"titles":856,"content":857,"level":40},"/v1.0.10/cookbook/pagination#_2-use-indexed-columns-for-cursors","2. Use Indexed Columns for Cursors",[790,766],"Ensure cursor columns are indexed for performance: CREATE INDEX idx_users_id ON users(id);\nCREATE INDEX idx_posts_created_at_id ON posts(created_at, id);",{"id":859,"title":860,"titles":861,"content":862,"level":40},"/v1.0.10/cookbook/pagination#_3-fetch-n1-for-has-more","3. Fetch N+1 for \"Has More\"",[790,766],"Fetch one extra row to determine if more pages exist: func GetUsersWithHasMore(instance *astql.ASTQL, cursor int64, limit int) (sql string, hasMore bool) {\n    // Fetch limit + 1\n    result, _ := GetUsersAfter(instance, cursor, limit+1)\n\n    // If we got limit+1 rows, there are more\n    // (Actual row count determined at execution time)\n    return result.SQL, true // hasMore determined after execution\n}",{"id":864,"title":865,"titles":866,"content":867,"level":40},"/v1.0.10/cookbook/pagination#_4-consider-distinct-on-for-deduplication","4. Consider DISTINCT ON for Deduplication",[790,766],"When joining might produce duplicates: astql.Select(instance.T(\"users\")).\n    DistinctOn(instance.F(\"id\")).\n    OrderBy(instance.F(\"id\"), astql.ASC).\n    Limit(20) 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 .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 .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}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 .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 .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":869,"title":870,"titles":871,"content":872,"level":9},"/v1.0.10/cookbook/vector-search","Vector Search",[],"pgvector similarity queries with ASTQL",{"id":874,"title":870,"titles":875,"content":876,"level":9},"/v1.0.10/cookbook/vector-search#vector-search",[],"Recipe: Build semantic search with pgvector and ASTQL. ASTQL supports pgvector operators for similarity search on embedding vectors.",{"id":878,"title":879,"titles":880,"content":881,"level":19},"/v1.0.10/cookbook/vector-search#schema-setup","Schema Setup",[870],"Define a table with a vector column: project := dbml.NewProject(\"myapp\")\n\ndocuments := dbml.NewTable(\"documents\")\ndocuments.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\ndocuments.AddColumn(dbml.NewColumn(\"title\", \"varchar\"))\ndocuments.AddColumn(dbml.NewColumn(\"content\", \"text\"))\ndocuments.AddColumn(dbml.NewColumn(\"embedding\", \"vector(1536)\"))  // OpenAI dimension\ndocuments.AddColumn(dbml.NewColumn(\"created_at\", \"timestamp\"))\nproject.AddTable(documents)\n\ninstance, _ := astql.NewFromDBML(project)",{"id":883,"title":884,"titles":885,"content":886,"level":19},"/v1.0.10/cookbook/vector-search#distance-operators","Distance Operators",[870],"OperatorConstantDistance Type\u003C->VectorL2DistanceEuclidean (L2)\u003C=>VectorCosineDistanceCosine\u003C#>VectorInnerProductNegative inner product\u003C+>VectorL1DistanceManhattan (L1)",{"id":888,"title":889,"titles":890,"content":891,"level":19},"/v1.0.10/cookbook/vector-search#basic-similarity-search","Basic Similarity Search",[870],"Find documents similar to a query vector: func SearchSimilar(instance *astql.ASTQL, limit int) (*astql.QueryResult, error) {\n    return astql.Select(instance.T(\"documents\")).\n        Fields(\n            instance.F(\"id\"),\n            instance.F(\"title\"),\n            instance.F(\"content\"),\n        ).\n        OrderByExpr(\n            instance.F(\"embedding\"),\n            astql.VectorL2Distance,\n            instance.P(\"query_embedding\"),\n            astql.ASC,\n        ).\n        Limit(limit).\n        Render()\n}\n\n// SELECT \"id\", \"title\", \"content\"\n// FROM \"documents\"\n// ORDER BY \"embedding\" \u003C-> :query_embedding ASC\n// LIMIT 10",{"id":893,"title":894,"titles":895,"content":896,"level":40},"/v1.0.10/cookbook/vector-search#execution-with-sqlx","Execution with sqlx",[870,889],"result, _ := SearchSimilar(instance, 10)\n\n// Convert embedding to pgvector format\nqueryEmbedding := pgvector.NewVector(embeddingFloats)\n\nparams := map[string]any{\n    \"query_embedding\": queryEmbedding,\n}\n\nvar docs []Document\nerr := db.Select(&docs, result.SQL, params)",{"id":898,"title":899,"titles":900,"content":901,"level":19},"/v1.0.10/cookbook/vector-search#selecting-distance-as-a-column","Selecting Distance as a Column",[870],"Use SelectBinaryExpr to include the computed distance in your results: func SearchWithDistance(instance *astql.ASTQL, limit int) (*astql.QueryResult, error) {\n    return astql.Select(instance.T(\"documents\")).\n        Fields(\n            instance.F(\"id\"),\n            instance.F(\"title\"),\n            instance.F(\"content\"),\n        ).\n        SelectBinaryExpr(\n            instance.F(\"embedding\"),\n            astql.VectorL2Distance,\n            instance.P(\"query_embedding\"),\n            \"distance\",\n        ).\n        OrderByExpr(\n            instance.F(\"embedding\"),\n            astql.VectorL2Distance,\n            instance.P(\"query_embedding\"),\n            astql.ASC,\n        ).\n        Limit(limit).\n        Render()\n}\n\n// SELECT \"id\", \"title\", \"content\", \"embedding\" \u003C-> :query_embedding AS \"distance\"\n// FROM \"documents\"\n// ORDER BY \"embedding\" \u003C-> :query_embedding ASC\n// LIMIT 10 This is useful when you need to display or filter by the actual distance value.",{"id":903,"title":904,"titles":905,"content":906,"level":19},"/v1.0.10/cookbook/vector-search#cosine-similarity","Cosine Similarity",[870],"For normalized embeddings, use cosine distance: func SearchByCosine(instance *astql.ASTQL, limit int) (*astql.QueryResult, error) {\n    return astql.Select(instance.T(\"documents\")).\n        Fields(instance.F(\"id\"), instance.F(\"title\")).\n        OrderByExpr(\n            instance.F(\"embedding\"),\n            astql.VectorCosineDistance,\n            instance.P(\"query_embedding\"),\n            astql.ASC,\n        ).\n        Limit(limit).\n        Render()\n}\n\n// ORDER BY \"embedding\" \u003C=> :query_embedding ASC",{"id":908,"title":909,"titles":910,"content":911,"level":19},"/v1.0.10/cookbook/vector-search#inner-product-for-maximum-similarity","Inner Product (for Maximum Similarity)",[870],"When embeddings are normalized, inner product gives similarity (not distance): func SearchByInnerProduct(instance *astql.ASTQL, limit int) (*astql.QueryResult, error) {\n    return astql.Select(instance.T(\"documents\")).\n        Fields(instance.F(\"id\"), instance.F(\"title\")).\n        OrderByExpr(\n            instance.F(\"embedding\"),\n            astql.VectorInnerProduct,\n            instance.P(\"query_embedding\"),\n            astql.ASC,  // Note: pgvector uses negative inner product\n        ).\n        Limit(limit).\n        Render()\n}\n\n// ORDER BY \"embedding\" \u003C#> :query_embedding ASC",{"id":913,"title":914,"titles":915,"content":916,"level":19},"/v1.0.10/cookbook/vector-search#filtered-vector-search","Filtered Vector Search",[870],"Combine vector search with traditional filters: func SearchWithFilters(\n    instance *astql.ASTQL,\n    category string,\n    minDate time.Time,\n    limit int,\n) (*astql.QueryResult, error) {\n    return astql.Select(instance.T(\"documents\")).\n        Fields(instance.F(\"id\"), instance.F(\"title\"), instance.F(\"content\")).\n        Where(instance.And(\n            instance.C(instance.F(\"category\"), astql.EQ, instance.P(\"category\")),\n            instance.C(instance.F(\"created_at\"), astql.GE, instance.P(\"min_date\")),\n        )).\n        OrderByExpr(\n            instance.F(\"embedding\"),\n            astql.VectorL2Distance,\n            instance.P(\"query_embedding\"),\n            astql.ASC,\n        ).\n        Limit(limit).\n        Render()\n}\n\n// SELECT \"id\", \"title\", \"content\"\n// FROM \"documents\"\n// WHERE (\"category\" = :category AND \"created_at\" >= :min_date)\n// ORDER BY \"embedding\" \u003C-> :query_embedding ASC\n// LIMIT 10",{"id":918,"title":919,"titles":920,"content":921,"level":19},"/v1.0.10/cookbook/vector-search#k-nearest-neighbors-with-distance-threshold","K-Nearest Neighbors with Distance Threshold",[870],"Filter results by maximum distance: func SearchWithThreshold(instance *astql.ASTQL, maxDistance float64, limit int) (*astql.QueryResult, error) {\n    return astql.Select(instance.T(\"documents\")).\n        Fields(instance.F(\"id\"), instance.F(\"title\")).\n        Where(instance.C(\n            instance.F(\"embedding\"),\n            astql.VectorL2Distance,\n            instance.P(\"query_embedding\"),\n        )).\n        // Note: distance threshold requires raw SQL or a computed column\n        OrderByExpr(\n            instance.F(\"embedding\"),\n            astql.VectorL2Distance,\n            instance.P(\"query_embedding\"),\n            astql.ASC,\n        ).\n        Limit(limit).\n        Render()\n}",{"id":923,"title":924,"titles":925,"content":926,"level":19},"/v1.0.10/cookbook/vector-search#hybrid-search","Hybrid Search",[870],"Combine vector similarity with full-text search: func HybridSearch(instance *astql.ASTQL, limit int) (*astql.QueryResult, error) {\n    // Vector search component\n    return astql.Select(instance.T(\"documents\")).\n        Fields(\n            instance.F(\"id\"),\n            instance.F(\"title\"),\n            instance.F(\"content\"),\n        ).\n        Where(instance.C(\n            instance.F(\"content\"),\n            astql.ILIKE,\n            instance.P(\"text_query\"),\n        )).\n        OrderByExpr(\n            instance.F(\"embedding\"),\n            astql.VectorL2Distance,\n            instance.P(\"query_embedding\"),\n            astql.ASC,\n        ).\n        Limit(limit).\n        Render()\n}\n\n// SELECT \"id\", \"title\", \"content\"\n// FROM \"documents\"\n// WHERE \"content\" ILIKE :text_query\n// ORDER BY \"embedding\" \u003C-> :query_embedding ASC\n// LIMIT 10",{"id":928,"title":929,"titles":930,"content":931,"level":19},"/v1.0.10/cookbook/vector-search#multi-vector-search","Multi-Vector Search",[870],"Search across multiple embedding columns: project := dbml.NewProject(\"myapp\")\n\nproducts := dbml.NewTable(\"products\")\nproducts.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\nproducts.AddColumn(dbml.NewColumn(\"name\", \"varchar\"))\nproducts.AddColumn(dbml.NewColumn(\"title_embedding\", \"vector(1536)\"))\nproducts.AddColumn(dbml.NewColumn(\"description_embedding\", \"vector(1536)\"))\nproducts.AddColumn(dbml.NewColumn(\"image_embedding\", \"vector(512)\"))\nproject.AddTable(products)\n\n// Search by title similarity\nfunc SearchByTitle(instance *astql.ASTQL, limit int) (*astql.QueryResult, error) {\n    return astql.Select(instance.T(\"products\")).\n        Fields(instance.F(\"id\"), instance.F(\"name\")).\n        OrderByExpr(\n            instance.F(\"title_embedding\"),\n            astql.VectorCosineDistance,\n            instance.P(\"query_embedding\"),\n            astql.ASC,\n        ).\n        Limit(limit).\n        Render()\n}\n\n// Search by image similarity\nfunc SearchByImage(instance *astql.ASTQL, limit int) (*astql.QueryResult, error) {\n    return astql.Select(instance.T(\"products\")).\n        Fields(instance.F(\"id\"), instance.F(\"name\")).\n        OrderByExpr(\n            instance.F(\"image_embedding\"),\n            astql.VectorL2Distance,\n            instance.P(\"image_query\"),\n            astql.ASC,\n        ).\n        Limit(limit).\n        Render()\n}",{"id":933,"title":934,"titles":935,"content":936,"level":19},"/v1.0.10/cookbook/vector-search#indexing-recommendations","Indexing Recommendations",[870],"For production vector search, create appropriate indexes: -- HNSW index (recommended for most cases)\nCREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);\n\n-- IVFFlat index (for very large datasets)\nCREATE INDEX ON documents USING ivfflat (embedding vector_l2_ops) WITH (lists = 100); Choose the operator class matching your distance function: vector_l2_ops for L2 distance (\u003C->)vector_cosine_ops for cosine distance (\u003C=>)vector_ip_ops for inner product (\u003C#>)",{"id":938,"title":766,"titles":939,"content":34,"level":19},"/v1.0.10/cookbook/vector-search#best-practices",[870],{"id":941,"title":942,"titles":943,"content":944,"level":40},"/v1.0.10/cookbook/vector-search#_1-normalize-embeddings-for-cosine","1. Normalize Embeddings for Cosine",[870,766],"Pre-normalize embeddings when using cosine distance: func normalize(v []float32) []float32 {\n    var sum float32\n    for _, x := range v {\n        sum += x * x\n    }\n    norm := float32(math.Sqrt(float64(sum)))\n    result := make([]float32, len(v))\n    for i, x := range v {\n        result[i] = x / norm\n    }\n    return result\n}",{"id":946,"title":947,"titles":948,"content":949,"level":40},"/v1.0.10/cookbook/vector-search#_2-use-appropriate-limits","2. Use Appropriate Limits",[870,766],"Vector search is expensive. Always use reasonable limits: // Good: bounded results\n.Limit(100)\n\n// Bad: unbounded\n// (no limit)",{"id":951,"title":952,"titles":953,"content":954,"level":40},"/v1.0.10/cookbook/vector-search#_3-pre-filter-when-possible","3. Pre-filter When Possible",[870,766],"Apply WHERE clauses before vector operations: // Good: filter first, then vector search\n.Where(instance.C(instance.F(\"category\"), astql.EQ, instance.P(\"cat\"))).\n.OrderByExpr(embedding, distance, query, astql.ASC)\n\n// Less efficient: vector search on all rows\n.OrderByExpr(embedding, distance, query, astql.ASC) 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 .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 .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}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 .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}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}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}",{"id":956,"title":957,"titles":958,"content":959,"level":9},"/v1.0.10/cookbook/upserts","Upserts",[],"ON CONFLICT patterns for insert-or-update operations",{"id":961,"title":957,"titles":962,"content":963,"level":9},"/v1.0.10/cookbook/upserts#upserts",[],"Recipe: Implement insert-or-update with PostgreSQL ON CONFLICT. ASTQL supports PostgreSQL's ON CONFLICT clause for upsert operations.",{"id":965,"title":966,"titles":967,"content":968,"level":19},"/v1.0.10/cookbook/upserts#basic-upsert","Basic Upsert",[957],"Insert a row or update if it already exists: func UpsertUser(instance *astql.ASTQL) (*astql.QueryResult, error) {\n    values := instance.ValueMap()\n    values[instance.F(\"email\")] = instance.P(\"email\")\n    values[instance.F(\"username\")] = instance.P(\"username\")\n    values[instance.F(\"updated_at\")] = instance.P(\"now\")\n\n    return astql.Insert(instance.T(\"users\")).\n        Values(values).\n        OnConflict(instance.F(\"email\")).\n        DoUpdate().\n        Set(instance.F(\"username\"), instance.P(\"username\")).\n        Set(instance.F(\"updated_at\"), instance.P(\"now\")).\n        Build().\n        Render(postgres.New())\n}\n\n// INSERT INTO \"users\" (\"email\", \"updated_at\", \"username\")\n// VALUES (:email, :now, :username)\n// ON CONFLICT (\"email\") DO UPDATE SET \"updated_at\" = :now, \"username\" = :username",{"id":970,"title":971,"titles":972,"content":973,"level":19},"/v1.0.10/cookbook/upserts#do-nothing","DO NOTHING",[957],"Skip conflicting rows without updating: func InsertIgnoreDuplicates(instance *astql.ASTQL) (*astql.QueryResult, error) {\n    values := instance.ValueMap()\n    values[instance.F(\"email\")] = instance.P(\"email\")\n    values[instance.F(\"username\")] = instance.P(\"username\")\n\n    return astql.Insert(instance.T(\"users\")).\n        Values(values).\n        OnConflict(instance.F(\"email\")).\n        DoNothing().\n        Render(postgres.New())\n}\n\n// INSERT INTO \"users\" (\"email\", \"username\")\n// VALUES (:email, :username)\n// ON CONFLICT (\"email\") DO NOTHING",{"id":975,"title":976,"titles":977,"content":978,"level":19},"/v1.0.10/cookbook/upserts#multi-column-conflict","Multi-Column Conflict",[957],"Handle conflicts on composite unique constraints: func UpsertUserRole(instance *astql.ASTQL) (*astql.QueryResult, error) {\n    values := instance.ValueMap()\n    values[instance.F(\"user_id\")] = instance.P(\"user_id\")\n    values[instance.F(\"role_id\")] = instance.P(\"role_id\")\n    values[instance.F(\"granted_at\")] = instance.P(\"now\")\n\n    return astql.Insert(instance.T(\"user_roles\")).\n        Values(values).\n        OnConflict(instance.F(\"user_id\"), instance.F(\"role_id\")).\n        DoUpdate().\n        Set(instance.F(\"granted_at\"), instance.P(\"now\")).\n        Build().\n        Render(postgres.New())\n}\n\n// INSERT INTO \"user_roles\" (\"granted_at\", \"role_id\", \"user_id\")\n// VALUES (:now, :role_id, :user_id)\n// ON CONFLICT (\"user_id\", \"role_id\") DO UPDATE SET \"granted_at\" = :now",{"id":980,"title":981,"titles":982,"content":983,"level":19},"/v1.0.10/cookbook/upserts#returning-with-upsert","RETURNING with Upsert",[957],"Get the inserted or updated row: func UpsertUserReturning(instance *astql.ASTQL) (*astql.QueryResult, error) {\n    values := instance.ValueMap()\n    values[instance.F(\"email\")] = instance.P(\"email\")\n    values[instance.F(\"username\")] = instance.P(\"username\")\n\n    return astql.Insert(instance.T(\"users\")).\n        Values(values).\n        OnConflict(instance.F(\"email\")).\n        DoUpdate().\n        Set(instance.F(\"username\"), instance.P(\"username\")).\n        Build().\n        Returning(instance.F(\"id\"), instance.F(\"created_at\"), instance.F(\"updated_at\")).\n        Render(postgres.New())\n}\n\n// INSERT INTO \"users\" (\"email\", \"username\")\n// VALUES (:email, :username)\n// ON CONFLICT (\"email\") DO UPDATE SET \"username\" = :username\n// RETURNING \"id\", \"created_at\", \"updated_at\"",{"id":985,"title":986,"titles":987,"content":988,"level":19},"/v1.0.10/cookbook/upserts#batch-upsert","Batch Upsert",[957],"Insert multiple rows with conflict handling: func BatchUpsertProducts(instance *astql.ASTQL, count int) (*astql.QueryResult, error) {\n    query := astql.Insert(instance.T(\"products\"))\n\n    // Add multiple value sets\n    for i := 0; i \u003C count; i++ {\n        values := instance.ValueMap()\n        values[instance.F(\"sku\")] = instance.P(fmt.Sprintf(\"sku_%d\", i))\n        values[instance.F(\"name\")] = instance.P(fmt.Sprintf(\"name_%d\", i))\n        values[instance.F(\"price\")] = instance.P(fmt.Sprintf(\"price_%d\", i))\n        query = query.Values(values)\n    }\n\n    return query.\n        OnConflict(instance.F(\"sku\")).\n        DoUpdate().\n        Set(instance.F(\"name\"), instance.P(\"name_0\")).  // Uses first row params\n        Set(instance.F(\"price\"), instance.P(\"price_0\")).\n        Build().\n        Render(postgres.New())\n}\n\n// INSERT INTO \"products\" (\"name\", \"price\", \"sku\")\n// VALUES (:name_0, :price_0, :sku_0), (:name_1, :price_1, :sku_1), ...\n// ON CONFLICT (\"sku\") DO UPDATE SET \"name\" = :name_0, \"price\" = :price_0",{"id":990,"title":991,"titles":992,"content":993,"level":19},"/v1.0.10/cookbook/upserts#conditional-upsert","Conditional Upsert",[957],"Update only certain fields based on values: func UpsertWithCondition(instance *astql.ASTQL) (*astql.QueryResult, error) {\n    values := instance.ValueMap()\n    values[instance.F(\"product_id\")] = instance.P(\"product_id\")\n    values[instance.F(\"quantity\")] = instance.P(\"quantity\")\n    values[instance.F(\"updated_at\")] = instance.P(\"now\")\n\n    // Only update if new quantity is greater\n    // Note: This requires raw SQL for the WHERE clause on conflict\n    return astql.Insert(instance.T(\"inventory\")).\n        Values(values).\n        OnConflict(instance.F(\"product_id\")).\n        DoUpdate().\n        Set(instance.F(\"quantity\"), instance.P(\"quantity\")).\n        Set(instance.F(\"updated_at\"), instance.P(\"now\")).\n        Build().\n        Render(postgres.New())\n}",{"id":995,"title":996,"titles":997,"content":998,"level":19},"/v1.0.10/cookbook/upserts#sync-pattern","Sync Pattern",[957],"Replace all rows for an entity (delete-then-insert pattern): func SyncUserPreferences(instance *astql.ASTQL, prefCount int) ([]astql.QueryResult, error) {\n    results := make([]astql.QueryResult, 0, 2)\n\n    // 1. Delete existing preferences\n    deleteResult, err := astql.Delete(instance.T(\"preferences\")).\n        Where(instance.C(instance.F(\"user_id\"), astql.EQ, instance.P(\"user_id\"))).\n        Render(postgres.New())\n    if err != nil {\n        return nil, err\n    }\n    results = append(results, *deleteResult)\n\n    // 2. Insert new preferences\n    query := astql.Insert(instance.T(\"preferences\"))\n    for i := 0; i \u003C prefCount; i++ {\n        values := instance.ValueMap()\n        values[instance.F(\"user_id\")] = instance.P(\"user_id\")\n        values[instance.F(\"key\")] = instance.P(fmt.Sprintf(\"key_%d\", i))\n        values[instance.F(\"value\")] = instance.P(fmt.Sprintf(\"value_%d\", i))\n        query = query.Values(values)\n    }\n\n    insertResult, err := query.Render(postgres.New())\n    if err != nil {\n        return nil, err\n    }\n    results = append(results, *insertResult)\n\n    return results, nil\n}",{"id":1000,"title":1001,"titles":1002,"content":1003,"level":19},"/v1.0.10/cookbook/upserts#upsert-with-timestamps","Upsert with Timestamps",[957],"Common pattern: set created_at on insert, updated_at always: func UpsertWithTimestamps(instance *astql.ASTQL) (*astql.QueryResult, error) {\n    values := instance.ValueMap()\n    values[instance.F(\"email\")] = instance.P(\"email\")\n    values[instance.F(\"username\")] = instance.P(\"username\")\n    values[instance.F(\"created_at\")] = instance.P(\"now\")  // Set on insert\n    values[instance.F(\"updated_at\")] = instance.P(\"now\")  // Set always\n\n    return astql.Insert(instance.T(\"users\")).\n        Values(values).\n        OnConflict(instance.F(\"email\")).\n        DoUpdate().\n        Set(instance.F(\"username\"), instance.P(\"username\")).\n        Set(instance.F(\"updated_at\"), instance.P(\"now\")).  // Only update updated_at\n        Build().\n        Render(postgres.New())\n}",{"id":1005,"title":766,"titles":1006,"content":34,"level":19},"/v1.0.10/cookbook/upserts#best-practices",[957],{"id":1008,"title":1009,"titles":1010,"content":1011,"level":40},"/v1.0.10/cookbook/upserts#_1-always-use-unique-constraints","1. Always Use Unique Constraints",[957,766],"ON CONFLICT requires a unique constraint or index: -- Unique constraint\nALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);\n\n-- Or unique index\nCREATE UNIQUE INDEX idx_users_email ON users(email);",{"id":1013,"title":1014,"titles":1015,"content":1016,"level":40},"/v1.0.10/cookbook/upserts#_2-be-explicit-about-updated-fields","2. Be Explicit About Updated Fields",[957,766],"Only update fields that should change on conflict: // Good: explicit updates\n.DoUpdate().\n    Set(instance.F(\"username\"), instance.P(\"username\")).\n    Set(instance.F(\"updated_at\"), instance.P(\"now\"))\n\n// Bad: might overwrite fields you want to preserve",{"id":1018,"title":1019,"titles":1020,"content":1021,"level":40},"/v1.0.10/cookbook/upserts#_3-use-returning-to-avoid-extra-queries","3. Use RETURNING to Avoid Extra Queries",[957,766],"// Good: single query\n.Returning(instance.F(\"id\"))\n\n// Less efficient: separate SELECT after INSERT",{"id":1023,"title":1024,"titles":1025,"content":1026,"level":40},"/v1.0.10/cookbook/upserts#_4-consider-do-nothing-for-idempotency","4. Consider DO NOTHING for Idempotency",[957,766],"When you only care about ensuring a row exists: // Idempotent: insert if missing, ignore if exists\n.OnConflict(instance.F(\"id\")).DoNothing() 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 .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 .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 .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 .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":1028,"title":1029,"titles":1030,"content":1031,"level":9},"/v1.0.10/cookbook/orm-foundation","ORM Foundation",[],"Building type-safe ORMs with ASTQL as the query layer",{"id":1033,"title":1029,"titles":1034,"content":1035,"level":9},"/v1.0.10/cookbook/orm-foundation#orm-foundation",[],"Recipe: Use ASTQL as the query building layer for type-safe ORMs. ASTQL provides the foundation for building ORMs that are structurally safe from SQL injection. This cookbook shows how cereal uses ASTQL to deliver a simple query API with full schema validation.",{"id":1037,"title":1038,"titles":1039,"content":1040,"level":19},"/v1.0.10/cookbook/orm-foundation#the-result","The Result",[1029],"Define your model: type User struct {\n    ID    int64  `db:\"id\" type:\"bigserial\" constraints:\"primarykey\"`\n    Email string `db:\"email\" type:\"text\" constraints:\"unique,notnull\"`\n    Name  string `db:\"name\" type:\"text\"`\n} Get a type-safe query API: users, _ := cereal.New[User](db, \"users\")\n\n// Simple queries hide ASTQL complexity\nuser, err := users.Select().\n    Where(\"email\", \"=\", \"user_email\").\n    Exec(ctx, map[string]any{\"user_email\": \"test@example.com\"})\n\n// Schema validation happens at initialization, not runtime\nusers.Select().Where(\"emai\", \"=\", \"x\")  // Error: field \"emai\" not in schema Three lines to set up. Zero reflection on the query path. Full SQL injection protection.",{"id":1042,"title":1043,"titles":1044,"content":1045,"level":19},"/v1.0.10/cookbook/orm-foundation#the-architecture","The Architecture",[1029],"Cereal wraps three libraries: +------------------+\n|     cereal       |  Simple query API (.Select(), .Insert(), etc.)\n+------------------+\n        |\n+------------------+\n|      astql       |  Schema validation + AST building\n+------------------+\n        |\n+------------------+\n|     sentinel     |  Struct metadata extraction\n+------------------+\n        |\n+------------------+\n|      sqlx        |  Database execution\n+------------------+ Each layer has a single responsibility: Sentinel extracts struct metadata (field names, tags, types)ASTQL validates queries against a DBML schemaCereal provides the user-facing API and execution",{"id":1047,"title":1048,"titles":1049,"content":1050,"level":19},"/v1.0.10/cookbook/orm-foundation#how-astql-enables-this","How ASTQL Enables This",[1029],"When you call cereal.New[T]():",{"id":1052,"title":1053,"titles":1054,"content":1055,"level":40},"/v1.0.10/cookbook/orm-foundation#_1-sentinel-extracts-metadata","1. Sentinel Extracts Metadata",[1029,1048],"metadata := sentinel.Inspect[User]()\n// metadata.Fields contains:\n// - {Name: \"ID\", Tags: {\"db\": \"id\", \"type\": \"bigserial\", \"constraints\": \"primarykey\"}}\n// - {Name: \"Email\", Tags: {\"db\": \"email\", \"type\": \"text\", \"constraints\": \"unique,notnull\"}}\n// - {Name: \"Name\", Tags: {\"db\": \"name\", \"type\": \"text\"}}",{"id":1057,"title":1058,"titles":1059,"content":1060,"level":40},"/v1.0.10/cookbook/orm-foundation#_2-cereal-builds-dbml-schema","2. Cereal Builds DBML Schema",[1029,1048],"project := dbml.NewProject(\"app\")\ntable := dbml.NewTable(\"users\")\ntable.AddColumn(dbml.NewColumn(\"id\", \"bigserial\"))\ntable.AddColumn(dbml.NewColumn(\"email\", \"text\"))\ntable.AddColumn(dbml.NewColumn(\"name\", \"text\"))\nproject.AddTable(table)",{"id":1062,"title":1063,"titles":1064,"content":1065,"level":40},"/v1.0.10/cookbook/orm-foundation#_3-astql-creates-validated-instance","3. ASTQL Creates Validated Instance",[1029,1048],"instance, err := astql.NewFromDBML(project)\n// instance now validates all table/field references against this schema",{"id":1067,"title":1068,"titles":1069,"content":1070,"level":40},"/v1.0.10/cookbook/orm-foundation#_4-queries-use-astql-builders","4. Queries Use ASTQL Builders",[1029,1048],"// cereal.Select() internally calls:\nbuilder := astql.Select(instance.T(\"users\"))\n\n// cereal.Where(\"email\", \"=\", \"user_email\") internally calls:\nfield, _ := instance.TryF(\"email\")      // Validates field exists\nparam, _ := instance.TryP(\"user_email\") // Validates param name\nbuilder = builder.Where(instance.C(field, astql.EQ, param))\n\n// cereal.Exec() internally calls:\nresult, _ := builder.Render(postgres.New())\n// result.SQL: SELECT ... FROM \"users\" WHERE \"email\" = :user_email",{"id":1072,"title":1073,"titles":1074,"content":1075,"level":19},"/v1.0.10/cookbook/orm-foundation#the-security-model","The Security Model",[1029],"User input can only provide values, never structure: // User provides: {\"user_email\": \"test@example.com\"}\n// This becomes a parameter value, not part of the SQL structure\n\nusers.Select().\n    Where(\"email\", \"=\", \"user_email\").  // \"email\" is validated against schema\n    Exec(ctx, map[string]any{\n        \"user_email\": userInput,  // User input is a value, safely parameterized\n    }) Even if userInput contains \"'; DROP TABLE users; --\", it's treated as a literal string value, not SQL. The field name \"email\" must exist in the struct. The table name \"users\" must match the schema. Injection vectors are eliminated by construction.",{"id":1077,"title":1078,"titles":1079,"content":1080,"level":19},"/v1.0.10/cookbook/orm-foundation#escape-hatch-to-astql","Escape Hatch to ASTQL",[1029],"For queries beyond cereal's simple API, access ASTQL directly: instance := users.Instance()\n\n// Build complex queries with full ASTQL power\nquery := astql.Select(instance.T(\"users\")).\n    Fields(\n        instance.F(\"id\"),\n        instance.F(\"email\"),\n        astql.Count(instance.F(\"id\")).As(\"order_count\"),\n    ).\n    Join(instance.T(\"orders\", \"o\"), astql.CF(\n        instance.WithTable(instance.F(\"id\"), \"users\"),\n        astql.EQ,\n        instance.WithTable(instance.F(\"user_id\"), \"o\"),\n    )).\n    GroupBy(instance.F(\"id\"), instance.F(\"email\")).\n    Having(instance.CAgg(astql.Count(instance.F(\"id\")), astql.GT, instance.P(\"min_orders\")))\n\nresult, _ := query.Render(postgres.New()) Same schema validation. Same injection protection. Full query expressiveness.",{"id":1082,"title":1083,"titles":1084,"content":1085,"level":19},"/v1.0.10/cookbook/orm-foundation#building-your-own-orm","Building Your Own ORM",[1029],"To build an ASTQL-backed ORM:",{"id":1087,"title":1088,"titles":1089,"content":1090,"level":40},"/v1.0.10/cookbook/orm-foundation#_1-extract-schema-from-your-source","1. Extract Schema from Your Source",[1029,1083],"// From structs (like cereal)\nmetadata := sentinel.Inspect[YourModel]()\nproject := buildDBMLFromMetadata(metadata)\n\n// Or from existing DBML files\nproject, _ := dbml.Parse(dbmlContent)\n\n// Or programmatically\nproject := dbml.NewProject(\"app\")\n// ... add tables and columns",{"id":1092,"title":1093,"titles":1094,"content":1095,"level":40},"/v1.0.10/cookbook/orm-foundation#_2-create-astql-instance","2. Create ASTQL Instance",[1029,1083],"instance, err := astql.NewFromDBML(project)\nif err != nil {\n    return err  // Schema validation failed\n}",{"id":1097,"title":1098,"titles":1099,"content":1100,"level":40},"/v1.0.10/cookbook/orm-foundation#_3-wrap-query-builders","3. Wrap Query Builders",[1029,1083],"type YourORM struct {\n    instance *astql.ASTQL\n    table    string\n}\n\nfunc (o *YourORM) Select(fields ...string) *YourSelectBuilder {\n    t := o.instance.T(o.table)\n    builder := astql.Select(t)\n\n    for _, name := range fields {\n        f := o.instance.F(name)  // Panics if invalid\n        builder = builder.Fields(f)\n    }\n\n    return &YourSelectBuilder{builder: builder, instance: o.instance}\n}",{"id":1102,"title":1103,"titles":1104,"content":1105,"level":40},"/v1.0.10/cookbook/orm-foundation#_4-render-with-provider","4. Render with Provider",[1029,1083],"func (b *YourSelectBuilder) SQL() (string, error) {\n    result, err := b.builder.Render(postgres.New())\n    if err != nil {\n        return \"\", err\n    }\n    return result.SQL, nil\n}",{"id":1107,"title":1108,"titles":1109,"content":1110,"level":19},"/v1.0.10/cookbook/orm-foundation#see-also","See Also",[1029],"cereal — the ORM built on ASTQLsentinel — struct metadata extractionSchema Validation guide — DBML integration detailsProviders — database dialect support 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 .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 .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 .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}",{"id":1112,"title":1113,"titles":1114,"content":1115,"level":9},"/v1.0.10/reference/api","API Reference",[],"Complete API reference for the astql package",{"id":1117,"title":1113,"titles":1118,"content":1119,"level":9},"/v1.0.10/reference/api#api-reference",[],"Complete API reference for the github.com/zoobzio/astql package.",{"id":1121,"title":1122,"titles":1123,"content":34,"level":19},"/v1.0.10/reference/api#instance-creation","Instance Creation",[1113],{"id":1125,"title":1126,"titles":1127,"content":1128,"level":40},"/v1.0.10/reference/api#newfromdbml","NewFromDBML",[1113,1122],"func NewFromDBML(project *dbml.Project) (*ASTQL, error) Creates a new ASTQL instance from a DBML project. Returns an error if the project is nil.",{"id":1130,"title":1131,"titles":1132,"content":34,"level":19},"/v1.0.10/reference/api#instance-methods","Instance Methods",[1113],{"id":1134,"title":1135,"titles":1136,"content":1137,"level":40},"/v1.0.10/reference/api#t","T",[1113,1131],"func (a *ASTQL) T(name string, alias ...string) types.Table Creates a validated table reference. Panics if the table doesn't exist in the schema. Optional single-letter alias (a-z).",{"id":1139,"title":1140,"titles":1141,"content":1142,"level":40},"/v1.0.10/reference/api#tryt","TryT",[1113,1131],"func (a *ASTQL) TryT(name string, alias ...string) (types.Table, error) Creates a validated table reference. Returns an error instead of panicking.",{"id":1144,"title":1145,"titles":1146,"content":1147,"level":40},"/v1.0.10/reference/api#f","F",[1113,1131],"func (a *ASTQL) F(name string) types.Field Creates a validated field reference. Panics if the field doesn't exist in any table in the schema.",{"id":1149,"title":1150,"titles":1151,"content":1152,"level":40},"/v1.0.10/reference/api#tryf","TryF",[1113,1131],"func (a *ASTQL) TryF(name string) (types.Field, error) Creates a validated field reference. Returns an error instead of panicking.",{"id":1154,"title":1155,"titles":1156,"content":1157,"level":40},"/v1.0.10/reference/api#p","P",[1113,1131],"func (a *ASTQL) P(name string) types.Param Creates a validated parameter reference. Panics if the name is not a valid SQL identifier.",{"id":1159,"title":1160,"titles":1161,"content":1162,"level":40},"/v1.0.10/reference/api#tryp","TryP",[1113,1131],"func (a *ASTQL) TryP(name string) (types.Param, error) Creates a validated parameter reference. Returns an error instead of panicking.",{"id":1164,"title":1165,"titles":1166,"content":1167,"level":40},"/v1.0.10/reference/api#c","C",[1113,1131],"func (a *ASTQL) C(field types.Field, op types.Operator, param types.Param) types.Condition Creates a validated condition. Panics if the field doesn't exist in the schema.",{"id":1169,"title":1170,"titles":1171,"content":1172,"level":40},"/v1.0.10/reference/api#tryc","TryC",[1113,1131],"func (a *ASTQL) TryC(field types.Field, op types.Operator, param types.Param) (types.Condition, error) Creates a validated condition. Returns an error instead of panicking.",{"id":1174,"title":1175,"titles":1176,"content":1177,"level":40},"/v1.0.10/reference/api#and","And",[1113,1131],"func (a *ASTQL) And(conditions ...types.ConditionItem) types.ConditionGroup Creates an AND condition group. Panics if no conditions provided.",{"id":1179,"title":1180,"titles":1181,"content":1182,"level":40},"/v1.0.10/reference/api#or","Or",[1113,1131],"func (a *ASTQL) Or(conditions ...types.ConditionItem) types.ConditionGroup Creates an OR condition group. Panics if no conditions provided.",{"id":1184,"title":1185,"titles":1186,"content":1187,"level":40},"/v1.0.10/reference/api#null","Null",[1113,1131],"func (a *ASTQL) Null(field types.Field) types.Condition Creates an IS NULL condition.",{"id":1189,"title":1190,"titles":1191,"content":1192,"level":40},"/v1.0.10/reference/api#notnull","NotNull",[1113,1131],"func (a *ASTQL) NotNull(field types.Field) types.Condition Creates an IS NOT NULL condition.",{"id":1194,"title":1195,"titles":1196,"content":1197,"level":40},"/v1.0.10/reference/api#withtable","WithTable",[1113,1131],"func (a *ASTQL) WithTable(field types.Field, tableOrAlias string) types.Field Prefixes a field with a table name or alias. Panics if tableOrAlias is not a valid table name or single-letter alias.",{"id":1199,"title":1200,"titles":1201,"content":1202,"level":40},"/v1.0.10/reference/api#trywithtable","TryWithTable",[1113,1131],"func (a *ASTQL) TryWithTable(field types.Field, tableOrAlias string) (types.Field, error) Prefixes a field with a table name or alias. Returns an error instead of panicking.",{"id":1204,"title":1205,"titles":1206,"content":1207,"level":40},"/v1.0.10/reference/api#aggc","AggC",[1113,1131],"func (a *ASTQL) AggC(aggFunc types.AggregateFunc, field *types.Field, op types.Operator, param types.Param) types.AggregateCondition Creates a validated aggregate condition for HAVING clauses. Use nil for field to create COUNT(*).",{"id":1209,"title":1210,"titles":1211,"content":1212,"level":40},"/v1.0.10/reference/api#tryaggc","TryAggC",[1113,1131],"func (a *ASTQL) TryAggC(aggFunc types.AggregateFunc, field *types.Field, op types.Operator, param types.Param) (types.AggregateCondition, error) Creates a validated aggregate condition. Returns an error instead of panicking.",{"id":1214,"title":1215,"titles":1216,"content":1217,"level":40},"/v1.0.10/reference/api#valuemap","ValueMap",[1113,1131],"func (a *ASTQL) ValueMap() map[types.Field]types.Param Returns an empty map for building INSERT value sets.",{"id":1219,"title":1220,"titles":1221,"content":1222,"level":40},"/v1.0.10/reference/api#jsonbtext","JSONBText",[1113,1131],"func (a *ASTQL) JSONBText(field types.Field, key types.Param) types.Field Creates a JSONB text extraction field with a parameterized key. Renders as field->>:key_param. PostgreSQL only. The key is passed as a parameter for SQL injection safety.",{"id":1224,"title":1225,"titles":1226,"content":1227,"level":40},"/v1.0.10/reference/api#jsonbpath","JSONBPath",[1113,1131],"func (a *ASTQL) JSONBPath(field types.Field, key types.Param) types.Field Creates a JSONB path access field with a parameterized key. Renders as field->:key_param. PostgreSQL only. Use with ArrayContains for JSONB array queries. The key is passed as a parameter for SQL injection safety.",{"id":1229,"title":1230,"titles":1231,"content":34,"level":19},"/v1.0.10/reference/api#query-builders","Query Builders",[1113],{"id":1233,"title":1234,"titles":1235,"content":1236,"level":40},"/v1.0.10/reference/api#select","Select",[1113,1230],"func Select(t types.Table) *Builder Creates a new SELECT query builder.",{"id":1238,"title":1239,"titles":1240,"content":1241,"level":40},"/v1.0.10/reference/api#insert","Insert",[1113,1230],"func Insert(t types.Table) *Builder Creates a new INSERT query builder.",{"id":1243,"title":1244,"titles":1245,"content":1246,"level":40},"/v1.0.10/reference/api#update","Update",[1113,1230],"func Update(t types.Table) *Builder Creates a new UPDATE query builder.",{"id":1248,"title":1249,"titles":1250,"content":1251,"level":40},"/v1.0.10/reference/api#delete","Delete",[1113,1230],"func Delete(t types.Table) *Builder Creates a new DELETE query builder.",{"id":1253,"title":1254,"titles":1255,"content":1256,"level":40},"/v1.0.10/reference/api#count","Count",[1113,1230],"func Count(t types.Table) *Builder Creates a new COUNT query builder.",{"id":1258,"title":1259,"titles":1260,"content":34,"level":19},"/v1.0.10/reference/api#builder-methods","Builder Methods",[1113],{"id":1262,"title":100,"titles":1263,"content":1264,"level":40},"/v1.0.10/reference/api#fields",[1113,1259],"func (b *Builder) Fields(fields ...types.Field) *Builder Sets the fields to select. SELECT only.",{"id":1266,"title":1267,"titles":1268,"content":1269,"level":40},"/v1.0.10/reference/api#where","Where",[1113,1259],"func (b *Builder) Where(condition types.ConditionItem) *Builder Sets or adds WHERE conditions. Multiple calls combine with AND.",{"id":1271,"title":1272,"titles":1273,"content":1274,"level":40},"/v1.0.10/reference/api#wherefield","WhereField",[1113,1259],"func (b *Builder) WhereField(f types.Field, op types.Operator, p types.Param) *Builder Shorthand for simple field conditions.",{"id":1276,"title":1277,"titles":1278,"content":1279,"level":40},"/v1.0.10/reference/api#orderby","OrderBy",[1113,1259],"func (b *Builder) OrderBy(f types.Field, direction types.Direction) *Builder Adds ORDER BY clause.",{"id":1281,"title":1282,"titles":1283,"content":1284,"level":40},"/v1.0.10/reference/api#orderbynulls","OrderByNulls",[1113,1259],"func (b *Builder) OrderByNulls(f types.Field, direction types.Direction, nulls types.NullsOrdering) *Builder Adds ORDER BY with NULLS FIRST/LAST.",{"id":1286,"title":1287,"titles":1288,"content":1289,"level":40},"/v1.0.10/reference/api#orderbyexpr","OrderByExpr",[1113,1259],"func (b *Builder) OrderByExpr(f types.Field, op types.Operator, p types.Param, direction types.Direction) *Builder Adds ORDER BY with an expression (e.g., vector distance).",{"id":1291,"title":1292,"titles":1293,"content":1294,"level":40},"/v1.0.10/reference/api#limit","Limit",[1113,1259],"func (b *Builder) Limit(limit int) *Builder Sets the LIMIT clause with a static value.",{"id":1296,"title":1297,"titles":1298,"content":1299,"level":40},"/v1.0.10/reference/api#limitparam","LimitParam",[1113,1259],"func (b *Builder) LimitParam(param types.Param) *Builder Sets the LIMIT clause with a parameterized value.",{"id":1301,"title":1302,"titles":1303,"content":1304,"level":40},"/v1.0.10/reference/api#offset","Offset",[1113,1259],"func (b *Builder) Offset(offset int) *Builder Sets the OFFSET clause with a static value.",{"id":1306,"title":1307,"titles":1308,"content":1309,"level":40},"/v1.0.10/reference/api#offsetparam","OffsetParam",[1113,1259],"func (b *Builder) OffsetParam(param types.Param) *Builder Sets the OFFSET clause with a parameterized value.",{"id":1311,"title":1312,"titles":1313,"content":1314,"level":40},"/v1.0.10/reference/api#set","Set",[1113,1259],"func (b *Builder) Set(f types.Field, p types.Param) *Builder Adds a field update. UPDATE only.",{"id":1316,"title":1317,"titles":1318,"content":1319,"level":40},"/v1.0.10/reference/api#setexpr","SetExpr",[1113,1259],"func (b *Builder) SetExpr(f types.Field, expr types.FieldExpression) *Builder Adds a field update with an expression value. UPDATE only. Use this for computed assignments like atomic increments. // Returns: \"items_completed\" = \"items_completed\" + :increment\n.SetExpr(instance.F(\"items_completed\"), types.FieldExpression{\n    Binary: &types.BinaryExpression{\n        Field:    instance.F(\"items_completed\"),\n        Operator: \"+\",\n        Param:    instance.P(\"increment\"),\n    },\n})",{"id":1321,"title":1322,"titles":1323,"content":1324,"level":40},"/v1.0.10/reference/api#values","Values",[1113,1259],"func (b *Builder) Values(valueMap map[types.Field]types.Param) *Builder Adds a row of values. INSERT only. Call multiple times for multiple rows.",{"id":1326,"title":1327,"titles":1328,"content":1329,"level":40},"/v1.0.10/reference/api#returning","Returning",[1113,1259],"func (b *Builder) Returning(fields ...types.Field) *Builder Adds RETURNING clause. INSERT, UPDATE, DELETE only.",{"id":1331,"title":1332,"titles":1333,"content":1334,"level":40},"/v1.0.10/reference/api#distinct","Distinct",[1113,1259],"func (b *Builder) Distinct() *Builder Adds DISTINCT to SELECT.",{"id":1336,"title":1337,"titles":1338,"content":1339,"level":40},"/v1.0.10/reference/api#distincton","DistinctOn",[1113,1259],"func (b *Builder) DistinctOn(fields ...types.Field) *Builder Adds DISTINCT ON to SELECT. PostgreSQL only.",{"id":1341,"title":1342,"titles":1343,"content":1344,"level":40},"/v1.0.10/reference/api#groupby","GroupBy",[1113,1259],"func (b *Builder) GroupBy(fields ...types.Field) *Builder Adds GROUP BY clause. SELECT only.",{"id":1346,"title":1347,"titles":1348,"content":1349,"level":40},"/v1.0.10/reference/api#having","Having",[1113,1259],"func (b *Builder) Having(conditions ...types.Condition) *Builder Adds HAVING conditions. Requires GROUP BY.",{"id":1351,"title":1352,"titles":1353,"content":1354,"level":40},"/v1.0.10/reference/api#havingagg","HavingAgg",[1113,1259],"func (b *Builder) HavingAgg(conditions ...types.AggregateCondition) *Builder Adds aggregate HAVING conditions (COUNT(*) > ). Requires GROUP BY.",{"id":1356,"title":1357,"titles":1358,"content":1359,"level":40},"/v1.0.10/reference/api#selectexpr","SelectExpr",[1113,1259],"func (b *Builder) SelectExpr(expr types.FieldExpression) *Builder Adds a field expression (aggregate, CASE, window function).",{"id":1361,"title":1362,"titles":1363,"content":1364,"level":40},"/v1.0.10/reference/api#selectbinaryexpr","SelectBinaryExpr",[1113,1259],"func (b *Builder) SelectBinaryExpr(f types.Field, op types.Operator, p types.Param, alias string) *Builder Adds a binary expression (field  param) with an alias to SELECT. Useful for vector distance calculations with pgvector. // Returns: \"embedding\" \u003C=> :query_vec AS \"score\"\n.SelectBinaryExpr(instance.F(\"embedding\"), astql.VectorCosineDistance, instance.P(\"query_vec\"), \"score\")",{"id":1366,"title":1367,"titles":1368,"content":1369,"level":40},"/v1.0.10/reference/api#onconflict","OnConflict",[1113,1259],"func (b *Builder) OnConflict(columns ...types.Field) *ConflictBuilder Starts ON CONFLICT clause. INSERT only.",{"id":1371,"title":1372,"titles":1373,"content":1374,"level":40},"/v1.0.10/reference/api#join-methods","Join Methods",[1113,1259],"func (b *Builder) Join(table types.Table, on types.ConditionItem) *Builder\nfunc (b *Builder) InnerJoin(table types.Table, on types.ConditionItem) *Builder\nfunc (b *Builder) LeftJoin(table types.Table, on types.ConditionItem) *Builder\nfunc (b *Builder) RightJoin(table types.Table, on types.ConditionItem) *Builder\nfunc (b *Builder) FullOuterJoin(table types.Table, on types.ConditionItem) *Builder\nfunc (b *Builder) CrossJoin(table types.Table) *Builder Adds JOIN clauses. SELECT and COUNT only.",{"id":1376,"title":1377,"titles":1378,"content":1379,"level":40},"/v1.0.10/reference/api#row-locking","Row Locking",[1113,1259],"func (b *Builder) ForUpdate() *Builder\nfunc (b *Builder) ForNoKeyUpdate() *Builder\nfunc (b *Builder) ForShare() *Builder\nfunc (b *Builder) ForKeyShare() *Builder Adds row locking. SELECT only.",{"id":1381,"title":1382,"titles":1383,"content":1384,"level":40},"/v1.0.10/reference/api#build","Build",[1113,1259],"func (b *Builder) Build() (*types.AST, error) Returns the constructed AST or an error.",{"id":1386,"title":1387,"titles":1388,"content":1389,"level":40},"/v1.0.10/reference/api#mustbuild","MustBuild",[1113,1259],"func (b *Builder) MustBuild() *types.AST Returns the AST or panics on error.",{"id":1391,"title":1392,"titles":1393,"content":1394,"level":40},"/v1.0.10/reference/api#render","Render",[1113,1259],"func (b *Builder) Render(renderer Renderer) (*QueryResult, error) Builds and renders the query to SQL using the specified provider (e.g., postgres.New(), sqlite.New()).",{"id":1396,"title":1397,"titles":1398,"content":1399,"level":40},"/v1.0.10/reference/api#mustrender","MustRender",[1113,1259],"func (b *Builder) MustRender(renderer Renderer) *QueryResult Builds and renders the query with the specified provider or panics on error.",{"id":1401,"title":1402,"titles":1403,"content":34,"level":19},"/v1.0.10/reference/api#set-operations","Set Operations",[1113],{"id":1405,"title":1406,"titles":1407,"content":1408,"level":40},"/v1.0.10/reference/api#union-unionall","Union / UnionAll",[1113,1402],"func (b *Builder) Union(other *Builder) *CompoundBuilder\nfunc (b *Builder) UnionAll(other *Builder) *CompoundBuilder Creates a UNION or UNION ALL between two SELECT queries.",{"id":1410,"title":1411,"titles":1412,"content":1413,"level":40},"/v1.0.10/reference/api#intersect-intersectall","Intersect / IntersectAll",[1113,1402],"func (b *Builder) Intersect(other *Builder) *CompoundBuilder\nfunc (b *Builder) IntersectAll(other *Builder) *CompoundBuilder Creates an INTERSECT between two SELECT queries.",{"id":1415,"title":1416,"titles":1417,"content":1418,"level":40},"/v1.0.10/reference/api#except-exceptall","Except / ExceptAll",[1113,1402],"func (b *Builder) Except(other *Builder) *CompoundBuilder\nfunc (b *Builder) ExceptAll(other *Builder) *CompoundBuilder Creates an EXCEPT between two SELECT queries.",{"id":1420,"title":1421,"titles":1422,"content":1423,"level":19},"/v1.0.10/reference/api#rendering","Rendering",[1113],"Rendering is done through provider instances. Each provider implements the Renderer interface: type Renderer interface {\n    Render(ast *types.AST) (*QueryResult, error)\n    RenderCompound(query *types.CompoundQuery) (*QueryResult, error)\n} Use the provider's methods directly or through the builder's Render() method: // Through builder (recommended)\nresult, err := query.Render(postgres.New())\n\n// Direct provider use\nast, _ := query.Build()\nresult, err := postgres.New().Render(ast)",{"id":1425,"title":1426,"titles":1427,"content":34,"level":19},"/v1.0.10/reference/api#expression-functions","Expression Functions",[1113],{"id":1429,"title":575,"titles":1430,"content":1431,"level":40},"/v1.0.10/reference/api#aggregates",[1113,1426],"func Sum(field types.Field) types.FieldExpression\nfunc Avg(field types.Field) types.FieldExpression\nfunc Min(field types.Field) types.FieldExpression\nfunc Max(field types.Field) types.FieldExpression\nfunc CountField(field types.Field) types.FieldExpression\nfunc CountDistinct(field types.Field) types.FieldExpression\nfunc CountStar() types.FieldExpression",{"id":1433,"title":1434,"titles":1435,"content":1436,"level":40},"/v1.0.10/reference/api#filter-aggregates","Filter Aggregates",[1113,1426],"func SumFilter(field types.Field, filter types.ConditionItem) types.FieldExpression\nfunc AvgFilter(field types.Field, filter types.ConditionItem) types.FieldExpression\nfunc MinFilter(field types.Field, filter types.ConditionItem) types.FieldExpression\nfunc MaxFilter(field types.Field, filter types.ConditionItem) types.FieldExpression\nfunc CountFieldFilter(field types.Field, filter types.ConditionItem) types.FieldExpression\nfunc CountDistinctFilter(field types.Field, filter types.ConditionItem) types.FieldExpression",{"id":1438,"title":1439,"titles":1440,"content":1441,"level":40},"/v1.0.10/reference/api#binary-expressions","Binary Expressions",[1113,1426],"func BinaryExpr(field types.Field, op types.Operator, param types.Param) types.FieldExpression Creates a binary expression for field \u003Cop> param patterns. Commonly used with vector distance operators to select computed distances as columns. For selecting binary expressions, prefer the builder method SelectBinaryExpr for cleaner syntax: // Preferred: using SelectBinaryExpr\n.SelectBinaryExpr(instance.F(\"embedding\"), astql.VectorL2Distance, instance.P(\"query\"), \"distance\")\n\n// Alternative: using BinaryExpr with As\n.SelectExpr(astql.As(\n    astql.BinaryExpr(instance.F(\"embedding\"), astql.VectorL2Distance, instance.P(\"query\")),\n    \"distance\",\n))\n\n// Both render: \"embedding\" \u003C-> :query AS \"distance\"",{"id":1443,"title":120,"titles":1444,"content":1445,"level":40},"/v1.0.10/reference/api#conditions",[1113,1426],"func Between(field types.Field, low, high types.Param) types.BetweenCondition\nfunc NotBetween(field types.Field, low, high types.Param) types.BetweenCondition\nfunc CF(left types.Field, op types.Operator, right types.Field) types.FieldComparison",{"id":1447,"title":464,"titles":1448,"content":1449,"level":40},"/v1.0.10/reference/api#subqueries",[1113,1426],"func Sub(builder *Builder) types.Subquery\nfunc CSub(field types.Field, op types.Operator, subquery types.Subquery) types.SubqueryCondition\nfunc CSubExists(op types.Operator, subquery types.Subquery) types.SubqueryCondition",{"id":1451,"title":1452,"titles":1453,"content":1454,"level":40},"/v1.0.10/reference/api#case-expression","CASE Expression",[1113,1426],"func Case() *CaseBuilder\nfunc (cb *CaseBuilder) When(condition types.ConditionItem, result types.Param) *CaseBuilder\nfunc (cb *CaseBuilder) Else(result types.Param) *CaseBuilder\nfunc (cb *CaseBuilder) As(alias string) *CaseBuilder\nfunc (cb *CaseBuilder) Build() types.FieldExpression",{"id":1456,"title":1457,"titles":1458,"content":1459,"level":40},"/v1.0.10/reference/api#null-handling","Null Handling",[1113,1426],"func Coalesce(values ...types.Param) types.FieldExpression\nfunc NullIf(value1, value2 types.Param) types.FieldExpression",{"id":1461,"title":663,"titles":1462,"content":1463,"level":40},"/v1.0.10/reference/api#math-functions",[1113,1426],"func Round(field types.Field, precision ...types.Param) types.FieldExpression\nfunc Floor(field types.Field) types.FieldExpression\nfunc Ceil(field types.Field) types.FieldExpression\nfunc Abs(field types.Field) types.FieldExpression\nfunc Power(field types.Field, exponent types.Param) types.FieldExpression\nfunc Sqrt(field types.Field) types.FieldExpression",{"id":1465,"title":1466,"titles":1467,"content":1468,"level":40},"/v1.0.10/reference/api#string-functions","String Functions",[1113,1426],"func Upper(field types.Field) types.FieldExpression\nfunc Lower(field types.Field) types.FieldExpression\nfunc Trim(field types.Field) types.FieldExpression\nfunc LTrim(field types.Field) types.FieldExpression\nfunc RTrim(field types.Field) types.FieldExpression\nfunc Length(field types.Field) types.FieldExpression\nfunc Substring(field types.Field, start types.Param, length types.Param) types.FieldExpression\nfunc Replace(field types.Field, search types.Param, replacement types.Param) types.FieldExpression\nfunc Concat(fields ...types.Field) types.FieldExpression",{"id":1470,"title":1471,"titles":1472,"content":1473,"level":40},"/v1.0.10/reference/api#date-functions","Date Functions",[1113,1426],"func Now() types.FieldExpression                                    // Current timestamp\nfunc CurrentDate() types.FieldExpression                            // Current date\nfunc CurrentTime() types.FieldExpression                            // Current time\nfunc CurrentTimestamp() types.FieldExpression                       // Current timestamp\nfunc Extract(part types.DatePart, field types.Field) types.FieldExpression  // Extract part from date\nfunc DateTrunc(part types.DatePart, field types.Field) types.FieldExpression // Truncate to precision Date parts: PartYear, PartMonth, PartDay, PartHour, PartMinute, PartSecond, PartWeek, PartQuarter, PartDayOfWeek, PartDayOfYear, PartEpoch.",{"id":1475,"title":682,"titles":1476,"content":1477,"level":40},"/v1.0.10/reference/api#type-casting",[1113,1426],"func Cast(field types.Field, castType types.CastType) types.FieldExpression",{"id":1479,"title":633,"titles":1480,"content":1481,"level":40},"/v1.0.10/reference/api#window-functions",[1113,1426],"func RowNumber() *WindowBuilder\nfunc Rank() *WindowBuilder\nfunc DenseRank() *WindowBuilder\nfunc Ntile(n types.Param) *WindowBuilder\nfunc Lag(field types.Field, offset types.Param, defaultVal ...types.Param) *WindowBuilder\nfunc Lead(field types.Field, offset types.Param, defaultVal ...types.Param) *WindowBuilder\nfunc FirstValue(field types.Field) *WindowBuilder\nfunc LastValue(field types.Field) *WindowBuilder\nfunc SumOver(field types.Field) *WindowBuilder\nfunc AvgOver(field types.Field) *WindowBuilder\nfunc CountOver(field ...types.Field) *WindowBuilder\nfunc MinOver(field types.Field) *WindowBuilder\nfunc MaxOver(field types.Field) *WindowBuilder",{"id":1483,"title":653,"titles":1484,"content":1485,"level":40},"/v1.0.10/reference/api#window-specification",[1113,1426],"func Window() *WindowSpecBuilder\nfunc (wsb *WindowSpecBuilder) PartitionBy(fields ...types.Field) *WindowSpecBuilder\nfunc (wsb *WindowSpecBuilder) OrderBy(field types.Field, direction types.Direction) *WindowSpecBuilder\nfunc (wsb *WindowSpecBuilder) OrderByNulls(field types.Field, direction types.Direction, nulls types.NullsOrdering) *WindowSpecBuilder\nfunc (wsb *WindowSpecBuilder) Rows(start, end types.FrameBound) *WindowSpecBuilder\nfunc (wsb *WindowSpecBuilder) Build() types.WindowSpec",{"id":1487,"title":1488,"titles":1489,"content":1490,"level":40},"/v1.0.10/reference/api#windowbuilder-methods","WindowBuilder Methods",[1113,1426],"func (wb *WindowBuilder) Over(spec types.WindowSpec) *WindowBuilder\nfunc (wb *WindowBuilder) OverBuilder(builder *WindowSpecBuilder) *WindowBuilder\nfunc (wb *WindowBuilder) PartitionBy(fields ...types.Field) *WindowBuilder\nfunc (wb *WindowBuilder) OrderBy(field types.Field, direction types.Direction) *WindowBuilder\nfunc (wb *WindowBuilder) Frame(start, end types.FrameBound) *WindowBuilder\nfunc (wb *WindowBuilder) As(alias string) types.FieldExpression\nfunc (wb *WindowBuilder) Build() types.FieldExpression",{"id":1492,"title":1493,"titles":1494,"content":1495,"level":40},"/v1.0.10/reference/api#expression-alias","Expression Alias",[1113,1426],"func As(expr types.FieldExpression, alias string) types.FieldExpression",{"id":1497,"title":623,"titles":1498,"content":1499,"level":40},"/v1.0.10/reference/api#having-helpers",[1113,1426],"func HavingCount(op types.Operator, value types.Param) types.AggregateCondition\nfunc HavingCountField(field types.Field, op types.Operator, value types.Param) types.AggregateCondition\nfunc HavingCountDistinct(field types.Field, op types.Operator, value types.Param) types.AggregateCondition\nfunc HavingSum(field types.Field, op types.Operator, value types.Param) types.AggregateCondition\nfunc HavingAvg(field types.Field, op types.Operator, value types.Param) types.AggregateCondition\nfunc HavingMin(field types.Field, op types.Operator, value types.Param) types.AggregateCondition\nfunc HavingMax(field types.Field, op types.Operator, value types.Param) types.AggregateCondition",{"id":1501,"title":1502,"titles":1503,"content":34,"level":19},"/v1.0.10/reference/api#types","Types",[1113],{"id":1505,"title":1506,"titles":1507,"content":1508,"level":40},"/v1.0.10/reference/api#queryresult","QueryResult",[1113,1502],"type QueryResult struct {\n    SQL            string\n    RequiredParams []string\n} Contains the rendered SQL and list of required parameters.",{"id":1510,"title":1511,"titles":1512,"content":1513,"level":40},"/v1.0.10/reference/api#direction","Direction",[1113,1502],"const (\n    ASC  Direction = \"ASC\"\n    DESC Direction = \"DESC\"\n) Sort direction for ORDER BY.",{"id":1515,"title":1516,"titles":1517,"content":1518,"level":40},"/v1.0.10/reference/api#nullsordering","NullsOrdering",[1113,1502],"const (\n    NullsFirst NullsOrdering = \"NULLS FIRST\"\n    NullsLast  NullsOrdering = \"NULLS LAST\"\n) NULL ordering for ORDER BY.",{"id":1520,"title":1521,"titles":1522,"content":1523,"level":40},"/v1.0.10/reference/api#operation","Operation",[1113,1502],"const (\n    OpSelect Operation = \"SELECT\"\n    OpInsert Operation = \"INSERT\"\n    OpUpdate Operation = \"UPDATE\"\n    OpDelete Operation = \"DELETE\"\n    OpCount  Operation = \"COUNT\"\n) Query operation types.",{"id":1525,"title":1526,"titles":1527,"content":34,"level":19},"/v1.0.10/reference/api#providers","Providers",[1113],{"id":1529,"title":1530,"titles":1531,"content":1532,"level":40},"/v1.0.10/reference/api#renderer-interface","Renderer Interface",[1113,1526],"type Renderer interface {\n    Render(ast *types.AST) (*types.QueryResult, error)\n    RenderCompound(query *types.CompoundQuery) (*types.QueryResult, error)\n    Capabilities() render.Capabilities\n}",{"id":1534,"title":27,"titles":1535,"content":1536,"level":40},"/v1.0.10/reference/api#capabilities",[1113,1526],"Query dialect capabilities before execution: type Capabilities struct {\n    DistinctOn          bool            // DISTINCT ON (field, ...)\n    Upsert              bool            // ON CONFLICT / ON DUPLICATE KEY\n    ReturningOnInsert   bool            // RETURNING after INSERT\n    ReturningOnUpdate   bool            // RETURNING after UPDATE\n    ReturningOnDelete   bool            // RETURNING after DELETE\n    CaseInsensitiveLike bool            // ILIKE operator\n    RegexOperators      bool            // ~, ~*, !~, !~*\n    ArrayOperators      bool            // @>, \u003C@, &&\n    InArray             bool            // IN (:array_param)\n    RowLocking          RowLockingLevel // FOR UPDATE/SHARE support\n}\n\ntype RowLockingLevel int\n\nconst (\n    RowLockingNone  RowLockingLevel = iota // No row locking\n    RowLockingBasic                        // FOR UPDATE, FOR SHARE\n    RowLockingFull                         // + FOR NO KEY UPDATE, FOR KEY SHARE\n) Example usage: renderer := postgres.New()\ncaps := renderer.Capabilities()\n\nif caps.Upsert {\n    // Safe to use ON CONFLICT\n}",{"id":1538,"title":1539,"titles":1540,"content":1541,"level":40},"/v1.0.10/reference/api#postgresql-provider","PostgreSQL Provider",[1113,1526],"import \"github.com/zoobzio/astql/postgres\"\n\nrenderer := postgres.New()\nresult, err := query.Render(renderer) Full feature support.",{"id":1543,"title":1544,"titles":1545,"content":1546,"level":40},"/v1.0.10/reference/api#sqlite-provider","SQLite Provider",[1113,1526],"import \"github.com/zoobzio/astql/sqlite\"\n\nrenderer := sqlite.New()\nresult, err := query.Render(renderer) Returns UnsupportedFeatureError for features not available in SQLite: DISTINCT ONILIKE / NOT ILIKERegex operators (~, ~*, !~, !~*)Array operators (@>, \u003C@, &&)Vector operators (\u003C->, \u003C#>, \u003C=>, \u003C+>)JSONB field access (->>, ->)IN / NOT IN with array parametersRow-level locking (FOR UPDATE, FOR SHARE)POWER and SQRT math functions",{"id":1548,"title":1549,"titles":1550,"content":1551,"level":40},"/v1.0.10/reference/api#mariadb-provider","MariaDB Provider",[1113,1526],"import \"github.com/zoobzio/astql/mariadb\"\n\nrenderer := mariadb.New()\nresult, err := query.Render(renderer) MariaDB-specific behavior: Uses backtick quoting for identifiers: `name`Uses :name parameter placeholders (sqlx compatible)ON CONFLICT DO UPDATE → ON DUPLICATE KEY UPDATEON CONFLICT DO NOTHING → ON DUPLICATE KEY UPDATE field = field (no-op)ILIKE maps to LIKE (MariaDB LIKE is case-insensitive by default)Standard IN (...) syntax instead of = ANY(:array)RETURNING clause support for INSERT/DELETE (MariaDB 10.5+, UPDATE not supported) Returns UnsupportedFeatureError for: DISTINCT ONFILTER on aggregatesRegex operators (~, ~*, !~, !~*)Array operators (@>, \u003C@, &&)Vector operators (\u003C->, \u003C#>, \u003C=>, \u003C+>)JSONB field access (->>, ->)FOR NO KEY UPDATE / FOR KEY SHARE (use FOR UPDATE or FOR SHARE instead)",{"id":1553,"title":1554,"titles":1555,"content":1556,"level":40},"/v1.0.10/reference/api#sql-server-provider","SQL Server Provider",[1113,1526],"import \"github.com/zoobzio/astql/mssql\"\n\nrenderer := mssql.New()\nresult, err := query.Render(renderer) SQL Server-specific behavior: Uses square bracket quoting for identifiers: [name]Uses @name parameter placeholdersLIMIT/OFFSET → OFFSET n ROWS FETCH NEXT m ROWS ONLY (requires ORDER BY)RETURNING → OUTPUT INSERTED.* / OUTPUT DELETED.*LENGTH() → LEN()NOW() → GETDATE()EXTRACT() → DATEPART()!= → \u003C> (preferred SQL Server syntax) Returns UnsupportedFeatureError for: ON CONFLICT / upsert (MERGE is too complex)DISTINCT ONILIKE / NOT ILIKEFILTER on aggregatesRegex operators (~, ~*, !~, !~*)Array operators (@>, \u003C@, &&)Vector operators (\u003C->, \u003C#>, \u003C=>, \u003C+>)JSONB field access (->>, ->)Row-level locking (FOR UPDATE, FOR SHARE)LIMIT without ORDER BY (returns error) 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 .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 .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 .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 .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}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)}",{"id":1558,"title":1559,"titles":1560,"content":1561,"level":9},"/v1.0.10/reference/operators","Operators Reference",[],"All comparison and special operators",{"id":1563,"title":1559,"titles":1564,"content":1565,"level":9},"/v1.0.10/reference/operators#operators-reference",[],"Complete list of operators available in ASTQL.",{"id":1567,"title":417,"titles":1568,"content":1569,"level":19},"/v1.0.10/reference/operators#comparison-operators",[1559],"Basic comparison operators for WHERE conditions. ConstantSQLDescriptionExampleEQ=Equals\"id\" = :idNE!=Not equals\"status\" != :statusGT>Greater than\"price\" > :minGE>=Greater than or equal\"age\" >= :min_ageLT\u003CLess than\"date\" \u003C :cutoffLE\u003C=Less than or equal\"count\" \u003C= :max",{"id":1571,"title":257,"titles":1572,"content":1573,"level":40},"/v1.0.10/reference/operators#usage",[1559,417],"instance.C(instance.F(\"id\"), astql.EQ, instance.P(\"id\"))\n// \"id\" = :id",{"id":1575,"title":422,"titles":1576,"content":1577,"level":19},"/v1.0.10/reference/operators#pattern-matching",[1559],"String pattern matching operators. ConstantSQLDescriptionExampleLIKELIKEPattern match (case-sensitive)\"name\" LIKE :patternNotLikeNOT LIKEPattern non-match\"name\" NOT LIKE :patternILIKEILIKEPattern match (case-insensitive)\"name\" ILIKE :patternNotILikeNOT ILIKEPattern non-match (case-insensitive)\"name\" NOT ILIKE :pattern",{"id":1579,"title":257,"titles":1580,"content":1581,"level":40},"/v1.0.10/reference/operators#usage-1",[1559,422],"instance.C(instance.F(\"email\"), astql.ILIKE, instance.P(\"search\"))\n// \"email\" ILIKE :search\n\n// With wildcards in parameter value\nparams := map[string]any{\"search\": \"%@example.com\"}",{"id":1583,"title":1584,"titles":1585,"content":1586,"level":19},"/v1.0.10/reference/operators#null-operators","NULL Operators",[1559],"Check for NULL values. ConstantSQLDescriptionExampleIsNullIS NULLValue is NULL\"deleted_at\" IS NULLIsNotNullIS NOT NULLValue is not NULL\"verified_at\" IS NOT NULL",{"id":1588,"title":257,"titles":1589,"content":1590,"level":40},"/v1.0.10/reference/operators#usage-2",[1559,1584],"instance.Null(instance.F(\"deleted_at\"))\n// \"deleted_at\" IS NULL\n\ninstance.NotNull(instance.F(\"email\"))\n// \"email\" IS NOT NULL",{"id":1592,"title":432,"titles":1593,"content":1594,"level":19},"/v1.0.10/reference/operators#array-operators",[1559],"PostgreSQL array membership operators. ConstantSQLDescriptionExampleIN= ANY()Value in array\"id\" = ANY(:ids)NotIn!= ALL()Value not in array\"status\" != ALL(:excluded)",{"id":1596,"title":257,"titles":1597,"content":1598,"level":40},"/v1.0.10/reference/operators#usage-3",[1559,432],"instance.C(instance.F(\"id\"), astql.IN, instance.P(\"ids\"))\n// \"id\" = ANY(:ids)\n\ninstance.C(instance.F(\"status\"), astql.NotIn, instance.P(\"excluded\"))\n// \"status\" != ALL(:excluded)",{"id":1600,"title":1601,"titles":1602,"content":1603,"level":40},"/v1.0.10/reference/operators#with-sqlx","With sqlx",[1559,432],"import \"github.com/lib/pq\"\n\nparams := map[string]any{\n    \"ids\": pq.Array([]int{1, 2, 3}),\n    \"excluded\": pq.Array([]string{\"draft\", \"deleted\"}),\n}",{"id":1605,"title":1606,"titles":1607,"content":1608,"level":19},"/v1.0.10/reference/operators#subquery-operators","Subquery Operators",[1559],"Operators for subquery conditions. ConstantSQLDescriptionININValue in subquery resultsNotInNOT INValue not in subquery resultsEXISTSEXISTSSubquery returns rowsNotExistsNOT EXISTSSubquery returns no rows",{"id":1610,"title":257,"titles":1611,"content":1612,"level":40},"/v1.0.10/reference/operators#usage-4",[1559,1606],"// IN subquery\nsubquery := astql.Sub(astql.Select(table).Fields(field))\nastql.CSub(instance.F(\"id\"), astql.IN, subquery)\n// \"id\" IN (SELECT \"user_id\" FROM \"orders\")\n\n// EXISTS\nastql.CSubExists(astql.EXISTS, subquery)\n// EXISTS (SELECT \"id\" FROM \"orders\" WHERE ...)",{"id":1614,"title":1615,"titles":1616,"content":1617,"level":19},"/v1.0.10/reference/operators#regex-operators","Regex Operators",[1559],"PostgreSQL regular expression operators. ConstantSQLDescriptionExampleRegexMatch~Regex match (case-sensitive)\"code\" ~ :patternRegexIMatch~*Regex match (case-insensitive)\"name\" ~* :patternNotRegexMatch!~Regex non-match\"email\" !~ :patternNotRegexIMatch!~*Regex non-match (case-insensitive)\"name\" !~* :pattern",{"id":1619,"title":257,"titles":1620,"content":1621,"level":40},"/v1.0.10/reference/operators#usage-5",[1559,1615],"instance.C(instance.F(\"code\"), astql.RegexMatch, instance.P(\"pattern\"))\n// \"code\" ~ :pattern\n\nparams := map[string]any{\"pattern\": \"^[A-Z]{3}-[0-9]+$\"}",{"id":1623,"title":1624,"titles":1625,"content":1626,"level":19},"/v1.0.10/reference/operators#postgresql-array-operators","PostgreSQL Array Operators",[1559],"Array containment and overlap operators. ConstantSQLDescriptionExampleArrayContains@>Array contains\"tags\" @> :required_tagsArrayContainedBy\u003C@Array is contained by\"permissions\" \u003C@ :allowedArrayOverlap&&Arrays overlap\"categories\" && :search_cats",{"id":1628,"title":257,"titles":1629,"content":1630,"level":40},"/v1.0.10/reference/operators#usage-6",[1559,1624],"instance.C(instance.F(\"tags\"), astql.ArrayContains, instance.P(\"required\"))\n// \"tags\" @> :required\n\nparams := map[string]any{\n    \"required\": pq.Array([]string{\"featured\", \"published\"}),\n}",{"id":1632,"title":1633,"titles":1634,"content":1635,"level":19},"/v1.0.10/reference/operators#jsonb-field-access-postgresql","JSONB Field Access (PostgreSQL)",[1559],"Access JSONB object keys directly in queries. Keys are parameterized for SQL injection safety. MethodSQLDescriptionExampleJSONBText()->>Extract as text\"metadata\"->>:key_paramJSONBPath()->Extract as JSON\"metadata\"->:key_param",{"id":1637,"title":257,"titles":1638,"content":1639,"level":40},"/v1.0.10/reference/operators#usage-7",[1559,1633],"// Text extraction (returns string) - key is parameterized\nstatusField := instance.JSONBText(instance.F(\"metadata\"), instance.P(\"status_key\"))\n// \"metadata\"->>:status_key\n\n// Path access (returns JSONB, use with array operators)\ntagsField := instance.JSONBPath(instance.F(\"metadata\"), instance.P(\"tags_key\"))\n// \"metadata\"->:tags_key\n\n// In WHERE clause\ninstance.C(statusField, astql.EQ, instance.P(\"status_value\"))\n// \"metadata\"->>:status_key = :status_value\n\n// JSONB array containment\ninstance.C(tagsField, astql.ArrayContains, instance.P(\"required_tags\"))\n// \"metadata\"->:tags_key @> :required_tags Both the JSONB key and the comparison value are parameterized, preventing SQL injection.",{"id":1641,"title":1642,"titles":1643,"content":1644,"level":19},"/v1.0.10/reference/operators#vector-operators-pgvector","Vector Operators (pgvector)",[1559],"Distance operators for vector similarity search. ConstantSQLDescriptionUse CaseVectorL2Distance\u003C->Euclidean (L2) distanceGeneral similarityVectorCosineDistance\u003C=>Cosine distanceText embeddingsVectorInnerProduct\u003C#>Negative inner productMaximum similarityVectorL1Distance\u003C+>Manhattan (L1) distanceSparse vectors",{"id":1646,"title":257,"titles":1647,"content":1648,"level":40},"/v1.0.10/reference/operators#usage-8",[1559,1642],"// In WHERE clause\ninstance.C(instance.F(\"embedding\"), astql.VectorL2Distance, instance.P(\"query\"))\n// \"embedding\" \u003C-> :query\n\n// In ORDER BY (most common)\nquery.OrderByExpr(\n    instance.F(\"embedding\"),\n    astql.VectorCosineDistance,\n    instance.P(\"query_embedding\"),\n    astql.ASC,\n)\n// ORDER BY \"embedding\" \u003C=> :query_embedding ASC",{"id":1650,"title":1651,"titles":1652,"content":1653,"level":40},"/v1.0.10/reference/operators#with-pgvector","With pgvector",[1559,1642],"import \"github.com/pgvector/pgvector-go\"\n\nembedding := pgvector.NewVector([]float32{0.1, 0.2, 0.3, ...})\nparams := map[string]any{\"query_embedding\": embedding}",{"id":1655,"title":1656,"titles":1657,"content":1658,"level":19},"/v1.0.10/reference/operators#operator-selection-guide","Operator Selection Guide",[1559],"NeedOperatorExact matchEQNot equalNERange checkGT, GE, LT, LEPattern searchLIKE, ILIKENULL checkIsNull, IsNotNullList membershipIN, NotInRegex matchRegexMatch, RegexIMatchArray containmentArrayContains, ArrayContainedByVector similarityVectorL2Distance, VectorCosineDistanceSubquery checkEXISTS, NotExists",{"id":1660,"title":584,"titles":1661,"content":1662,"level":19},"/v1.0.10/reference/operators#aggregate-functions",[1559],"For use with SelectExpr and aggregate expressions. ConstantSQLDescriptionAggSumSUM()Sum of valuesAggAvgAVG()Average of valuesAggMinMIN()Minimum valueAggMaxMAX()Maximum valueAggCountFieldCOUNT()Count of valuesAggCountDistinctCOUNT(DISTINCT)Count of unique values",{"id":1664,"title":633,"titles":1665,"content":1666,"level":19},"/v1.0.10/reference/operators#window-functions",[1559],"For use with window expressions. ConstantSQLDescriptionWinRowNumberROW_NUMBER()Sequential row numberWinRankRANK()Rank with gapsWinDenseRankDENSE_RANK()Rank without gapsWinNtileNTILE(:param)Divide into n bucketsWinLagLAG()Previous row valueWinLeadLEAD()Next row valueWinFirstValueFIRST_VALUE()First value in windowWinLastValueLAST_VALUE()Last value in window",{"id":1668,"title":658,"titles":1669,"content":1670,"level":19},"/v1.0.10/reference/operators#frame-bounds",[1559],"For window frame specifications. ConstantSQLFrameUnboundedPrecedingUNBOUNDED PRECEDINGFrameCurrentRowCURRENT ROWFrameUnboundedFollowingUNBOUNDED FOLLOWING",{"id":1672,"title":1673,"titles":1674,"content":1675,"level":19},"/v1.0.10/reference/operators#cast-types","Cast Types",[1559],"For type casting with Cast(). ConstantPostgreSQL TypeCastTextTEXTCastIntegerINTEGERCastBigintBIGINTCastSmallintSMALLINTCastNumericNUMERICCastRealREALCastDoublePrecisionDOUBLE PRECISIONCastBooleanBOOLEANCastDateDATECastTimeTIMECastTimestampTIMESTAMPCastTimestampTZTIMESTAMPTZCastIntervalINTERVALCastUUIDUUIDCastJSONJSONCastJSONBJSONBCastByteaBYTEA 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 .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 .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 .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}",[1677],{"title":1678,"path":1679,"stem":1680,"children":1681,"page":1695},"V1010","/v1.0.10","v1.0.10",[1682,1684,1696,1711,1724],{"title":6,"path":5,"stem":1683,"description":8},"v1.0.10/1.overview",{"title":1685,"path":1686,"stem":1687,"children":1688,"page":1695},"Learn","/v1.0.10/learn","v1.0.10/2.learn",[1689,1691,1693],{"title":53,"path":52,"stem":1690,"description":55},"v1.0.10/2.learn/1.quickstart",{"title":86,"path":85,"stem":1692,"description":88},"v1.0.10/2.learn/2.concepts",{"title":16,"path":179,"stem":1694,"description":181},"v1.0.10/2.learn/3.architecture",false,{"title":1697,"path":1698,"stem":1699,"children":1700,"page":1695},"Guides","/v1.0.10/guides","v1.0.10/3.guides",[1701,1703,1705,1707,1709],{"title":197,"path":295,"stem":1702,"description":297},"v1.0.10/3.guides/1.schema-validation",{"title":120,"path":400,"stem":1704,"description":402},"v1.0.10/3.guides/2.conditions",{"title":507,"path":506,"stem":1706,"description":509},"v1.0.10/3.guides/3.joins",{"title":575,"path":574,"stem":1708,"description":577},"v1.0.10/3.guides/4.aggregates",{"title":692,"path":691,"stem":1710,"description":694},"v1.0.10/3.guides/5.testing",{"title":1712,"path":1713,"stem":1714,"children":1715,"page":1695},"Cookbook","/v1.0.10/cookbook","v1.0.10/4.cookbook",[1716,1718,1720,1722],{"title":790,"path":789,"stem":1717,"description":792},"v1.0.10/4.cookbook/1.pagination",{"title":870,"path":869,"stem":1719,"description":872},"v1.0.10/4.cookbook/2.vector-search",{"title":957,"path":956,"stem":1721,"description":959},"v1.0.10/4.cookbook/3.upserts",{"title":1029,"path":1028,"stem":1723,"description":1031},"v1.0.10/4.cookbook/4.orm-foundation",{"title":1725,"path":1726,"stem":1727,"children":1728,"page":1695},"Reference","/v1.0.10/reference","v1.0.10/5.reference",[1729,1731],{"title":1113,"path":1112,"stem":1730,"description":1115},"v1.0.10/5.reference/1.api",{"title":1559,"path":1558,"stem":1732,"description":1561},"v1.0.10/5.reference/2.operators",[1734],{"title":1678,"path":1679,"stem":1680,"children":1735,"page":1695},[1736,1737,1742,1749,1755],{"title":6,"path":5,"stem":1683},{"title":1685,"path":1686,"stem":1687,"children":1738,"page":1695},[1739,1740,1741],{"title":53,"path":52,"stem":1690},{"title":86,"path":85,"stem":1692},{"title":16,"path":179,"stem":1694},{"title":1697,"path":1698,"stem":1699,"children":1743,"page":1695},[1744,1745,1746,1747,1748],{"title":197,"path":295,"stem":1702},{"title":120,"path":400,"stem":1704},{"title":507,"path":506,"stem":1706},{"title":575,"path":574,"stem":1708},{"title":692,"path":691,"stem":1710},{"title":1712,"path":1713,"stem":1714,"children":1750,"page":1695},[1751,1752,1753,1754],{"title":790,"path":789,"stem":1717},{"title":870,"path":869,"stem":1719},{"title":957,"path":956,"stem":1721},{"title":1029,"path":1028,"stem":1723},{"title":1725,"path":1726,"stem":1727,"children":1756,"page":1695},[1757,1758],{"title":1113,"path":1112,"stem":1730},{"title":1559,"path":1558,"stem":1732},[1760,3504,3861],{"id":1761,"extension":1762,"icon":1763,"meta":1764,"stem":3502,"__hash__":3503},"resources/readme.md","md","book-open",{"title":1765,"body":1766},"README",{"type":1767,"value":1768,"toc":3486},"minimark",[1769,1773,1841,1844,1847,1852,2119,2122,2125,2289,2292,2296,2322,2325,2329,2932,2935,3022,3026,3063,3067,3073,3076,3101,3342,3345,3349,3357,3361,3381,3384,3413,3416,3442,3445,3458,3462,3474,3477,3482],[1770,1771,1772],"h1",{"id":1772},"astql",[1774,1775,1776,1787,1795,1803,1811,1819,1826,1833],"p",{},[1777,1778,1782],"a",{"href":1779,"rel":1780},"https://github.com/zoobz-io/astql/actions/workflows/ci.yml",[1781],"nofollow",[1783,1784],"img",{"alt":1785,"src":1786},"CI","https://github.com/zoobz-io/astql/actions/workflows/ci.yml/badge.svg",[1777,1788,1791],{"href":1789,"rel":1790},"https://codecov.io/gh/zoobz-io/astql",[1781],[1783,1792],{"alt":1793,"src":1794},"Coverage","https://codecov.io/gh/zoobz-io/astql/branch/main/graph/badge.svg",[1777,1796,1799],{"href":1797,"rel":1798},"https://goreportcard.com/report/github.com/zoobz-io/astql",[1781],[1783,1800],{"alt":1801,"src":1802},"Go Report Card","https://goreportcard.com/badge/github.com/zoobz-io/astql",[1777,1804,1807],{"href":1805,"rel":1806},"https://github.com/zoobz-io/astql/actions/workflows/codeql.yml",[1781],[1783,1808],{"alt":1809,"src":1810},"CodeQL","https://github.com/zoobz-io/astql/actions/workflows/codeql.yml/badge.svg",[1777,1812,1815],{"href":1813,"rel":1814},"https://pkg.go.dev/github.com/zoobz-io/astql",[1781],[1783,1816],{"alt":1817,"src":1818},"Go Reference","https://pkg.go.dev/badge/github.com/zoobz-io/astql.svg",[1777,1820,1822],{"href":1821},"LICENSE",[1783,1823],{"alt":1824,"src":1825},"License","https://img.shields.io/github/license/zoobz-io/astql",[1777,1827,1829],{"href":1828},"go.mod",[1783,1830],{"alt":1831,"src":1832},"Go Version","https://img.shields.io/github/go-mod/go-version/zoobz-io/astql",[1777,1834,1837],{"href":1835,"rel":1836},"https://github.com/zoobz-io/astql/releases",[1781],[1783,1838],{"alt":1839,"src":1840},"Release","https://img.shields.io/github/v/release/zoobz-io/astql",[1774,1842,1843],{},"Type-safe SQL query builder with DBML schema validation.",[1774,1845,1846],{},"Build queries as an AST, validate against your schema, render to parameterized SQL.",[1848,1849,1851],"h2",{"id":1850},"injection-safe-sql-expressions-any-dialect","Injection-safe SQL Expressions, Any Dialect",[1853,1854,1858],"pre",{"className":1855,"code":1856,"language":1857,"meta":34,"style":34},"language-go shiki shiki-themes","instance.T(\"users\")      // ✓ exists in schema\ninstance.T(\"uusers\")     // panic: table \"uusers\" not found\n\ninstance.F(\"email\")      // ✓ exists in schema\ninstance.F(\"emial\")      // panic: field \"emial\" not found\n\nquery := astql.Select(instance.T(\"users\")).\n    Fields(instance.F(\"username\"), instance.F(\"email\")).\n    Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\")))\n\nresult, _ := query.Render(postgres.New())\n// SELECT \"username\", \"email\" FROM \"users\" WHERE \"active\" = :is_active\n","go",[1859,1860,1861,1890,1908,1914,1932,1951,1956,1987,2022,2075,2080,2113],"code",{"__ignoreMap":34},[1862,1863,1865,1869,1873,1876,1879,1883,1886],"span",{"class":1864,"line":9},"line",[1862,1866,1868],{"class":1867},"sh8_p","instance",[1862,1870,1872],{"class":1871},"sq5bi",".",[1862,1874,1135],{"class":1875},"s5klm",[1862,1877,1878],{"class":1871},"(",[1862,1880,1882],{"class":1881},"sxAnc","\"users\"",[1862,1884,1885],{"class":1871},")",[1862,1887,1889],{"class":1888},"sLkEo","      // ✓ exists in schema\n",[1862,1891,1892,1894,1896,1898,1900,1903,1905],{"class":1864,"line":19},[1862,1893,1868],{"class":1867},[1862,1895,1872],{"class":1871},[1862,1897,1135],{"class":1875},[1862,1899,1878],{"class":1871},[1862,1901,1902],{"class":1881},"\"uusers\"",[1862,1904,1885],{"class":1871},[1862,1906,1907],{"class":1888},"     // panic: table \"uusers\" not found\n",[1862,1909,1910],{"class":1864,"line":40},[1862,1911,1913],{"emptyLinePlaceholder":1912},true,"\n",[1862,1915,1917,1919,1921,1923,1925,1928,1930],{"class":1864,"line":1916},4,[1862,1918,1868],{"class":1867},[1862,1920,1872],{"class":1871},[1862,1922,1145],{"class":1875},[1862,1924,1878],{"class":1871},[1862,1926,1927],{"class":1881},"\"email\"",[1862,1929,1885],{"class":1871},[1862,1931,1889],{"class":1888},[1862,1933,1935,1937,1939,1941,1943,1946,1948],{"class":1864,"line":1934},5,[1862,1936,1868],{"class":1867},[1862,1938,1872],{"class":1871},[1862,1940,1145],{"class":1875},[1862,1942,1878],{"class":1871},[1862,1944,1945],{"class":1881},"\"emial\"",[1862,1947,1885],{"class":1871},[1862,1949,1950],{"class":1888},"      // panic: field \"emial\" not found\n",[1862,1952,1954],{"class":1864,"line":1953},6,[1862,1955,1913],{"emptyLinePlaceholder":1912},[1862,1957,1959,1962,1965,1968,1970,1972,1974,1976,1978,1980,1982,1984],{"class":1864,"line":1958},7,[1862,1960,1961],{"class":1867},"query",[1862,1963,1964],{"class":1867}," :=",[1862,1966,1967],{"class":1867}," astql",[1862,1969,1872],{"class":1871},[1862,1971,1234],{"class":1875},[1862,1973,1878],{"class":1871},[1862,1975,1868],{"class":1867},[1862,1977,1872],{"class":1871},[1862,1979,1135],{"class":1875},[1862,1981,1878],{"class":1871},[1862,1983,1882],{"class":1881},[1862,1985,1986],{"class":1871},")).\n",[1862,1988,1990,1993,1995,1997,1999,2001,2003,2006,2009,2012,2014,2016,2018,2020],{"class":1864,"line":1989},8,[1862,1991,1992],{"class":1875},"    Fields",[1862,1994,1878],{"class":1871},[1862,1996,1868],{"class":1867},[1862,1998,1872],{"class":1871},[1862,2000,1145],{"class":1875},[1862,2002,1878],{"class":1871},[1862,2004,2005],{"class":1881},"\"username\"",[1862,2007,2008],{"class":1871},"),",[1862,2010,2011],{"class":1867}," instance",[1862,2013,1872],{"class":1871},[1862,2015,1145],{"class":1875},[1862,2017,1878],{"class":1871},[1862,2019,1927],{"class":1881},[1862,2021,1986],{"class":1871},[1862,2023,2025,2028,2030,2032,2034,2036,2038,2040,2042,2044,2046,2049,2051,2053,2055,2058,2061,2063,2065,2067,2069,2072],{"class":1864,"line":2024},9,[1862,2026,2027],{"class":1875},"    Where",[1862,2029,1878],{"class":1871},[1862,2031,1868],{"class":1867},[1862,2033,1872],{"class":1871},[1862,2035,1165],{"class":1875},[1862,2037,1878],{"class":1871},[1862,2039,1868],{"class":1867},[1862,2041,1872],{"class":1871},[1862,2043,1145],{"class":1875},[1862,2045,1878],{"class":1871},[1862,2047,2048],{"class":1881},"\"active\"",[1862,2050,2008],{"class":1871},[1862,2052,1967],{"class":1867},[1862,2054,1872],{"class":1871},[1862,2056,2057],{"class":1867},"EQ",[1862,2059,2060],{"class":1871},",",[1862,2062,2011],{"class":1867},[1862,2064,1872],{"class":1871},[1862,2066,1155],{"class":1875},[1862,2068,1878],{"class":1871},[1862,2070,2071],{"class":1881},"\"is_active\"",[1862,2073,2074],{"class":1871},")))\n",[1862,2076,2078],{"class":1864,"line":2077},10,[1862,2079,1913],{"emptyLinePlaceholder":1912},[1862,2081,2083,2086,2088,2091,2093,2096,2098,2100,2102,2105,2107,2110],{"class":1864,"line":2082},11,[1862,2084,2085],{"class":1867},"result",[1862,2087,2060],{"class":1871},[1862,2089,2090],{"class":1867}," _",[1862,2092,1964],{"class":1867},[1862,2094,2095],{"class":1867}," query",[1862,2097,1872],{"class":1871},[1862,2099,1392],{"class":1875},[1862,2101,1878],{"class":1871},[1862,2103,2104],{"class":1867},"postgres",[1862,2106,1872],{"class":1871},[1862,2108,2109],{"class":1875},"New",[1862,2111,2112],{"class":1871},"())\n",[1862,2114,2116],{"class":1864,"line":2115},12,[1862,2117,2118],{"class":1888},"// SELECT \"username\", \"email\" FROM \"users\" WHERE \"active\" = :is_active\n",[1774,2120,2121],{},"Typos become compile-time failures, not runtime surprises. Values are parameterized. Identifiers are quoted. The schema is the source of truth.",[1774,2123,2124],{},"Same query, different databases:",[1853,2126,2128],{"className":1855,"code":2127,"language":1857,"meta":34,"style":34},"import (\n    \"github.com/zoobz-io/astql/postgres\"\n    \"github.com/zoobz-io/astql/sqlite\"\n    \"github.com/zoobz-io/astql/mariadb\"\n    \"github.com/zoobz-io/astql/mssql\"\n)\n\nresult, _ := query.Render(postgres.New())  // \"username\", LIMIT 10\nresult, _ := query.Render(sqlite.New())    // \"username\", LIMIT 10\nresult, _ := query.Render(mariadb.New())   // `username`, LIMIT 10\nresult, _ := query.Render(mssql.New())     // [username], TOP 10\n",[1859,2129,2130,2140,2145,2150,2155,2160,2165,2169,2199,2229,2259],{"__ignoreMap":34},[1862,2131,2132,2136],{"class":1864,"line":9},[1862,2133,2135],{"class":2134},"sUt3r","import",[1862,2137,2139],{"class":2138},"soy-K"," (\n",[1862,2141,2142],{"class":1864,"line":19},[1862,2143,2144],{"class":1881},"    \"github.com/zoobz-io/astql/postgres\"\n",[1862,2146,2147],{"class":1864,"line":40},[1862,2148,2149],{"class":1881},"    \"github.com/zoobz-io/astql/sqlite\"\n",[1862,2151,2152],{"class":1864,"line":1916},[1862,2153,2154],{"class":1881},"    \"github.com/zoobz-io/astql/mariadb\"\n",[1862,2156,2157],{"class":1864,"line":1934},[1862,2158,2159],{"class":1881},"    \"github.com/zoobz-io/astql/mssql\"\n",[1862,2161,2162],{"class":1864,"line":1953},[1862,2163,2164],{"class":2138},")\n",[1862,2166,2167],{"class":1864,"line":1958},[1862,2168,1913],{"emptyLinePlaceholder":1912},[1862,2170,2171,2173,2175,2177,2179,2181,2183,2185,2187,2189,2191,2193,2196],{"class":1864,"line":1989},[1862,2172,2085],{"class":1867},[1862,2174,2060],{"class":1871},[1862,2176,2090],{"class":1867},[1862,2178,1964],{"class":1867},[1862,2180,2095],{"class":1867},[1862,2182,1872],{"class":1871},[1862,2184,1392],{"class":1875},[1862,2186,1878],{"class":1871},[1862,2188,2104],{"class":1867},[1862,2190,1872],{"class":1871},[1862,2192,2109],{"class":1875},[1862,2194,2195],{"class":1871},"())",[1862,2197,2198],{"class":1888},"  // \"username\", LIMIT 10\n",[1862,2200,2201,2203,2205,2207,2209,2211,2213,2215,2217,2220,2222,2224,2226],{"class":1864,"line":2024},[1862,2202,2085],{"class":1867},[1862,2204,2060],{"class":1871},[1862,2206,2090],{"class":1867},[1862,2208,1964],{"class":1867},[1862,2210,2095],{"class":1867},[1862,2212,1872],{"class":1871},[1862,2214,1392],{"class":1875},[1862,2216,1878],{"class":1871},[1862,2218,2219],{"class":1867},"sqlite",[1862,2221,1872],{"class":1871},[1862,2223,2109],{"class":1875},[1862,2225,2195],{"class":1871},[1862,2227,2228],{"class":1888},"    // \"username\", LIMIT 10\n",[1862,2230,2231,2233,2235,2237,2239,2241,2243,2245,2247,2250,2252,2254,2256],{"class":1864,"line":2077},[1862,2232,2085],{"class":1867},[1862,2234,2060],{"class":1871},[1862,2236,2090],{"class":1867},[1862,2238,1964],{"class":1867},[1862,2240,2095],{"class":1867},[1862,2242,1872],{"class":1871},[1862,2244,1392],{"class":1875},[1862,2246,1878],{"class":1871},[1862,2248,2249],{"class":1867},"mariadb",[1862,2251,1872],{"class":1871},[1862,2253,2109],{"class":1875},[1862,2255,2195],{"class":1871},[1862,2257,2258],{"class":1888},"   // `username`, LIMIT 10\n",[1862,2260,2261,2263,2265,2267,2269,2271,2273,2275,2277,2280,2282,2284,2286],{"class":1864,"line":2082},[1862,2262,2085],{"class":1867},[1862,2264,2060],{"class":1871},[1862,2266,2090],{"class":1867},[1862,2268,1964],{"class":1867},[1862,2270,2095],{"class":1867},[1862,2272,1872],{"class":1871},[1862,2274,1392],{"class":1875},[1862,2276,1878],{"class":1871},[1862,2278,2279],{"class":1867},"mssql",[1862,2281,1872],{"class":1871},[1862,2283,2109],{"class":1875},[1862,2285,2195],{"class":1871},[1862,2287,2288],{"class":1888},"     // [username], TOP 10\n",[1774,2290,2291],{},"One AST. Four dialects. Each renderer handles identifier quoting, pagination syntax, vendor-specific operators.",[1848,2293,2295],{"id":2294},"install","Install",[1853,2297,2301],{"className":2298,"code":2299,"language":2300,"meta":34,"style":34},"language-bash shiki shiki-themes","go get github.com/zoobz-io/astql\ngo get github.com/zoobz-io/dbml\n","bash",[1859,2302,2303,2313],{"__ignoreMap":34},[1862,2304,2305,2307,2310],{"class":1864,"line":9},[1862,2306,1857],{"class":1875},[1862,2308,2309],{"class":1881}," get",[1862,2311,2312],{"class":1881}," github.com/zoobz-io/astql\n",[1862,2314,2315,2317,2319],{"class":1864,"line":19},[1862,2316,1857],{"class":1875},[1862,2318,2309],{"class":1881},[1862,2320,2321],{"class":1881}," github.com/zoobz-io/dbml\n",[1774,2323,2324],{},"Requires Go 1.24+.",[1848,2326,2328],{"id":2327},"quick-start","Quick Start",[1853,2330,2332],{"className":1855,"code":2331,"language":1857,"meta":34,"style":34},"package main\n\nimport (\n    \"fmt\"\n    \"github.com/zoobz-io/astql\"\n    \"github.com/zoobz-io/astql/postgres\"\n    \"github.com/zoobz-io/dbml\"\n)\n\nfunc main() {\n    // Define schema\n    project := dbml.NewProject(\"myapp\")\n    users := dbml.NewTable(\"users\")\n    users.AddColumn(dbml.NewColumn(\"id\", \"bigint\"))\n    users.AddColumn(dbml.NewColumn(\"username\", \"varchar\"))\n    users.AddColumn(dbml.NewColumn(\"email\", \"varchar\"))\n    users.AddColumn(dbml.NewColumn(\"active\", \"boolean\"))\n    project.AddTable(users)\n\n    // Create instance\n    instance, err := astql.NewFromDBML(project)\n    if err != nil {\n        panic(err)\n    }\n\n    // Build and render\n    result, err := astql.Select(instance.T(\"users\")).\n        Fields(instance.F(\"username\"), instance.F(\"email\")).\n        Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"is_active\"))).\n        OrderBy(instance.F(\"username\"), astql.ASC).\n        Limit(10).\n        Render(postgres.New())\n\n    if err != nil {\n        panic(err)\n    }\n\n    fmt.Println(result.SQL)\n    // SELECT \"username\", \"email\" FROM \"users\" WHERE \"active\" = :is_active ORDER BY \"username\" ASC LIMIT 10\n    fmt.Println(result.RequiredParams)\n    // [is_active]\n}\n",[1859,2333,2334,2343,2347,2353,2358,2363,2367,2372,2376,2380,2394,2399,2421,2442,2475,2503,2530,2558,2575,2580,2586,2612,2629,2643,2649,2654,2660,2692,2724,2773,2803,2817,2833,2838,2851,2862,2867,2872,2894,2900,2920,2926],{"__ignoreMap":34},[1862,2335,2336,2339],{"class":1864,"line":9},[1862,2337,2338],{"class":2134},"package",[1862,2340,2342],{"class":2341},"sYBwO"," main\n",[1862,2344,2345],{"class":1864,"line":19},[1862,2346,1913],{"emptyLinePlaceholder":1912},[1862,2348,2349,2351],{"class":1864,"line":40},[1862,2350,2135],{"class":2134},[1862,2352,2139],{"class":2138},[1862,2354,2355],{"class":1864,"line":1916},[1862,2356,2357],{"class":1881},"    \"fmt\"\n",[1862,2359,2360],{"class":1864,"line":1934},[1862,2361,2362],{"class":1881},"    \"github.com/zoobz-io/astql\"\n",[1862,2364,2365],{"class":1864,"line":1953},[1862,2366,2144],{"class":1881},[1862,2368,2369],{"class":1864,"line":1958},[1862,2370,2371],{"class":1881},"    \"github.com/zoobz-io/dbml\"\n",[1862,2373,2374],{"class":1864,"line":1989},[1862,2375,2164],{"class":2138},[1862,2377,2378],{"class":1864,"line":2024},[1862,2379,1913],{"emptyLinePlaceholder":1912},[1862,2381,2382,2385,2388,2391],{"class":1864,"line":2077},[1862,2383,2384],{"class":2134},"func",[1862,2386,2387],{"class":1875}," main",[1862,2389,2390],{"class":1871},"()",[1862,2392,2393],{"class":1871}," {\n",[1862,2395,2396],{"class":1864,"line":2082},[1862,2397,2398],{"class":1888},"    // Define schema\n",[1862,2400,2401,2404,2406,2409,2411,2414,2416,2419],{"class":1864,"line":2115},[1862,2402,2403],{"class":1867},"    project",[1862,2405,1964],{"class":1867},[1862,2407,2408],{"class":1867}," dbml",[1862,2410,1872],{"class":1871},[1862,2412,2413],{"class":1875},"NewProject",[1862,2415,1878],{"class":1871},[1862,2417,2418],{"class":1881},"\"myapp\"",[1862,2420,2164],{"class":1871},[1862,2422,2424,2427,2429,2431,2433,2436,2438,2440],{"class":1864,"line":2423},13,[1862,2425,2426],{"class":1867},"    users",[1862,2428,1964],{"class":1867},[1862,2430,2408],{"class":1867},[1862,2432,1872],{"class":1871},[1862,2434,2435],{"class":1875},"NewTable",[1862,2437,1878],{"class":1871},[1862,2439,1882],{"class":1881},[1862,2441,2164],{"class":1871},[1862,2443,2445,2447,2449,2452,2454,2457,2459,2462,2464,2467,2469,2472],{"class":1864,"line":2444},14,[1862,2446,2426],{"class":1867},[1862,2448,1872],{"class":1871},[1862,2450,2451],{"class":1875},"AddColumn",[1862,2453,1878],{"class":1871},[1862,2455,2456],{"class":1867},"dbml",[1862,2458,1872],{"class":1871},[1862,2460,2461],{"class":1875},"NewColumn",[1862,2463,1878],{"class":1871},[1862,2465,2466],{"class":1881},"\"id\"",[1862,2468,2060],{"class":1871},[1862,2470,2471],{"class":1881}," \"bigint\"",[1862,2473,2474],{"class":1871},"))\n",[1862,2476,2478,2480,2482,2484,2486,2488,2490,2492,2494,2496,2498,2501],{"class":1864,"line":2477},15,[1862,2479,2426],{"class":1867},[1862,2481,1872],{"class":1871},[1862,2483,2451],{"class":1875},[1862,2485,1878],{"class":1871},[1862,2487,2456],{"class":1867},[1862,2489,1872],{"class":1871},[1862,2491,2461],{"class":1875},[1862,2493,1878],{"class":1871},[1862,2495,2005],{"class":1881},[1862,2497,2060],{"class":1871},[1862,2499,2500],{"class":1881}," \"varchar\"",[1862,2502,2474],{"class":1871},[1862,2504,2506,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528],{"class":1864,"line":2505},16,[1862,2507,2426],{"class":1867},[1862,2509,1872],{"class":1871},[1862,2511,2451],{"class":1875},[1862,2513,1878],{"class":1871},[1862,2515,2456],{"class":1867},[1862,2517,1872],{"class":1871},[1862,2519,2461],{"class":1875},[1862,2521,1878],{"class":1871},[1862,2523,1927],{"class":1881},[1862,2525,2060],{"class":1871},[1862,2527,2500],{"class":1881},[1862,2529,2474],{"class":1871},[1862,2531,2533,2535,2537,2539,2541,2543,2545,2547,2549,2551,2553,2556],{"class":1864,"line":2532},17,[1862,2534,2426],{"class":1867},[1862,2536,1872],{"class":1871},[1862,2538,2451],{"class":1875},[1862,2540,1878],{"class":1871},[1862,2542,2456],{"class":1867},[1862,2544,1872],{"class":1871},[1862,2546,2461],{"class":1875},[1862,2548,1878],{"class":1871},[1862,2550,2048],{"class":1881},[1862,2552,2060],{"class":1871},[1862,2554,2555],{"class":1881}," \"boolean\"",[1862,2557,2474],{"class":1871},[1862,2559,2561,2563,2565,2568,2570,2573],{"class":1864,"line":2560},18,[1862,2562,2403],{"class":1867},[1862,2564,1872],{"class":1871},[1862,2566,2567],{"class":1875},"AddTable",[1862,2569,1878],{"class":1871},[1862,2571,2572],{"class":1867},"users",[1862,2574,2164],{"class":1871},[1862,2576,2578],{"class":1864,"line":2577},19,[1862,2579,1913],{"emptyLinePlaceholder":1912},[1862,2581,2583],{"class":1864,"line":2582},20,[1862,2584,2585],{"class":1888},"    // Create instance\n",[1862,2587,2589,2592,2594,2597,2599,2601,2603,2605,2607,2610],{"class":1864,"line":2588},21,[1862,2590,2591],{"class":1867},"    instance",[1862,2593,2060],{"class":1871},[1862,2595,2596],{"class":1867}," err",[1862,2598,1964],{"class":1867},[1862,2600,1967],{"class":1867},[1862,2602,1872],{"class":1871},[1862,2604,1126],{"class":1875},[1862,2606,1878],{"class":1871},[1862,2608,2609],{"class":1867},"project",[1862,2611,2164],{"class":1871},[1862,2613,2615,2619,2621,2624,2627],{"class":1864,"line":2614},22,[1862,2616,2618],{"class":2617},"sW3Qg","    if",[1862,2620,2596],{"class":1867},[1862,2622,2623],{"class":2617}," !=",[1862,2625,2626],{"class":2134}," nil",[1862,2628,2393],{"class":1871},[1862,2630,2632,2636,2638,2641],{"class":1864,"line":2631},23,[1862,2633,2635],{"class":2634},"skxcq","        panic",[1862,2637,1878],{"class":1871},[1862,2639,2640],{"class":1867},"err",[1862,2642,2164],{"class":1871},[1862,2644,2646],{"class":1864,"line":2645},24,[1862,2647,2648],{"class":1871},"    }\n",[1862,2650,2652],{"class":1864,"line":2651},25,[1862,2653,1913],{"emptyLinePlaceholder":1912},[1862,2655,2657],{"class":1864,"line":2656},26,[1862,2658,2659],{"class":1888},"    // Build and render\n",[1862,2661,2663,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690],{"class":1864,"line":2662},27,[1862,2664,2665],{"class":1867},"    result",[1862,2667,2060],{"class":1871},[1862,2669,2596],{"class":1867},[1862,2671,1964],{"class":1867},[1862,2673,1967],{"class":1867},[1862,2675,1872],{"class":1871},[1862,2677,1234],{"class":1875},[1862,2679,1878],{"class":1871},[1862,2681,1868],{"class":1867},[1862,2683,1872],{"class":1871},[1862,2685,1135],{"class":1875},[1862,2687,1878],{"class":1871},[1862,2689,1882],{"class":1881},[1862,2691,1986],{"class":1871},[1862,2693,2695,2698,2700,2702,2704,2706,2708,2710,2712,2714,2716,2718,2720,2722],{"class":1864,"line":2694},28,[1862,2696,2697],{"class":1875},"        Fields",[1862,2699,1878],{"class":1871},[1862,2701,1868],{"class":1867},[1862,2703,1872],{"class":1871},[1862,2705,1145],{"class":1875},[1862,2707,1878],{"class":1871},[1862,2709,2005],{"class":1881},[1862,2711,2008],{"class":1871},[1862,2713,2011],{"class":1867},[1862,2715,1872],{"class":1871},[1862,2717,1145],{"class":1875},[1862,2719,1878],{"class":1871},[1862,2721,1927],{"class":1881},[1862,2723,1986],{"class":1871},[1862,2725,2727,2730,2732,2734,2736,2738,2740,2742,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770],{"class":1864,"line":2726},29,[1862,2728,2729],{"class":1875},"        Where",[1862,2731,1878],{"class":1871},[1862,2733,1868],{"class":1867},[1862,2735,1872],{"class":1871},[1862,2737,1165],{"class":1875},[1862,2739,1878],{"class":1871},[1862,2741,1868],{"class":1867},[1862,2743,1872],{"class":1871},[1862,2745,1145],{"class":1875},[1862,2747,1878],{"class":1871},[1862,2749,2048],{"class":1881},[1862,2751,2008],{"class":1871},[1862,2753,1967],{"class":1867},[1862,2755,1872],{"class":1871},[1862,2757,2057],{"class":1867},[1862,2759,2060],{"class":1871},[1862,2761,2011],{"class":1867},[1862,2763,1872],{"class":1871},[1862,2765,1155],{"class":1875},[1862,2767,1878],{"class":1871},[1862,2769,2071],{"class":1881},[1862,2771,2772],{"class":1871},"))).\n",[1862,2774,2776,2779,2781,2783,2785,2787,2789,2791,2793,2795,2797,2800],{"class":1864,"line":2775},30,[1862,2777,2778],{"class":1875},"        OrderBy",[1862,2780,1878],{"class":1871},[1862,2782,1868],{"class":1867},[1862,2784,1872],{"class":1871},[1862,2786,1145],{"class":1875},[1862,2788,1878],{"class":1871},[1862,2790,2005],{"class":1881},[1862,2792,2008],{"class":1871},[1862,2794,1967],{"class":1867},[1862,2796,1872],{"class":1871},[1862,2798,2799],{"class":1867},"ASC",[1862,2801,2802],{"class":1871},").\n",[1862,2804,2806,2809,2811,2815],{"class":1864,"line":2805},31,[1862,2807,2808],{"class":1875},"        Limit",[1862,2810,1878],{"class":1871},[1862,2812,2814],{"class":2813},"sMAmT","10",[1862,2816,2802],{"class":1871},[1862,2818,2820,2823,2825,2827,2829,2831],{"class":1864,"line":2819},32,[1862,2821,2822],{"class":1875},"        Render",[1862,2824,1878],{"class":1871},[1862,2826,2104],{"class":1867},[1862,2828,1872],{"class":1871},[1862,2830,2109],{"class":1875},[1862,2832,2112],{"class":1871},[1862,2834,2836],{"class":1864,"line":2835},33,[1862,2837,1913],{"emptyLinePlaceholder":1912},[1862,2839,2841,2843,2845,2847,2849],{"class":1864,"line":2840},34,[1862,2842,2618],{"class":2617},[1862,2844,2596],{"class":1867},[1862,2846,2623],{"class":2617},[1862,2848,2626],{"class":2134},[1862,2850,2393],{"class":1871},[1862,2852,2854,2856,2858,2860],{"class":1864,"line":2853},35,[1862,2855,2635],{"class":2634},[1862,2857,1878],{"class":1871},[1862,2859,2640],{"class":1867},[1862,2861,2164],{"class":1871},[1862,2863,2865],{"class":1864,"line":2864},36,[1862,2866,2648],{"class":1871},[1862,2868,2870],{"class":1864,"line":2869},37,[1862,2871,1913],{"emptyLinePlaceholder":1912},[1862,2873,2875,2878,2880,2883,2885,2887,2889,2892],{"class":1864,"line":2874},38,[1862,2876,2877],{"class":1867},"    fmt",[1862,2879,1872],{"class":1871},[1862,2881,2882],{"class":1875},"Println",[1862,2884,1878],{"class":1871},[1862,2886,2085],{"class":1867},[1862,2888,1872],{"class":1871},[1862,2890,2891],{"class":1867},"SQL",[1862,2893,2164],{"class":1871},[1862,2895,2897],{"class":1864,"line":2896},39,[1862,2898,2899],{"class":1888},"    // SELECT \"username\", \"email\" FROM \"users\" WHERE \"active\" = :is_active ORDER BY \"username\" ASC LIMIT 10\n",[1862,2901,2903,2905,2907,2909,2911,2913,2915,2918],{"class":1864,"line":2902},40,[1862,2904,2877],{"class":1867},[1862,2906,1872],{"class":1871},[1862,2908,2882],{"class":1875},[1862,2910,1878],{"class":1871},[1862,2912,2085],{"class":1867},[1862,2914,1872],{"class":1871},[1862,2916,2917],{"class":1867},"RequiredParams",[1862,2919,2164],{"class":1871},[1862,2921,2923],{"class":1864,"line":2922},41,[1862,2924,2925],{"class":1888},"    // [is_active]\n",[1862,2927,2929],{"class":1864,"line":2928},42,[1862,2930,2931],{"class":1871},"}\n",[1848,2933,27],{"id":2934},"capabilities",[2936,2937,2938,2954],"table",{},[2939,2940,2941],"thead",{},[2942,2943,2944,2948,2951],"tr",{},[2945,2946,2947],"th",{},"Feature",[2945,2949,2950],{},"Description",[2945,2952,2953],{},"Docs",[2955,2956,2957,2970,2983,2996,3009],"tbody",{},[2942,2958,2959,2962,2965],{},[2960,2961,197],"td",{},[2960,2963,2964],{},"Tables and fields checked against DBML at build time",[2960,2966,2967],{},[1777,2968,197],{"href":2969},"docs/guides/schema-validation",[2942,2971,2972,2975,2978],{},[2960,2973,2974],{},"Multi-Dialect",[2960,2976,2977],{},"PostgreSQL, SQLite, MariaDB, MSSQL from one AST",[2960,2979,2980],{},[1777,2981,16],{"href":2982},"docs/learn/architecture",[2942,2984,2985,2988,2991],{},[2960,2986,2987],{},"Parameterized Values",[2960,2989,2990],{},"Injection-resistant queries with named parameters",[2960,2992,2993],{},[1777,2994,120],{"href":2995},"docs/guides/conditions",[2942,2997,2998,3001,3004],{},[2960,2999,3000],{},"Composable Queries",[2960,3002,3003],{},"Subqueries, JOINs, aggregates, window functions",[2960,3005,3006],{},[1777,3007,507],{"href":3008},"docs/guides/joins",[2942,3010,3011,3013,3016],{},[2960,3012,677],{},[2960,3014,3015],{},"Conditional logic within queries",[2960,3017,3018],{},[1777,3019,3021],{"href":3020},"docs/reference/api","API",[1848,3023,3025],{"id":3024},"why-astql","Why ASTQL?",[3027,3028,3029,3045,3051,3057],"ul",{},[3030,3031,3032,3036,3037,3040,3041,3044],"li",{},[3033,3034,3035],"strong",{},"Schema-validated"," — ",[1859,3038,3039],{},"T(\"users\")"," and ",[1859,3042,3043],{},"F(\"email\")"," checked against DBML at build time",[3030,3046,3047,3050],{},[3033,3048,3049],{},"Injection-resistant"," — parameterized values, quoted identifiers, no string concatenation",[3030,3052,3053,3056],{},[3033,3054,3055],{},"Multi-dialect"," — one query, four databases",[3030,3058,3059,3062],{},[3033,3060,3061],{},"Composable"," — subqueries, JOINs, aggregates, window functions, CASE expressions",[1848,3064,3066],{"id":3065},"schema-first-data-access","Schema-First Data Access",[1774,3068,3069,3070,1872],{},"ASTQL enables a pattern: ",[3033,3071,3072],{},"define schema once in DBML, generate everything else",[1774,3074,3075],{},"Your DBML becomes the single source of truth. Downstream tools consume the schema to build:",[3027,3077,3078,3089,3095],{},[3030,3079,3080,3083,3084],{},[3033,3081,3082],{},"Type-safe repositories"," — generated data access layers with ",[1777,3085,3088],{"href":3086,"rel":3087},"https://github.com/zoobz-io/cereal",[1781],"cereal",[3030,3090,3091,3094],{},[3033,3092,3093],{},"Query builders"," — domain-specific methods that can't reference invalid columns",[3030,3096,3097,3100],{},[3033,3098,3099],{},"Multi-database applications"," — same business logic, swappable storage backends",[1853,3102,3104],{"className":1855,"code":3103,"language":1857,"meta":34,"style":34},"// Schema defines what's valid\nproject := dbml.ParseFile(\"schema.dbml\")\ninstance, _ := astql.NewFromDBML(project)\n\n// Queries are structurally correct by construction\nusers := instance.T(\"users\")\nquery := astql.Select(users).\n    Fields(instance.F(\"id\"), instance.F(\"email\")).\n    Where(instance.C(instance.F(\"active\"), astql.EQ, instance.P(\"active\")))\n\n// Render to any supported database\nsql, _ := query.Render(postgres.New())  // production\nsql, _ := query.Render(sqlite.New())    // testing\n",[1859,3105,3106,3111,3131,3153,3157,3162,3180,3198,3228,3274,3278,3283,3313],{"__ignoreMap":34},[1862,3107,3108],{"class":1864,"line":9},[1862,3109,3110],{"class":1888},"// Schema defines what's valid\n",[1862,3112,3113,3115,3117,3119,3121,3124,3126,3129],{"class":1864,"line":19},[1862,3114,2609],{"class":1867},[1862,3116,1964],{"class":1867},[1862,3118,2408],{"class":1867},[1862,3120,1872],{"class":1871},[1862,3122,3123],{"class":1875},"ParseFile",[1862,3125,1878],{"class":1871},[1862,3127,3128],{"class":1881},"\"schema.dbml\"",[1862,3130,2164],{"class":1871},[1862,3132,3133,3135,3137,3139,3141,3143,3145,3147,3149,3151],{"class":1864,"line":40},[1862,3134,1868],{"class":1867},[1862,3136,2060],{"class":1871},[1862,3138,2090],{"class":1867},[1862,3140,1964],{"class":1867},[1862,3142,1967],{"class":1867},[1862,3144,1872],{"class":1871},[1862,3146,1126],{"class":1875},[1862,3148,1878],{"class":1871},[1862,3150,2609],{"class":1867},[1862,3152,2164],{"class":1871},[1862,3154,3155],{"class":1864,"line":1916},[1862,3156,1913],{"emptyLinePlaceholder":1912},[1862,3158,3159],{"class":1864,"line":1934},[1862,3160,3161],{"class":1888},"// Queries are structurally correct by construction\n",[1862,3163,3164,3166,3168,3170,3172,3174,3176,3178],{"class":1864,"line":1953},[1862,3165,2572],{"class":1867},[1862,3167,1964],{"class":1867},[1862,3169,2011],{"class":1867},[1862,3171,1872],{"class":1871},[1862,3173,1135],{"class":1875},[1862,3175,1878],{"class":1871},[1862,3177,1882],{"class":1881},[1862,3179,2164],{"class":1871},[1862,3181,3182,3184,3186,3188,3190,3192,3194,3196],{"class":1864,"line":1958},[1862,3183,1961],{"class":1867},[1862,3185,1964],{"class":1867},[1862,3187,1967],{"class":1867},[1862,3189,1872],{"class":1871},[1862,3191,1234],{"class":1875},[1862,3193,1878],{"class":1871},[1862,3195,2572],{"class":1867},[1862,3197,2802],{"class":1871},[1862,3199,3200,3202,3204,3206,3208,3210,3212,3214,3216,3218,3220,3222,3224,3226],{"class":1864,"line":1989},[1862,3201,1992],{"class":1875},[1862,3203,1878],{"class":1871},[1862,3205,1868],{"class":1867},[1862,3207,1872],{"class":1871},[1862,3209,1145],{"class":1875},[1862,3211,1878],{"class":1871},[1862,3213,2466],{"class":1881},[1862,3215,2008],{"class":1871},[1862,3217,2011],{"class":1867},[1862,3219,1872],{"class":1871},[1862,3221,1145],{"class":1875},[1862,3223,1878],{"class":1871},[1862,3225,1927],{"class":1881},[1862,3227,1986],{"class":1871},[1862,3229,3230,3232,3234,3236,3238,3240,3242,3244,3246,3248,3250,3252,3254,3256,3258,3260,3262,3264,3266,3268,3270,3272],{"class":1864,"line":2024},[1862,3231,2027],{"class":1875},[1862,3233,1878],{"class":1871},[1862,3235,1868],{"class":1867},[1862,3237,1872],{"class":1871},[1862,3239,1165],{"class":1875},[1862,3241,1878],{"class":1871},[1862,3243,1868],{"class":1867},[1862,3245,1872],{"class":1871},[1862,3247,1145],{"class":1875},[1862,3249,1878],{"class":1871},[1862,3251,2048],{"class":1881},[1862,3253,2008],{"class":1871},[1862,3255,1967],{"class":1867},[1862,3257,1872],{"class":1871},[1862,3259,2057],{"class":1867},[1862,3261,2060],{"class":1871},[1862,3263,2011],{"class":1867},[1862,3265,1872],{"class":1871},[1862,3267,1155],{"class":1875},[1862,3269,1878],{"class":1871},[1862,3271,2048],{"class":1881},[1862,3273,2074],{"class":1871},[1862,3275,3276],{"class":1864,"line":2077},[1862,3277,1913],{"emptyLinePlaceholder":1912},[1862,3279,3280],{"class":1864,"line":2082},[1862,3281,3282],{"class":1888},"// Render to any supported database\n",[1862,3284,3285,3288,3290,3292,3294,3296,3298,3300,3302,3304,3306,3308,3310],{"class":1864,"line":2115},[1862,3286,3287],{"class":1867},"sql",[1862,3289,2060],{"class":1871},[1862,3291,2090],{"class":1867},[1862,3293,1964],{"class":1867},[1862,3295,2095],{"class":1867},[1862,3297,1872],{"class":1871},[1862,3299,1392],{"class":1875},[1862,3301,1878],{"class":1871},[1862,3303,2104],{"class":1867},[1862,3305,1872],{"class":1871},[1862,3307,2109],{"class":1875},[1862,3309,2195],{"class":1871},[1862,3311,3312],{"class":1888},"  // production\n",[1862,3314,3315,3317,3319,3321,3323,3325,3327,3329,3331,3333,3335,3337,3339],{"class":1864,"line":2423},[1862,3316,3287],{"class":1867},[1862,3318,2060],{"class":1871},[1862,3320,2090],{"class":1867},[1862,3322,1964],{"class":1867},[1862,3324,2095],{"class":1867},[1862,3326,1872],{"class":1871},[1862,3328,1392],{"class":1875},[1862,3330,1878],{"class":1871},[1862,3332,2219],{"class":1867},[1862,3334,1872],{"class":1871},[1862,3336,2109],{"class":1875},[1862,3338,2195],{"class":1871},[1862,3340,3341],{"class":1888},"    // testing\n",[1774,3343,3344],{},"The schema guards the boundary. Queries inside the boundary are safe by construction.",[1848,3346,3348],{"id":3347},"documentation","Documentation",[3027,3350,3351],{},[3030,3352,3353,3356],{},[1777,3354,6],{"href":3355},"docs/overview"," — what astql does and why",[3358,3359,1685],"h3",{"id":3360},"learn",[3027,3362,3363,3369,3376],{},[3030,3364,3365,3368],{},[1777,3366,53],{"href":3367},"docs/learn/quickstart"," — get started in minutes",[3030,3370,3371,3375],{},[1777,3372,3374],{"href":3373},"docs/learn/concepts","Concepts"," — tables, fields, params, conditions, builders",[3030,3377,3378,3380],{},[1777,3379,16],{"href":2982}," — AST structure, render pipeline, security layers",[3358,3382,1697],{"id":3383},"guides",[3027,3385,3386,3391,3396,3401,3407],{},[3030,3387,3388,3390],{},[1777,3389,197],{"href":2969}," — DBML integration and validation",[3030,3392,3393,3395],{},[1777,3394,120],{"href":2995}," — WHERE, AND/OR, subqueries, BETWEEN",[3030,3397,3398,3400],{},[1777,3399,507],{"href":3008}," — INNER, LEFT, RIGHT, CROSS joins",[3030,3402,3403,3406],{},[1777,3404,575],{"href":3405},"docs/guides/aggregates"," — GROUP BY, HAVING, window functions",[3030,3408,3409,3412],{},[1777,3410,692],{"href":3411},"docs/guides/testing"," — testing patterns for query builders",[3358,3414,1712],{"id":3415},"cookbook",[3027,3417,3418,3424,3430,3436],{},[3030,3419,3420,3423],{},[1777,3421,790],{"href":3422},"docs/cookbook/pagination"," — LIMIT/OFFSET and cursor patterns",[3030,3425,3426,3429],{},[1777,3427,870],{"href":3428},"docs/cookbook/vector-search"," — pgvector similarity queries",[3030,3431,3432,3435],{},[1777,3433,957],{"href":3434},"docs/cookbook/upserts"," — ON CONFLICT patterns",[3030,3437,3438,3441],{},[1777,3439,1029],{"href":3440},"docs/cookbook/orm-foundation"," — building type-safe ORMs with cereal",[3358,3443,1725],{"id":3444},"reference",[3027,3446,3447,3452],{},[3030,3448,3449,3451],{},[1777,3450,3021],{"href":3020}," — complete function documentation",[3030,3453,3454,3457],{},[1777,3455,125],{"href":3456},"docs/reference/operators"," — all comparison and special operators",[1848,3459,3461],{"id":3460},"contributing","Contributing",[1774,3463,3464,3465,3469,3470,1872],{},"See ",[1777,3466,3468],{"href":3467},"CONTRIBUTING","CONTRIBUTING.md"," for guidelines. For security issues, see ",[1777,3471,3473],{"href":3472},"SECURITY","SECURITY.md",[1848,3475,1824],{"id":3476},"license",[1774,3478,3479,3480,1872],{},"MIT — see ",[1777,3481,1821],{"href":1821},[3483,3484,3485],"style",{},"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 .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 .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 .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 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)}",{"title":34,"searchDepth":19,"depth":19,"links":3487},[3488,3489,3490,3491,3492,3493,3494,3500,3501],{"id":1850,"depth":19,"text":1851},{"id":2294,"depth":19,"text":2295},{"id":2327,"depth":19,"text":2328},{"id":2934,"depth":19,"text":27},{"id":3024,"depth":19,"text":3025},{"id":3065,"depth":19,"text":3066},{"id":3347,"depth":19,"text":3348,"children":3495},[3496,3497,3498,3499],{"id":3360,"depth":40,"text":1685},{"id":3383,"depth":40,"text":1697},{"id":3415,"depth":40,"text":1712},{"id":3444,"depth":40,"text":1725},{"id":3460,"depth":19,"text":3461},{"id":3476,"depth":19,"text":1824},"readme","zi4yEJyRwuXDKMv0WVEh3s2pNJwmIyW1TSDTnheBP4U",{"id":3505,"extension":1762,"icon":3506,"meta":3507,"stem":3859,"__hash__":3860},"resources/security.md","shield",{"title":37,"body":3508},{"type":1767,"value":3509,"toc":3847},[3510,3514,3518,3521,3560,3564,3567,3571,3576,3579,3618,3622,3625,3674,3678,3681,3707,3711,3743,3747,3750,3792,3796,3799,3837,3841,3844],[1770,3511,3513],{"id":3512},"security-policy","Security Policy",[1848,3515,3517],{"id":3516},"supported-versions","Supported Versions",[1774,3519,3520],{},"We release patches for security vulnerabilities. Which versions are eligible for receiving such patches depends on the CVSS v3.0 Rating:",[2936,3522,3523,3536],{},[2939,3524,3525],{},[2942,3526,3527,3530,3533],{},[2945,3528,3529],{},"Version",[2945,3531,3532],{},"Supported",[2945,3534,3535],{},"Status",[2955,3537,3538,3549],{},[2942,3539,3540,3543,3546],{},[2960,3541,3542],{},"latest",[2960,3544,3545],{},"✅",[2960,3547,3548],{},"Active development",[2942,3550,3551,3554,3557],{},[2960,3552,3553],{},"\u003C latest",[2960,3555,3556],{},"❌",[2960,3558,3559],{},"Security fixes only for critical issues",[1848,3561,3563],{"id":3562},"reporting-a-vulnerability","Reporting a Vulnerability",[1774,3565,3566],{},"We take the security of ASTQL seriously. If you have discovered a security vulnerability in this project, please report it responsibly.",[3358,3568,3570],{"id":3569},"how-to-report","How to Report",[1774,3572,3573],{},[3033,3574,3575],{},"Please DO NOT report security vulnerabilities through public GitHub issues.",[1774,3577,3578],{},"Instead, please report them via one of the following methods:",[3580,3581,3582,3605],"ol",{},[3030,3583,3584,3587,3588],{},[3033,3585,3586],{},"GitHub Security Advisories"," (Preferred)",[3027,3589,3590,3599,3602],{},[3030,3591,3592,3593,3598],{},"Go to the ",[1777,3594,3597],{"href":3595,"rel":3596},"https://github.com/zoobzio/astql/security",[1781],"Security tab"," of this repository",[3030,3600,3601],{},"Click \"Report a vulnerability\"",[3030,3603,3604],{},"Fill out the form with details about the vulnerability",[3030,3606,3607,3610],{},[3033,3608,3609],{},"Email",[3027,3611,3612,3615],{},[3030,3613,3614],{},"Send details to the repository maintainer through GitHub profile contact information",[3030,3616,3617],{},"Use PGP encryption if possible for sensitive details",[3358,3619,3621],{"id":3620},"what-to-include","What to Include",[1774,3623,3624],{},"Please include the following information (as much as you can provide) to help us better understand the nature and scope of the possible issue:",[3027,3626,3627,3633,3639,3645,3651,3656,3662,3668],{},[3030,3628,3629,3632],{},[3033,3630,3631],{},"Type of issue"," (e.g., SQL injection, query manipulation, parameter injection, etc.)",[3030,3634,3635,3638],{},[3033,3636,3637],{},"Full paths of source file(s)"," related to the manifestation of the issue",[3030,3640,3641,3644],{},[3033,3642,3643],{},"The location of the affected source code"," (tag/branch/commit or direct URL)",[3030,3646,3647,3650],{},[3033,3648,3649],{},"Any special configuration required"," to reproduce the issue",[3030,3652,3653,3650],{},[3033,3654,3655],{},"Step-by-step instructions",[3030,3657,3658,3661],{},[3033,3659,3660],{},"Proof-of-concept or exploit code"," (if possible)",[3030,3663,3664,3667],{},[3033,3665,3666],{},"Impact of the issue",", including how an attacker might exploit the issue",[3030,3669,3670,3673],{},[3033,3671,3672],{},"Your name and affiliation"," (optional)",[3358,3675,3677],{"id":3676},"security-considerations-for-astql","Security Considerations for ASTQL",[1774,3679,3680],{},"Given that ASTQL is a SQL query builder, please pay special attention to:",[3027,3682,3683,3689,3695,3701],{},[3030,3684,3685,3688],{},[3033,3686,3687],{},"SQL Injection vulnerabilities"," - Any way to bypass parameterization",[3030,3690,3691,3694],{},[3033,3692,3693],{},"Parameter validation issues"," - Improper sanitization of parameter names",[3030,3696,3697,3700],{},[3033,3698,3699],{},"Field/Table validation bypasses"," - Ways to inject arbitrary SQL through field or table names",[3030,3702,3703,3706],{},[3033,3704,3705],{},"Schema validation vulnerabilities"," - Issues with DBML schema parsing",[3358,3708,3710],{"id":3709},"what-to-expect","What to Expect",[3027,3712,3713,3719,3725,3731,3737],{},[3030,3714,3715,3718],{},[3033,3716,3717],{},"Acknowledgment",": We will acknowledge receipt of your vulnerability report within 48 hours",[3030,3720,3721,3724],{},[3033,3722,3723],{},"Initial Assessment",": Within 7 days, we will provide an initial assessment of the report",[3030,3726,3727,3730],{},[3033,3728,3729],{},"Resolution Timeline",": We aim to resolve critical issues within 30 days",[3030,3732,3733,3736],{},[3033,3734,3735],{},"Disclosure",": We will coordinate with you on the disclosure timeline",[3030,3738,3739,3742],{},[3033,3740,3741],{},"Credit",": Security researchers who responsibly disclose vulnerabilities will be acknowledged (unless they prefer to remain anonymous)",[1848,3744,3746],{"id":3745},"security-best-practices-for-users","Security Best Practices for Users",[1774,3748,3749],{},"When using ASTQL in your applications:",[3580,3751,3752,3758,3768,3774,3780,3786],{},[3030,3753,3754,3757],{},[3033,3755,3756],{},"Always use parameterized queries"," - Never concatenate user input directly",[3030,3759,3760,3763,3764,3767],{},[3033,3761,3762],{},"Validate all input"," - Use the ",[1859,3765,3766],{},"Try*"," functions for user-provided data",[3030,3769,3770,3773],{},[3033,3771,3772],{},"Keep dependencies updated"," - Regularly update ASTQL and its dependencies",[3030,3775,3776,3779],{},[3033,3777,3778],{},"Review generated SQL"," - In development, log and review the generated SQL queries",[3030,3781,3782,3785],{},[3033,3783,3784],{},"Use least privilege"," - Database connections should have minimal required permissions",[3030,3787,3788,3791],{},[3033,3789,3790],{},"Validate schema inputs"," - When loading DBML schemas, ensure they come from trusted sources",[1848,3793,3795],{"id":3794},"security-features","Security Features",[1774,3797,3798],{},"ASTQL includes several security features:",[3027,3800,3801,3807,3813,3819,3825,3831],{},[3030,3802,3803,3806],{},[3033,3804,3805],{},"Automatic parameterization"," - All values are parameterized by default",[3030,3808,3809,3812],{},[3033,3810,3811],{},"Schema validation"," - All tables and fields must exist in DBML schema",[3030,3814,3815,3818],{},[3033,3816,3817],{},"Input validation"," - Field, table, and parameter names are validated",[3030,3820,3821,3824],{},[3033,3822,3823],{},"SQL keyword blocking"," - Parameter names cannot be SQL keywords",[3030,3826,3827,3830],{},[3033,3828,3829],{},"Injection prevention"," - Special characters in identifiers are escaped",[3030,3832,3833,3836],{},[3033,3834,3835],{},"Type safety"," - Go's type system prevents many common mistakes",[1848,3838,3840],{"id":3839},"contact","Contact",[1774,3842,3843],{},"For security-related questions that are not vulnerabilities, please open a discussion in the repository.",[1774,3845,3846],{},"Thank you for helping keep ASTQL and its users safe!",{"title":34,"searchDepth":19,"depth":19,"links":3848},[3849,3850,3856,3857,3858],{"id":3516,"depth":19,"text":3517},{"id":3562,"depth":19,"text":3563,"children":3851},[3852,3853,3854,3855],{"id":3569,"depth":40,"text":3570},{"id":3620,"depth":40,"text":3621},{"id":3676,"depth":40,"text":3677},{"id":3709,"depth":40,"text":3710},{"id":3745,"depth":19,"text":3746},{"id":3794,"depth":19,"text":3795},{"id":3839,"depth":19,"text":3840},"security","X8fJ2OeihVq7zr6Sr3y53_8VSMubcuJThZcoq3CSTrU",{"id":3862,"extension":1762,"icon":1859,"meta":3863,"stem":3460,"__hash__":4295},"resources/contributing.md",{"title":3461,"body":3864},{"type":1767,"value":3865,"toc":4273},[3866,3870,3873,3877,3880,3884,3928,3932,3936,3962,3965,3990,3992,4009,4013,4017,4034,4038,4052,4056,4073,4077,4079,4180,4184,4187,4201,4205,4208,4221,4225,4242,4246,4260,4264,4267,4270],[1770,3867,3869],{"id":3868},"contributing-to-astql","Contributing to ASTQL",[1774,3871,3872],{},"Thank you for your interest in contributing to ASTQL! This guide will help you get started.",[1848,3874,3876],{"id":3875},"code-of-conduct","Code of Conduct",[1774,3878,3879],{},"By participating in this project, you agree to maintain a respectful and inclusive environment for all contributors.",[1848,3881,3883],{"id":3882},"getting-started","Getting Started",[3580,3885,3886,3889,3895,3901,3904,3910,3916,3919,3925],{},[3030,3887,3888],{},"Fork the repository",[3030,3890,3891,3892],{},"Clone your fork: ",[1859,3893,3894],{},"git clone https://github.com/yourusername/astql.git",[3030,3896,3897,3898],{},"Create a feature branch: ",[1859,3899,3900],{},"git checkout -b feature/your-feature-name",[3030,3902,3903],{},"Make your changes",[3030,3905,3906,3907],{},"Run tests: ",[1859,3908,3909],{},"make test",[3030,3911,3912,3913],{},"Run linter: ",[1859,3914,3915],{},"make lint",[3030,3917,3918],{},"Commit your changes with a descriptive message",[3030,3920,3921,3922],{},"Push to your fork: ",[1859,3923,3924],{},"git push origin feature/your-feature-name",[3030,3926,3927],{},"Create a Pull Request",[1848,3929,3931],{"id":3930},"development-guidelines","Development Guidelines",[3358,3933,3935],{"id":3934},"code-style","Code Style",[3027,3937,3938,3941,3948,3953,3956,3959],{},[3030,3939,3940],{},"Follow standard Go conventions",[3030,3942,3943,3944,3947],{},"Run ",[1859,3945,3946],{},"gofmt"," before committing",[3030,3949,3950,3951],{},"Pass all linter checks: ",[1859,3952,3915],{},[3030,3954,3955],{},"Add godoc comments for all exported functions and types",[3030,3957,3958],{},"Keep functions small and focused",[3030,3960,3961],{},"Add periods to comment lines",[3358,3963,692],{"id":3964},"testing",[3027,3966,3967,3970,3975,3981,3984,3987],{},[3030,3968,3969],{},"Write tests for new functionality",[3030,3971,3972,3973],{},"Ensure all tests pass: ",[1859,3974,3909],{},[3030,3976,3977,3978],{},"Run race detector: ",[1859,3979,3980],{},"make test-race",[3030,3982,3983],{},"Include benchmarks for performance-critical code",[3030,3985,3986],{},"Aim for >70% test coverage",[3030,3988,3989],{},"Test both PostgreSQL and SQLite providers",[3358,3991,3348],{"id":3347},[3027,3993,3994,3997,4000,4003,4006],{},[3030,3995,3996],{},"Update README for significant features",[3030,3998,3999],{},"Add godoc comments for all exported APIs",[3030,4001,4002],{},"Include examples in documentation",[3030,4004,4005],{},"Update schema examples if adding new features",[3030,4007,4008],{},"Keep comments clear and concise",[1848,4010,4012],{"id":4011},"types-of-contributions","Types of Contributions",[3358,4014,4016],{"id":4015},"bug-reports","Bug Reports",[3027,4018,4019,4022,4025,4028,4031],{},[3030,4020,4021],{},"Use GitHub Issues",[3030,4023,4024],{},"Include minimal reproduction code",[3030,4026,4027],{},"Describe expected vs actual behavior",[3030,4029,4030],{},"Include Go version and OS",[3030,4032,4033],{},"Specify which provider (PostgreSQL/SQLite)",[3358,4035,4037],{"id":4036},"feature-requests","Feature Requests",[3027,4039,4040,4043,4046,4049],{},[3030,4041,4042],{},"Open an issue first to discuss",[3030,4044,4045],{},"Describe the use case",[3030,4047,4048],{},"Consider backward compatibility",[3030,4050,4051],{},"Propose API design if applicable",[3358,4053,4055],{"id":4054},"pull-requests","Pull Requests",[3027,4057,4058,4061,4064,4067,4070],{},[3030,4059,4060],{},"Reference related issues",[3030,4062,4063],{},"Keep changes focused and atomic",[3030,4065,4066],{},"Add tests for new features",[3030,4068,4069],{},"Update documentation",[3030,4071,4072],{},"Ensure CI passes",[1848,4074,4076],{"id":4075},"development-workflow","Development Workflow",[3358,4078,2328],{"id":2327},[1853,4080,4082],{"className":2298,"code":4081,"language":2300,"meta":34,"style":34},"# Install dependencies\ngo mod download\n\n# Install development tools\nmake install-tools\n\n# Run tests\nmake test\n\n# Run linter\nmake lint\n\n# Run all checks\nmake check\n\n# Generate coverage report\nmake coverage\n",[1859,4083,4084,4089,4099,4103,4108,4116,4120,4125,4132,4136,4141,4148,4152,4157,4164,4168,4173],{"__ignoreMap":34},[1862,4085,4086],{"class":1864,"line":9},[1862,4087,4088],{"class":1888},"# Install dependencies\n",[1862,4090,4091,4093,4096],{"class":1864,"line":19},[1862,4092,1857],{"class":1875},[1862,4094,4095],{"class":1881}," mod",[1862,4097,4098],{"class":1881}," download\n",[1862,4100,4101],{"class":1864,"line":40},[1862,4102,1913],{"emptyLinePlaceholder":1912},[1862,4104,4105],{"class":1864,"line":1916},[1862,4106,4107],{"class":1888},"# Install development tools\n",[1862,4109,4110,4113],{"class":1864,"line":1934},[1862,4111,4112],{"class":1875},"make",[1862,4114,4115],{"class":1881}," install-tools\n",[1862,4117,4118],{"class":1864,"line":1953},[1862,4119,1913],{"emptyLinePlaceholder":1912},[1862,4121,4122],{"class":1864,"line":1958},[1862,4123,4124],{"class":1888},"# Run tests\n",[1862,4126,4127,4129],{"class":1864,"line":1989},[1862,4128,4112],{"class":1875},[1862,4130,4131],{"class":1881}," test\n",[1862,4133,4134],{"class":1864,"line":2024},[1862,4135,1913],{"emptyLinePlaceholder":1912},[1862,4137,4138],{"class":1864,"line":2077},[1862,4139,4140],{"class":1888},"# Run linter\n",[1862,4142,4143,4145],{"class":1864,"line":2082},[1862,4144,4112],{"class":1875},[1862,4146,4147],{"class":1881}," lint\n",[1862,4149,4150],{"class":1864,"line":2115},[1862,4151,1913],{"emptyLinePlaceholder":1912},[1862,4153,4154],{"class":1864,"line":2423},[1862,4155,4156],{"class":1888},"# Run all checks\n",[1862,4158,4159,4161],{"class":1864,"line":2444},[1862,4160,4112],{"class":1875},[1862,4162,4163],{"class":1881}," check\n",[1862,4165,4166],{"class":1864,"line":2477},[1862,4167,1913],{"emptyLinePlaceholder":1912},[1862,4169,4170],{"class":1864,"line":2505},[1862,4171,4172],{"class":1888},"# Generate coverage report\n",[1862,4174,4175,4177],{"class":1864,"line":2532},[1862,4176,4112],{"class":1875},[1862,4178,4179],{"class":1881}," coverage\n",[3358,4181,4183],{"id":4182},"provider-specific-development","Provider-Specific Development",[1774,4185,4186],{},"When working on provider-specific features:",[3580,4188,4189,4192,4195,4198],{},[3030,4190,4191],{},"Implement for both PostgreSQL and SQLite when applicable",[3030,4193,4194],{},"If a feature is database-specific, document the limitation",[3030,4196,4197],{},"Add provider-specific tests",[3030,4199,4200],{},"Ensure consistent API across providers",[3358,4202,4204],{"id":4203},"schema-development","Schema Development",[1774,4206,4207],{},"When modifying schema support:",[3580,4209,4210,4213,4216,4219],{},[3030,4211,4212],{},"Update both provider implementations",[3030,4214,4215],{},"Add schema validation tests",[3030,4217,4218],{},"Update schema documentation",[3030,4220,4048],{},[1848,4222,4224],{"id":4223},"code-review-process","Code Review Process",[3580,4226,4227,4230,4233,4236,4239],{},[3030,4228,4229],{},"All submissions require review",[3030,4231,4232],{},"CI must pass",[3030,4234,4235],{},"Maintainers will provide feedback",[3030,4237,4238],{},"Address review comments",[3030,4240,4241],{},"Squash commits if requested",[1848,4243,4245],{"id":4244},"release-process","Release Process",[3580,4247,4248,4251,4254,4257],{},[3030,4249,4250],{},"Maintainers handle releases",[3030,4252,4253],{},"Semantic versioning is used",[3030,4255,4256],{},"Changelog is maintained",[3030,4258,4259],{},"Tagged releases trigger automation",[1848,4261,4263],{"id":4262},"questions","Questions?",[1774,4265,4266],{},"Feel free to open an issue for questions or join discussions in existing issues.",[1774,4268,4269],{},"Thank you for contributing to ASTQL!",[3483,4271,4272],{},"html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}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":4274},[4275,4276,4277,4282,4287,4292,4293,4294],{"id":3875,"depth":19,"text":3876},{"id":3882,"depth":19,"text":3883},{"id":3930,"depth":19,"text":3931,"children":4278},[4279,4280,4281],{"id":3934,"depth":40,"text":3935},{"id":3964,"depth":40,"text":692},{"id":3347,"depth":40,"text":3348},{"id":4011,"depth":19,"text":4012,"children":4283},[4284,4285,4286],{"id":4015,"depth":40,"text":4016},{"id":4036,"depth":40,"text":4037},{"id":4054,"depth":40,"text":4055},{"id":4075,"depth":19,"text":4076,"children":4288},[4289,4290,4291],{"id":2327,"depth":40,"text":2328},{"id":4182,"depth":40,"text":4183},{"id":4203,"depth":40,"text":4204},{"id":4223,"depth":19,"text":4224},{"id":4244,"depth":19,"text":4245},{"id":4262,"depth":19,"text":4263},"2q7xGabsgYD3HBHSQNc1RnI4DOzvE7x91kluZ3BCJSY",{"id":4297,"title":1559,"author":4298,"body":4299,"description":1561,"extension":1762,"meta":7003,"navigation":1912,"path":1558,"published":7004,"readtime":7005,"seo":7006,"stem":1732,"tags":7007,"updated":7004,"__hash__":7009},"astql/v1.0.10/5.reference/2.operators.md","zoobzio",{"type":1767,"value":4300,"toc":6967},[4301,4304,4306,4309,4312,4448,4451,4503,4506,4509,4603,4606,4703,4706,4709,4765,4768,4832,4835,4838,4894,4897,5003,5006,5113,5116,5119,5187,5190,5316,5319,5322,5418,5421,5506,5509,5512,5588,5591,5700,5703,5706,5763,5766,5974,5977,5980,5983,6072,6075,6214,6217,6303,6306,6431,6434,6440,6544,6547,6550,6684,6687,6690,6738,6741,6747,6964],[1770,4302,1559],{"id":4303},"operators-reference",[1774,4305,1565],{},[1848,4307,417],{"id":4308},"comparison-operators",[1774,4310,4311],{},"Basic comparison operators for WHERE conditions.",[2936,4313,4314,4327],{},[2939,4315,4316],{},[2942,4317,4318,4321,4323,4325],{},[2945,4319,4320],{},"Constant",[2945,4322,2891],{},[2945,4324,2950],{},[2945,4326,672],{},[2955,4328,4329,4348,4368,4388,4408,4428],{},[2942,4330,4331,4335,4340,4343],{},[2960,4332,4333],{},[1859,4334,2057],{},[2960,4336,4337],{},[1859,4338,4339],{},"=",[2960,4341,4342],{},"Equals",[2960,4344,4345],{},[1859,4346,4347],{},"\"id\" = :id",[2942,4349,4350,4355,4360,4363],{},[2960,4351,4352],{},[1859,4353,4354],{},"NE",[2960,4356,4357],{},[1859,4358,4359],{},"!=",[2960,4361,4362],{},"Not equals",[2960,4364,4365],{},[1859,4366,4367],{},"\"status\" != :status",[2942,4369,4370,4375,4380,4383],{},[2960,4371,4372],{},[1859,4373,4374],{},"GT",[2960,4376,4377],{},[1859,4378,4379],{},">",[2960,4381,4382],{},"Greater than",[2960,4384,4385],{},[1859,4386,4387],{},"\"price\" > :min",[2942,4389,4390,4395,4400,4403],{},[2960,4391,4392],{},[1859,4393,4394],{},"GE",[2960,4396,4397],{},[1859,4398,4399],{},">=",[2960,4401,4402],{},"Greater than or equal",[2960,4404,4405],{},[1859,4406,4407],{},"\"age\" >= :min_age",[2942,4409,4410,4415,4420,4423],{},[2960,4411,4412],{},[1859,4413,4414],{},"LT",[2960,4416,4417],{},[1859,4418,4419],{},"\u003C",[2960,4421,4422],{},"Less than",[2960,4424,4425],{},[1859,4426,4427],{},"\"date\" \u003C :cutoff",[2942,4429,4430,4435,4440,4443],{},[2960,4431,4432],{},[1859,4433,4434],{},"LE",[2960,4436,4437],{},[1859,4438,4439],{},"\u003C=",[2960,4441,4442],{},"Less than or equal",[2960,4444,4445],{},[1859,4446,4447],{},"\"count\" \u003C= :max",[3358,4449,257],{"id":4450},"usage",[1853,4452,4454],{"className":1855,"code":4453,"language":1857,"meta":34,"style":34},"instance.C(instance.F(\"id\"), astql.EQ, instance.P(\"id\"))\n// \"id\" = :id\n",[1859,4455,4456,4498],{"__ignoreMap":34},[1862,4457,4458,4460,4462,4464,4466,4468,4470,4472,4474,4476,4478,4480,4482,4484,4486,4488,4490,4492,4494,4496],{"class":1864,"line":9},[1862,4459,1868],{"class":1867},[1862,4461,1872],{"class":1871},[1862,4463,1165],{"class":1875},[1862,4465,1878],{"class":1871},[1862,4467,1868],{"class":1867},[1862,4469,1872],{"class":1871},[1862,4471,1145],{"class":1875},[1862,4473,1878],{"class":1871},[1862,4475,2466],{"class":1881},[1862,4477,2008],{"class":1871},[1862,4479,1967],{"class":1867},[1862,4481,1872],{"class":1871},[1862,4483,2057],{"class":1867},[1862,4485,2060],{"class":1871},[1862,4487,2011],{"class":1867},[1862,4489,1872],{"class":1871},[1862,4491,1155],{"class":1875},[1862,4493,1878],{"class":1871},[1862,4495,2466],{"class":1881},[1862,4497,2474],{"class":1871},[1862,4499,4500],{"class":1864,"line":19},[1862,4501,4502],{"class":1888},"// \"id\" = :id\n",[1848,4504,422],{"id":4505},"pattern-matching",[1774,4507,4508],{},"String pattern matching operators.",[2936,4510,4511,4523],{},[2939,4512,4513],{},[2942,4514,4515,4517,4519,4521],{},[2945,4516,4320],{},[2945,4518,2891],{},[2945,4520,2950],{},[2945,4522,672],{},[2955,4524,4525,4544,4564,4583],{},[2942,4526,4527,4532,4536,4539],{},[2960,4528,4529],{},[1859,4530,4531],{},"LIKE",[2960,4533,4534],{},[1859,4535,4531],{},[2960,4537,4538],{},"Pattern match (case-sensitive)",[2960,4540,4541],{},[1859,4542,4543],{},"\"name\" LIKE :pattern",[2942,4545,4546,4551,4556,4559],{},[2960,4547,4548],{},[1859,4549,4550],{},"NotLike",[2960,4552,4553],{},[1859,4554,4555],{},"NOT LIKE",[2960,4557,4558],{},"Pattern non-match",[2960,4560,4561],{},[1859,4562,4563],{},"\"name\" NOT LIKE :pattern",[2942,4565,4566,4571,4575,4578],{},[2960,4567,4568],{},[1859,4569,4570],{},"ILIKE",[2960,4572,4573],{},[1859,4574,4570],{},[2960,4576,4577],{},"Pattern match (case-insensitive)",[2960,4579,4580],{},[1859,4581,4582],{},"\"name\" ILIKE :pattern",[2942,4584,4585,4590,4595,4598],{},[2960,4586,4587],{},[1859,4588,4589],{},"NotILike",[2960,4591,4592],{},[1859,4593,4594],{},"NOT ILIKE",[2960,4596,4597],{},"Pattern non-match (case-insensitive)",[2960,4599,4600],{},[1859,4601,4602],{},"\"name\" NOT ILIKE :pattern",[3358,4604,257],{"id":4605},"usage-1",[1853,4607,4609],{"className":1855,"code":4608,"language":1857,"meta":34,"style":34},"instance.C(instance.F(\"email\"), astql.ILIKE, instance.P(\"search\"))\n// \"email\" ILIKE :search\n\n// With wildcards in parameter value\nparams := map[string]any{\"search\": \"%@example.com\"}\n",[1859,4610,4611,4654,4659,4663,4668],{"__ignoreMap":34},[1862,4612,4613,4615,4617,4619,4621,4623,4625,4627,4629,4631,4633,4635,4637,4639,4641,4643,4645,4647,4649,4652],{"class":1864,"line":9},[1862,4614,1868],{"class":1867},[1862,4616,1872],{"class":1871},[1862,4618,1165],{"class":1875},[1862,4620,1878],{"class":1871},[1862,4622,1868],{"class":1867},[1862,4624,1872],{"class":1871},[1862,4626,1145],{"class":1875},[1862,4628,1878],{"class":1871},[1862,4630,1927],{"class":1881},[1862,4632,2008],{"class":1871},[1862,4634,1967],{"class":1867},[1862,4636,1872],{"class":1871},[1862,4638,4570],{"class":1867},[1862,4640,2060],{"class":1871},[1862,4642,2011],{"class":1867},[1862,4644,1872],{"class":1871},[1862,4646,1155],{"class":1875},[1862,4648,1878],{"class":1871},[1862,4650,4651],{"class":1881},"\"search\"",[1862,4653,2474],{"class":1871},[1862,4655,4656],{"class":1864,"line":19},[1862,4657,4658],{"class":1888},"// \"email\" ILIKE :search\n",[1862,4660,4661],{"class":1864,"line":40},[1862,4662,1913],{"emptyLinePlaceholder":1912},[1862,4664,4665],{"class":1864,"line":1916},[1862,4666,4667],{"class":1888},"// With wildcards in parameter value\n",[1862,4669,4670,4673,4675,4678,4681,4684,4687,4690,4693,4695,4698,4701],{"class":1864,"line":1934},[1862,4671,4672],{"class":1867},"params",[1862,4674,1964],{"class":1867},[1862,4676,4677],{"class":2134}," map",[1862,4679,4680],{"class":1871},"[",[1862,4682,4683],{"class":2341},"string",[1862,4685,4686],{"class":1871},"]",[1862,4688,4689],{"class":2341},"any",[1862,4691,4692],{"class":1871},"{",[1862,4694,4651],{"class":1881},[1862,4696,4697],{"class":1871},":",[1862,4699,4700],{"class":1881}," \"%@example.com\"",[1862,4702,2931],{"class":1871},[1848,4704,1584],{"id":4705},"null-operators",[1774,4707,4708],{},"Check for NULL values.",[2936,4710,4711,4723],{},[2939,4712,4713],{},[2942,4714,4715,4717,4719,4721],{},[2945,4716,4320],{},[2945,4718,2891],{},[2945,4720,2950],{},[2945,4722,672],{},[2955,4724,4725,4745],{},[2942,4726,4727,4732,4737,4740],{},[2960,4728,4729],{},[1859,4730,4731],{},"IsNull",[2960,4733,4734],{},[1859,4735,4736],{},"IS NULL",[2960,4738,4739],{},"Value is NULL",[2960,4741,4742],{},[1859,4743,4744],{},"\"deleted_at\" IS NULL",[2942,4746,4747,4752,4757,4760],{},[2960,4748,4749],{},[1859,4750,4751],{},"IsNotNull",[2960,4753,4754],{},[1859,4755,4756],{},"IS NOT NULL",[2960,4758,4759],{},"Value is not NULL",[2960,4761,4762],{},[1859,4763,4764],{},"\"verified_at\" IS NOT NULL",[3358,4766,257],{"id":4767},"usage-2",[1853,4769,4771],{"className":1855,"code":4770,"language":1857,"meta":34,"style":34},"instance.Null(instance.F(\"deleted_at\"))\n// \"deleted_at\" IS NULL\n\ninstance.NotNull(instance.F(\"email\"))\n// \"email\" IS NOT NULL\n",[1859,4772,4773,4796,4801,4805,4827],{"__ignoreMap":34},[1862,4774,4775,4777,4779,4781,4783,4785,4787,4789,4791,4794],{"class":1864,"line":9},[1862,4776,1868],{"class":1867},[1862,4778,1872],{"class":1871},[1862,4780,1185],{"class":1875},[1862,4782,1878],{"class":1871},[1862,4784,1868],{"class":1867},[1862,4786,1872],{"class":1871},[1862,4788,1145],{"class":1875},[1862,4790,1878],{"class":1871},[1862,4792,4793],{"class":1881},"\"deleted_at\"",[1862,4795,2474],{"class":1871},[1862,4797,4798],{"class":1864,"line":19},[1862,4799,4800],{"class":1888},"// \"deleted_at\" IS NULL\n",[1862,4802,4803],{"class":1864,"line":40},[1862,4804,1913],{"emptyLinePlaceholder":1912},[1862,4806,4807,4809,4811,4813,4815,4817,4819,4821,4823,4825],{"class":1864,"line":1916},[1862,4808,1868],{"class":1867},[1862,4810,1872],{"class":1871},[1862,4812,1190],{"class":1875},[1862,4814,1878],{"class":1871},[1862,4816,1868],{"class":1867},[1862,4818,1872],{"class":1871},[1862,4820,1145],{"class":1875},[1862,4822,1878],{"class":1871},[1862,4824,1927],{"class":1881},[1862,4826,2474],{"class":1871},[1862,4828,4829],{"class":1864,"line":1934},[1862,4830,4831],{"class":1888},"// \"email\" IS NOT NULL\n",[1848,4833,432],{"id":4834},"array-operators",[1774,4836,4837],{},"PostgreSQL array membership operators.",[2936,4839,4840,4852],{},[2939,4841,4842],{},[2942,4843,4844,4846,4848,4850],{},[2945,4845,4320],{},[2945,4847,2891],{},[2945,4849,2950],{},[2945,4851,672],{},[2955,4853,4854,4874],{},[2942,4855,4856,4861,4866,4869],{},[2960,4857,4858],{},[1859,4859,4860],{},"IN",[2960,4862,4863],{},[1859,4864,4865],{},"= ANY()",[2960,4867,4868],{},"Value in array",[2960,4870,4871],{},[1859,4872,4873],{},"\"id\" = ANY(:ids)",[2942,4875,4876,4881,4886,4889],{},[2960,4877,4878],{},[1859,4879,4880],{},"NotIn",[2960,4882,4883],{},[1859,4884,4885],{},"!= ALL()",[2960,4887,4888],{},"Value not in array",[2960,4890,4891],{},[1859,4892,4893],{},"\"status\" != ALL(:excluded)",[3358,4895,257],{"id":4896},"usage-3",[1853,4898,4900],{"className":1855,"code":4899,"language":1857,"meta":34,"style":34},"instance.C(instance.F(\"id\"), astql.IN, instance.P(\"ids\"))\n// \"id\" = ANY(:ids)\n\ninstance.C(instance.F(\"status\"), astql.NotIn, instance.P(\"excluded\"))\n// \"status\" != ALL(:excluded)\n",[1859,4901,4902,4945,4950,4954,4998],{"__ignoreMap":34},[1862,4903,4904,4906,4908,4910,4912,4914,4916,4918,4920,4922,4924,4926,4928,4930,4932,4934,4936,4938,4940,4943],{"class":1864,"line":9},[1862,4905,1868],{"class":1867},[1862,4907,1872],{"class":1871},[1862,4909,1165],{"class":1875},[1862,4911,1878],{"class":1871},[1862,4913,1868],{"class":1867},[1862,4915,1872],{"class":1871},[1862,4917,1145],{"class":1875},[1862,4919,1878],{"class":1871},[1862,4921,2466],{"class":1881},[1862,4923,2008],{"class":1871},[1862,4925,1967],{"class":1867},[1862,4927,1872],{"class":1871},[1862,4929,4860],{"class":1867},[1862,4931,2060],{"class":1871},[1862,4933,2011],{"class":1867},[1862,4935,1872],{"class":1871},[1862,4937,1155],{"class":1875},[1862,4939,1878],{"class":1871},[1862,4941,4942],{"class":1881},"\"ids\"",[1862,4944,2474],{"class":1871},[1862,4946,4947],{"class":1864,"line":19},[1862,4948,4949],{"class":1888},"// \"id\" = ANY(:ids)\n",[1862,4951,4952],{"class":1864,"line":40},[1862,4953,1913],{"emptyLinePlaceholder":1912},[1862,4955,4956,4958,4960,4962,4964,4966,4968,4970,4972,4975,4977,4979,4981,4983,4985,4987,4989,4991,4993,4996],{"class":1864,"line":1916},[1862,4957,1868],{"class":1867},[1862,4959,1872],{"class":1871},[1862,4961,1165],{"class":1875},[1862,4963,1878],{"class":1871},[1862,4965,1868],{"class":1867},[1862,4967,1872],{"class":1871},[1862,4969,1145],{"class":1875},[1862,4971,1878],{"class":1871},[1862,4973,4974],{"class":1881},"\"status\"",[1862,4976,2008],{"class":1871},[1862,4978,1967],{"class":1867},[1862,4980,1872],{"class":1871},[1862,4982,4880],{"class":1867},[1862,4984,2060],{"class":1871},[1862,4986,2011],{"class":1867},[1862,4988,1872],{"class":1871},[1862,4990,1155],{"class":1875},[1862,4992,1878],{"class":1871},[1862,4994,4995],{"class":1881},"\"excluded\"",[1862,4997,2474],{"class":1871},[1862,4999,5000],{"class":1864,"line":1934},[1862,5001,5002],{"class":1888},"// \"status\" != ALL(:excluded)\n",[3358,5004,1601],{"id":5005},"with-sqlx",[1853,5007,5009],{"className":1855,"code":5008,"language":1857,"meta":34,"style":34},"import \"github.com/lib/pq\"\n\nparams := map[string]any{\n    \"ids\": pq.Array([]int{1, 2, 3}),\n    \"excluded\": pq.Array([]string{\"draft\", \"deleted\"}),\n}\n",[1859,5010,5011,5018,5022,5041,5080,5109],{"__ignoreMap":34},[1862,5012,5013,5015],{"class":1864,"line":9},[1862,5014,2135],{"class":2134},[1862,5016,5017],{"class":1881}," \"github.com/lib/pq\"\n",[1862,5019,5020],{"class":1864,"line":19},[1862,5021,1913],{"emptyLinePlaceholder":1912},[1862,5023,5024,5026,5028,5030,5032,5034,5036,5038],{"class":1864,"line":40},[1862,5025,4672],{"class":1867},[1862,5027,1964],{"class":1867},[1862,5029,4677],{"class":2134},[1862,5031,4680],{"class":1871},[1862,5033,4683],{"class":2341},[1862,5035,4686],{"class":1871},[1862,5037,4689],{"class":2341},[1862,5039,5040],{"class":1871},"{\n",[1862,5042,5043,5046,5048,5051,5053,5056,5059,5062,5064,5067,5069,5072,5074,5077],{"class":1864,"line":1916},[1862,5044,5045],{"class":1881},"    \"ids\"",[1862,5047,4697],{"class":1871},[1862,5049,5050],{"class":1867}," pq",[1862,5052,1872],{"class":1871},[1862,5054,5055],{"class":1875},"Array",[1862,5057,5058],{"class":1871},"([]",[1862,5060,5061],{"class":2341},"int",[1862,5063,4692],{"class":1871},[1862,5065,5066],{"class":2813},"1",[1862,5068,2060],{"class":1871},[1862,5070,5071],{"class":2813}," 2",[1862,5073,2060],{"class":1871},[1862,5075,5076],{"class":2813}," 3",[1862,5078,5079],{"class":1871},"}),\n",[1862,5081,5082,5085,5087,5089,5091,5093,5095,5097,5099,5102,5104,5107],{"class":1864,"line":1934},[1862,5083,5084],{"class":1881},"    \"excluded\"",[1862,5086,4697],{"class":1871},[1862,5088,5050],{"class":1867},[1862,5090,1872],{"class":1871},[1862,5092,5055],{"class":1875},[1862,5094,5058],{"class":1871},[1862,5096,4683],{"class":2341},[1862,5098,4692],{"class":1871},[1862,5100,5101],{"class":1881},"\"draft\"",[1862,5103,2060],{"class":1871},[1862,5105,5106],{"class":1881}," \"deleted\"",[1862,5108,5079],{"class":1871},[1862,5110,5111],{"class":1864,"line":1953},[1862,5112,2931],{"class":1871},[1848,5114,1606],{"id":5115},"subquery-operators",[1774,5117,5118],{},"Operators for subquery conditions.",[2936,5120,5121,5131],{},[2939,5122,5123],{},[2942,5124,5125,5127,5129],{},[2945,5126,4320],{},[2945,5128,2891],{},[2945,5130,2950],{},[2955,5132,5133,5146,5160,5173],{},[2942,5134,5135,5139,5143],{},[2960,5136,5137],{},[1859,5138,4860],{},[2960,5140,5141],{},[1859,5142,4860],{},[2960,5144,5145],{},"Value in subquery results",[2942,5147,5148,5152,5157],{},[2960,5149,5150],{},[1859,5151,4880],{},[2960,5153,5154],{},[1859,5155,5156],{},"NOT IN",[2960,5158,5159],{},"Value not in subquery results",[2942,5161,5162,5166,5170],{},[2960,5163,5164],{},[1859,5165,478],{},[2960,5167,5168],{},[1859,5169,478],{},[2960,5171,5172],{},"Subquery returns rows",[2942,5174,5175,5180,5184],{},[2960,5176,5177],{},[1859,5178,5179],{},"NotExists",[2960,5181,5182],{},[1859,5183,483],{},[2960,5185,5186],{},"Subquery returns no rows",[3358,5188,257],{"id":5189},"usage-4",[1853,5191,5193],{"className":1855,"code":5192,"language":1857,"meta":34,"style":34},"// IN subquery\nsubquery := astql.Sub(astql.Select(table).Fields(field))\nastql.CSub(instance.F(\"id\"), astql.IN, subquery)\n// \"id\" IN (SELECT \"user_id\" FROM \"orders\")\n\n// EXISTS\nastql.CSubExists(astql.EXISTS, subquery)\n// EXISTS (SELECT \"id\" FROM \"orders\" WHERE ...)\n",[1859,5194,5195,5200,5238,5274,5279,5283,5288,5311],{"__ignoreMap":34},[1862,5196,5197],{"class":1864,"line":9},[1862,5198,5199],{"class":1888},"// IN subquery\n",[1862,5201,5202,5205,5207,5209,5211,5214,5216,5218,5220,5222,5224,5226,5229,5231,5233,5236],{"class":1864,"line":19},[1862,5203,5204],{"class":1867},"subquery",[1862,5206,1964],{"class":1867},[1862,5208,1967],{"class":1867},[1862,5210,1872],{"class":1871},[1862,5212,5213],{"class":1875},"Sub",[1862,5215,1878],{"class":1871},[1862,5217,1772],{"class":1867},[1862,5219,1872],{"class":1871},[1862,5221,1234],{"class":1875},[1862,5223,1878],{"class":1871},[1862,5225,2936],{"class":1867},[1862,5227,5228],{"class":1871},").",[1862,5230,100],{"class":1875},[1862,5232,1878],{"class":1871},[1862,5234,5235],{"class":1867},"field",[1862,5237,2474],{"class":1871},[1862,5239,5240,5242,5244,5247,5249,5251,5253,5255,5257,5259,5261,5263,5265,5267,5269,5272],{"class":1864,"line":40},[1862,5241,1772],{"class":1867},[1862,5243,1872],{"class":1871},[1862,5245,5246],{"class":1875},"CSub",[1862,5248,1878],{"class":1871},[1862,5250,1868],{"class":1867},[1862,5252,1872],{"class":1871},[1862,5254,1145],{"class":1875},[1862,5256,1878],{"class":1871},[1862,5258,2466],{"class":1881},[1862,5260,2008],{"class":1871},[1862,5262,1967],{"class":1867},[1862,5264,1872],{"class":1871},[1862,5266,4860],{"class":1867},[1862,5268,2060],{"class":1871},[1862,5270,5271],{"class":1867}," subquery",[1862,5273,2164],{"class":1871},[1862,5275,5276],{"class":1864,"line":1916},[1862,5277,5278],{"class":1888},"// \"id\" IN (SELECT \"user_id\" FROM \"orders\")\n",[1862,5280,5281],{"class":1864,"line":1934},[1862,5282,1913],{"emptyLinePlaceholder":1912},[1862,5284,5285],{"class":1864,"line":1953},[1862,5286,5287],{"class":1888},"// EXISTS\n",[1862,5289,5290,5292,5294,5297,5299,5301,5303,5305,5307,5309],{"class":1864,"line":1958},[1862,5291,1772],{"class":1867},[1862,5293,1872],{"class":1871},[1862,5295,5296],{"class":1875},"CSubExists",[1862,5298,1878],{"class":1871},[1862,5300,1772],{"class":1867},[1862,5302,1872],{"class":1871},[1862,5304,478],{"class":1867},[1862,5306,2060],{"class":1871},[1862,5308,5271],{"class":1867},[1862,5310,2164],{"class":1871},[1862,5312,5313],{"class":1864,"line":1989},[1862,5314,5315],{"class":1888},"// EXISTS (SELECT \"id\" FROM \"orders\" WHERE ...)\n",[1848,5317,1615],{"id":5318},"regex-operators",[1774,5320,5321],{},"PostgreSQL regular expression operators.",[2936,5323,5324,5336],{},[2939,5325,5326],{},[2942,5327,5328,5330,5332,5334],{},[2945,5329,4320],{},[2945,5331,2891],{},[2945,5333,2950],{},[2945,5335,672],{},[2955,5337,5338,5358,5378,5398],{},[2942,5339,5340,5345,5350,5353],{},[2960,5341,5342],{},[1859,5343,5344],{},"RegexMatch",[2960,5346,5347],{},[1859,5348,5349],{},"~",[2960,5351,5352],{},"Regex match (case-sensitive)",[2960,5354,5355],{},[1859,5356,5357],{},"\"code\" ~ :pattern",[2942,5359,5360,5365,5370,5373],{},[2960,5361,5362],{},[1859,5363,5364],{},"RegexIMatch",[2960,5366,5367],{},[1859,5368,5369],{},"~*",[2960,5371,5372],{},"Regex match (case-insensitive)",[2960,5374,5375],{},[1859,5376,5377],{},"\"name\" ~* :pattern",[2942,5379,5380,5385,5390,5393],{},[2960,5381,5382],{},[1859,5383,5384],{},"NotRegexMatch",[2960,5386,5387],{},[1859,5388,5389],{},"!~",[2960,5391,5392],{},"Regex non-match",[2960,5394,5395],{},[1859,5396,5397],{},"\"email\" !~ :pattern",[2942,5399,5400,5405,5410,5413],{},[2960,5401,5402],{},[1859,5403,5404],{},"NotRegexIMatch",[2960,5406,5407],{},[1859,5408,5409],{},"!~*",[2960,5411,5412],{},"Regex non-match (case-insensitive)",[2960,5414,5415],{},[1859,5416,5417],{},"\"name\" !~* :pattern",[3358,5419,257],{"id":5420},"usage-5",[1853,5422,5424],{"className":1855,"code":5423,"language":1857,"meta":34,"style":34},"instance.C(instance.F(\"code\"), astql.RegexMatch, instance.P(\"pattern\"))\n// \"code\" ~ :pattern\n\nparams := map[string]any{\"pattern\": \"^[A-Z]{3}-[0-9]+$\"}\n",[1859,5425,5426,5470,5475,5479],{"__ignoreMap":34},[1862,5427,5428,5430,5432,5434,5436,5438,5440,5442,5444,5447,5449,5451,5453,5455,5457,5459,5461,5463,5465,5468],{"class":1864,"line":9},[1862,5429,1868],{"class":1867},[1862,5431,1872],{"class":1871},[1862,5433,1165],{"class":1875},[1862,5435,1878],{"class":1871},[1862,5437,1868],{"class":1867},[1862,5439,1872],{"class":1871},[1862,5441,1145],{"class":1875},[1862,5443,1878],{"class":1871},[1862,5445,5446],{"class":1881},"\"code\"",[1862,5448,2008],{"class":1871},[1862,5450,1967],{"class":1867},[1862,5452,1872],{"class":1871},[1862,5454,5344],{"class":1867},[1862,5456,2060],{"class":1871},[1862,5458,2011],{"class":1867},[1862,5460,1872],{"class":1871},[1862,5462,1155],{"class":1875},[1862,5464,1878],{"class":1871},[1862,5466,5467],{"class":1881},"\"pattern\"",[1862,5469,2474],{"class":1871},[1862,5471,5472],{"class":1864,"line":19},[1862,5473,5474],{"class":1888},"// \"code\" ~ :pattern\n",[1862,5476,5477],{"class":1864,"line":40},[1862,5478,1913],{"emptyLinePlaceholder":1912},[1862,5480,5481,5483,5485,5487,5489,5491,5493,5495,5497,5499,5501,5504],{"class":1864,"line":1916},[1862,5482,4672],{"class":1867},[1862,5484,1964],{"class":1867},[1862,5486,4677],{"class":2134},[1862,5488,4680],{"class":1871},[1862,5490,4683],{"class":2341},[1862,5492,4686],{"class":1871},[1862,5494,4689],{"class":2341},[1862,5496,4692],{"class":1871},[1862,5498,5467],{"class":1881},[1862,5500,4697],{"class":1871},[1862,5502,5503],{"class":1881}," \"^[A-Z]{3}-[0-9]+$\"",[1862,5505,2931],{"class":1871},[1848,5507,1624],{"id":5508},"postgresql-array-operators",[1774,5510,5511],{},"Array containment and overlap operators.",[2936,5513,5514,5526],{},[2939,5515,5516],{},[2942,5517,5518,5520,5522,5524],{},[2945,5519,4320],{},[2945,5521,2891],{},[2945,5523,2950],{},[2945,5525,672],{},[2955,5527,5528,5548,5568],{},[2942,5529,5530,5535,5540,5543],{},[2960,5531,5532],{},[1859,5533,5534],{},"ArrayContains",[2960,5536,5537],{},[1859,5538,5539],{},"@>",[2960,5541,5542],{},"Array contains",[2960,5544,5545],{},[1859,5546,5547],{},"\"tags\" @> :required_tags",[2942,5549,5550,5555,5560,5563],{},[2960,5551,5552],{},[1859,5553,5554],{},"ArrayContainedBy",[2960,5556,5557],{},[1859,5558,5559],{},"\u003C@",[2960,5561,5562],{},"Array is contained by",[2960,5564,5565],{},[1859,5566,5567],{},"\"permissions\" \u003C@ :allowed",[2942,5569,5570,5575,5580,5583],{},[2960,5571,5572],{},[1859,5573,5574],{},"ArrayOverlap",[2960,5576,5577],{},[1859,5578,5579],{},"&&",[2960,5581,5582],{},"Arrays overlap",[2960,5584,5585],{},[1859,5586,5587],{},"\"categories\" && :search_cats",[3358,5589,257],{"id":5590},"usage-6",[1853,5592,5594],{"className":1855,"code":5593,"language":1857,"meta":34,"style":34},"instance.C(instance.F(\"tags\"), astql.ArrayContains, instance.P(\"required\"))\n// \"tags\" @> :required\n\nparams := map[string]any{\n    \"required\": pq.Array([]string{\"featured\", \"published\"}),\n}\n",[1859,5595,5596,5640,5645,5649,5667,5696],{"__ignoreMap":34},[1862,5597,5598,5600,5602,5604,5606,5608,5610,5612,5614,5617,5619,5621,5623,5625,5627,5629,5631,5633,5635,5638],{"class":1864,"line":9},[1862,5599,1868],{"class":1867},[1862,5601,1872],{"class":1871},[1862,5603,1165],{"class":1875},[1862,5605,1878],{"class":1871},[1862,5607,1868],{"class":1867},[1862,5609,1872],{"class":1871},[1862,5611,1145],{"class":1875},[1862,5613,1878],{"class":1871},[1862,5615,5616],{"class":1881},"\"tags\"",[1862,5618,2008],{"class":1871},[1862,5620,1967],{"class":1867},[1862,5622,1872],{"class":1871},[1862,5624,5534],{"class":1867},[1862,5626,2060],{"class":1871},[1862,5628,2011],{"class":1867},[1862,5630,1872],{"class":1871},[1862,5632,1155],{"class":1875},[1862,5634,1878],{"class":1871},[1862,5636,5637],{"class":1881},"\"required\"",[1862,5639,2474],{"class":1871},[1862,5641,5642],{"class":1864,"line":19},[1862,5643,5644],{"class":1888},"// \"tags\" @> :required\n",[1862,5646,5647],{"class":1864,"line":40},[1862,5648,1913],{"emptyLinePlaceholder":1912},[1862,5650,5651,5653,5655,5657,5659,5661,5663,5665],{"class":1864,"line":1916},[1862,5652,4672],{"class":1867},[1862,5654,1964],{"class":1867},[1862,5656,4677],{"class":2134},[1862,5658,4680],{"class":1871},[1862,5660,4683],{"class":2341},[1862,5662,4686],{"class":1871},[1862,5664,4689],{"class":2341},[1862,5666,5040],{"class":1871},[1862,5668,5669,5672,5674,5676,5678,5680,5682,5684,5686,5689,5691,5694],{"class":1864,"line":1934},[1862,5670,5671],{"class":1881},"    \"required\"",[1862,5673,4697],{"class":1871},[1862,5675,5050],{"class":1867},[1862,5677,1872],{"class":1871},[1862,5679,5055],{"class":1875},[1862,5681,5058],{"class":1871},[1862,5683,4683],{"class":2341},[1862,5685,4692],{"class":1871},[1862,5687,5688],{"class":1881},"\"featured\"",[1862,5690,2060],{"class":1871},[1862,5692,5693],{"class":1881}," \"published\"",[1862,5695,5079],{"class":1871},[1862,5697,5698],{"class":1864,"line":1953},[1862,5699,2931],{"class":1871},[1848,5701,1633],{"id":5702},"jsonb-field-access-postgresql",[1774,5704,5705],{},"Access JSONB object keys directly in queries. Keys are parameterized for SQL injection safety.",[2936,5707,5708,5721],{},[2939,5709,5710],{},[2942,5711,5712,5715,5717,5719],{},[2945,5713,5714],{},"Method",[2945,5716,2891],{},[2945,5718,2950],{},[2945,5720,672],{},[2955,5722,5723,5743],{},[2942,5724,5725,5730,5735,5738],{},[2960,5726,5727],{},[1859,5728,5729],{},"JSONBText()",[2960,5731,5732],{},[1859,5733,5734],{},"->>",[2960,5736,5737],{},"Extract as text",[2960,5739,5740],{},[1859,5741,5742],{},"\"metadata\"->>:key_param",[2942,5744,5745,5750,5755,5758],{},[2960,5746,5747],{},[1859,5748,5749],{},"JSONBPath()",[2960,5751,5752],{},[1859,5753,5754],{},"->",[2960,5756,5757],{},"Extract as JSON",[2960,5759,5760],{},[1859,5761,5762],{},"\"metadata\"->:key_param",[3358,5764,257],{"id":5765},"usage-7",[1853,5767,5769],{"className":1855,"code":5768,"language":1857,"meta":34,"style":34},"// Text extraction (returns string) - key is parameterized\nstatusField := instance.JSONBText(instance.F(\"metadata\"), instance.P(\"status_key\"))\n// \"metadata\"->>:status_key\n\n// Path access (returns JSONB, use with array operators)\ntagsField := instance.JSONBPath(instance.F(\"metadata\"), instance.P(\"tags_key\"))\n// \"metadata\"->:tags_key\n\n// In WHERE clause\ninstance.C(statusField, astql.EQ, instance.P(\"status_value\"))\n// \"metadata\"->>:status_key = :status_value\n\n// JSONB array containment\ninstance.C(tagsField, astql.ArrayContains, instance.P(\"required_tags\"))\n// \"metadata\"->:tags_key @> :required_tags\n",[1859,5770,5771,5776,5817,5822,5826,5831,5871,5876,5880,5885,5920,5925,5929,5934,5969],{"__ignoreMap":34},[1862,5772,5773],{"class":1864,"line":9},[1862,5774,5775],{"class":1888},"// Text extraction (returns string) - key is parameterized\n",[1862,5777,5778,5781,5783,5785,5787,5789,5791,5793,5795,5797,5799,5802,5804,5806,5808,5810,5812,5815],{"class":1864,"line":19},[1862,5779,5780],{"class":1867},"statusField",[1862,5782,1964],{"class":1867},[1862,5784,2011],{"class":1867},[1862,5786,1872],{"class":1871},[1862,5788,1220],{"class":1875},[1862,5790,1878],{"class":1871},[1862,5792,1868],{"class":1867},[1862,5794,1872],{"class":1871},[1862,5796,1145],{"class":1875},[1862,5798,1878],{"class":1871},[1862,5800,5801],{"class":1881},"\"metadata\"",[1862,5803,2008],{"class":1871},[1862,5805,2011],{"class":1867},[1862,5807,1872],{"class":1871},[1862,5809,1155],{"class":1875},[1862,5811,1878],{"class":1871},[1862,5813,5814],{"class":1881},"\"status_key\"",[1862,5816,2474],{"class":1871},[1862,5818,5819],{"class":1864,"line":40},[1862,5820,5821],{"class":1888},"// \"metadata\"->>:status_key\n",[1862,5823,5824],{"class":1864,"line":1916},[1862,5825,1913],{"emptyLinePlaceholder":1912},[1862,5827,5828],{"class":1864,"line":1934},[1862,5829,5830],{"class":1888},"// Path access (returns JSONB, use with array operators)\n",[1862,5832,5833,5836,5838,5840,5842,5844,5846,5848,5850,5852,5854,5856,5858,5860,5862,5864,5866,5869],{"class":1864,"line":1953},[1862,5834,5835],{"class":1867},"tagsField",[1862,5837,1964],{"class":1867},[1862,5839,2011],{"class":1867},[1862,5841,1872],{"class":1871},[1862,5843,1225],{"class":1875},[1862,5845,1878],{"class":1871},[1862,5847,1868],{"class":1867},[1862,5849,1872],{"class":1871},[1862,5851,1145],{"class":1875},[1862,5853,1878],{"class":1871},[1862,5855,5801],{"class":1881},[1862,5857,2008],{"class":1871},[1862,5859,2011],{"class":1867},[1862,5861,1872],{"class":1871},[1862,5863,1155],{"class":1875},[1862,5865,1878],{"class":1871},[1862,5867,5868],{"class":1881},"\"tags_key\"",[1862,5870,2474],{"class":1871},[1862,5872,5873],{"class":1864,"line":1958},[1862,5874,5875],{"class":1888},"// \"metadata\"->:tags_key\n",[1862,5877,5878],{"class":1864,"line":1989},[1862,5879,1913],{"emptyLinePlaceholder":1912},[1862,5881,5882],{"class":1864,"line":2024},[1862,5883,5884],{"class":1888},"// In WHERE clause\n",[1862,5886,5887,5889,5891,5893,5895,5897,5899,5901,5903,5905,5907,5909,5911,5913,5915,5918],{"class":1864,"line":2077},[1862,5888,1868],{"class":1867},[1862,5890,1872],{"class":1871},[1862,5892,1165],{"class":1875},[1862,5894,1878],{"class":1871},[1862,5896,5780],{"class":1867},[1862,5898,2060],{"class":1871},[1862,5900,1967],{"class":1867},[1862,5902,1872],{"class":1871},[1862,5904,2057],{"class":1867},[1862,5906,2060],{"class":1871},[1862,5908,2011],{"class":1867},[1862,5910,1872],{"class":1871},[1862,5912,1155],{"class":1875},[1862,5914,1878],{"class":1871},[1862,5916,5917],{"class":1881},"\"status_value\"",[1862,5919,2474],{"class":1871},[1862,5921,5922],{"class":1864,"line":2082},[1862,5923,5924],{"class":1888},"// \"metadata\"->>:status_key = :status_value\n",[1862,5926,5927],{"class":1864,"line":2115},[1862,5928,1913],{"emptyLinePlaceholder":1912},[1862,5930,5931],{"class":1864,"line":2423},[1862,5932,5933],{"class":1888},"// JSONB array containment\n",[1862,5935,5936,5938,5940,5942,5944,5946,5948,5950,5952,5954,5956,5958,5960,5962,5964,5967],{"class":1864,"line":2444},[1862,5937,1868],{"class":1867},[1862,5939,1872],{"class":1871},[1862,5941,1165],{"class":1875},[1862,5943,1878],{"class":1871},[1862,5945,5835],{"class":1867},[1862,5947,2060],{"class":1871},[1862,5949,1967],{"class":1867},[1862,5951,1872],{"class":1871},[1862,5953,5534],{"class":1867},[1862,5955,2060],{"class":1871},[1862,5957,2011],{"class":1867},[1862,5959,1872],{"class":1871},[1862,5961,1155],{"class":1875},[1862,5963,1878],{"class":1871},[1862,5965,5966],{"class":1881},"\"required_tags\"",[1862,5968,2474],{"class":1871},[1862,5970,5971],{"class":1864,"line":2477},[1862,5972,5973],{"class":1888},"// \"metadata\"->:tags_key @> :required_tags\n",[1774,5975,5976],{},"Both the JSONB key and the comparison value are parameterized, preventing SQL injection.",[1848,5978,1642],{"id":5979},"vector-operators-pgvector",[1774,5981,5982],{},"Distance operators for vector similarity search.",[2936,5984,5985,5998],{},[2939,5986,5987],{},[2942,5988,5989,5991,5993,5995],{},[2945,5990,4320],{},[2945,5992,2891],{},[2945,5994,2950],{},[2945,5996,5997],{},"Use Case",[2955,5999,6000,6018,6036,6054],{},[2942,6001,6002,6007,6012,6015],{},[2960,6003,6004],{},[1859,6005,6006],{},"VectorL2Distance",[2960,6008,6009],{},[1859,6010,6011],{},"\u003C->",[2960,6013,6014],{},"Euclidean (L2) distance",[2960,6016,6017],{},"General similarity",[2942,6019,6020,6025,6030,6033],{},[2960,6021,6022],{},[1859,6023,6024],{},"VectorCosineDistance",[2960,6026,6027],{},[1859,6028,6029],{},"\u003C=>",[2960,6031,6032],{},"Cosine distance",[2960,6034,6035],{},"Text embeddings",[2942,6037,6038,6043,6048,6051],{},[2960,6039,6040],{},[1859,6041,6042],{},"VectorInnerProduct",[2960,6044,6045],{},[1859,6046,6047],{},"\u003C#>",[2960,6049,6050],{},"Negative inner product",[2960,6052,6053],{},"Maximum similarity",[2942,6055,6056,6061,6066,6069],{},[2960,6057,6058],{},[1859,6059,6060],{},"VectorL1Distance",[2960,6062,6063],{},[1859,6064,6065],{},"\u003C+>",[2960,6067,6068],{},"Manhattan (L1) distance",[2960,6070,6071],{},"Sparse vectors",[3358,6073,257],{"id":6074},"usage-8",[1853,6076,6078],{"className":1855,"code":6077,"language":1857,"meta":34,"style":34},"// In WHERE clause\ninstance.C(instance.F(\"embedding\"), astql.VectorL2Distance, instance.P(\"query\"))\n// \"embedding\" \u003C-> :query\n\n// In ORDER BY (most common)\nquery.OrderByExpr(\n    instance.F(\"embedding\"),\n    astql.VectorCosineDistance,\n    instance.P(\"query_embedding\"),\n    astql.ASC,\n)\n// ORDER BY \"embedding\" \u003C=> :query_embedding ASC\n",[1859,6079,6080,6084,6128,6133,6137,6142,6153,6168,6180,6195,6205,6209],{"__ignoreMap":34},[1862,6081,6082],{"class":1864,"line":9},[1862,6083,5884],{"class":1888},[1862,6085,6086,6088,6090,6092,6094,6096,6098,6100,6102,6105,6107,6109,6111,6113,6115,6117,6119,6121,6123,6126],{"class":1864,"line":19},[1862,6087,1868],{"class":1867},[1862,6089,1872],{"class":1871},[1862,6091,1165],{"class":1875},[1862,6093,1878],{"class":1871},[1862,6095,1868],{"class":1867},[1862,6097,1872],{"class":1871},[1862,6099,1145],{"class":1875},[1862,6101,1878],{"class":1871},[1862,6103,6104],{"class":1881},"\"embedding\"",[1862,6106,2008],{"class":1871},[1862,6108,1967],{"class":1867},[1862,6110,1872],{"class":1871},[1862,6112,6006],{"class":1867},[1862,6114,2060],{"class":1871},[1862,6116,2011],{"class":1867},[1862,6118,1872],{"class":1871},[1862,6120,1155],{"class":1875},[1862,6122,1878],{"class":1871},[1862,6124,6125],{"class":1881},"\"query\"",[1862,6127,2474],{"class":1871},[1862,6129,6130],{"class":1864,"line":40},[1862,6131,6132],{"class":1888},"// \"embedding\" \u003C-> :query\n",[1862,6134,6135],{"class":1864,"line":1916},[1862,6136,1913],{"emptyLinePlaceholder":1912},[1862,6138,6139],{"class":1864,"line":1934},[1862,6140,6141],{"class":1888},"// In ORDER BY (most common)\n",[1862,6143,6144,6146,6148,6150],{"class":1864,"line":1953},[1862,6145,1961],{"class":1867},[1862,6147,1872],{"class":1871},[1862,6149,1287],{"class":1875},[1862,6151,6152],{"class":1871},"(\n",[1862,6154,6155,6157,6159,6161,6163,6165],{"class":1864,"line":1958},[1862,6156,2591],{"class":1867},[1862,6158,1872],{"class":1871},[1862,6160,1145],{"class":1875},[1862,6162,1878],{"class":1871},[1862,6164,6104],{"class":1881},[1862,6166,6167],{"class":1871},"),\n",[1862,6169,6170,6173,6175,6177],{"class":1864,"line":1989},[1862,6171,6172],{"class":1867},"    astql",[1862,6174,1872],{"class":1871},[1862,6176,6024],{"class":1867},[1862,6178,6179],{"class":1871},",\n",[1862,6181,6182,6184,6186,6188,6190,6193],{"class":1864,"line":2024},[1862,6183,2591],{"class":1867},[1862,6185,1872],{"class":1871},[1862,6187,1155],{"class":1875},[1862,6189,1878],{"class":1871},[1862,6191,6192],{"class":1881},"\"query_embedding\"",[1862,6194,6167],{"class":1871},[1862,6196,6197,6199,6201,6203],{"class":1864,"line":2077},[1862,6198,6172],{"class":1867},[1862,6200,1872],{"class":1871},[1862,6202,2799],{"class":1867},[1862,6204,6179],{"class":1871},[1862,6206,6207],{"class":1864,"line":2082},[1862,6208,2164],{"class":1871},[1862,6210,6211],{"class":1864,"line":2115},[1862,6212,6213],{"class":1888},"// ORDER BY \"embedding\" \u003C=> :query_embedding ASC\n",[3358,6215,1651],{"id":6216},"with-pgvector",[1853,6218,6220],{"className":1855,"code":6219,"language":1857,"meta":34,"style":34},"import \"github.com/pgvector/pgvector-go\"\n\nembedding := pgvector.NewVector([]float32{0.1, 0.2, 0.3, ...})\nparams := map[string]any{\"query_embedding\": embedding}\n",[1859,6221,6222,6229,6233,6276],{"__ignoreMap":34},[1862,6223,6224,6226],{"class":1864,"line":9},[1862,6225,2135],{"class":2134},[1862,6227,6228],{"class":1881}," \"github.com/pgvector/pgvector-go\"\n",[1862,6230,6231],{"class":1864,"line":19},[1862,6232,1913],{"emptyLinePlaceholder":1912},[1862,6234,6235,6238,6240,6243,6245,6248,6250,6253,6255,6258,6260,6263,6265,6268,6270,6273],{"class":1864,"line":40},[1862,6236,6237],{"class":1867},"embedding",[1862,6239,1964],{"class":1867},[1862,6241,6242],{"class":1867}," pgvector",[1862,6244,1872],{"class":1871},[1862,6246,6247],{"class":1875},"NewVector",[1862,6249,5058],{"class":1871},[1862,6251,6252],{"class":2341},"float32",[1862,6254,4692],{"class":1871},[1862,6256,6257],{"class":2813},"0.1",[1862,6259,2060],{"class":1871},[1862,6261,6262],{"class":2813}," 0.2",[1862,6264,2060],{"class":1871},[1862,6266,6267],{"class":2813}," 0.3",[1862,6269,2060],{"class":1871},[1862,6271,6272],{"class":2617}," ...",[1862,6274,6275],{"class":1871},"})\n",[1862,6277,6278,6280,6282,6284,6286,6288,6290,6292,6294,6296,6298,6301],{"class":1864,"line":1916},[1862,6279,4672],{"class":1867},[1862,6281,1964],{"class":1867},[1862,6283,4677],{"class":2134},[1862,6285,4680],{"class":1871},[1862,6287,4683],{"class":2341},[1862,6289,4686],{"class":1871},[1862,6291,4689],{"class":2341},[1862,6293,4692],{"class":1871},[1862,6295,6192],{"class":1881},[1862,6297,4697],{"class":1871},[1862,6299,6300],{"class":1867}," embedding",[1862,6302,2931],{"class":1871},[1848,6304,1656],{"id":6305},"operator-selection-guide",[2936,6307,6308,6318],{},[2939,6309,6310],{},[2942,6311,6312,6315],{},[2945,6313,6314],{},"Need",[2945,6316,6317],{},"Operator",[2955,6319,6320,6329,6338,6354,6365,6376,6387,6398,6409,6420],{},[2942,6321,6322,6325],{},[2960,6323,6324],{},"Exact match",[2960,6326,6327],{},[1859,6328,2057],{},[2942,6330,6331,6334],{},[2960,6332,6333],{},"Not equal",[2960,6335,6336],{},[1859,6337,4354],{},[2942,6339,6340,6343],{},[2960,6341,6342],{},"Range check",[2960,6344,6345,6347,6348,6347,6350,6347,6352],{},[1859,6346,4374],{},", ",[1859,6349,4394],{},[1859,6351,4414],{},[1859,6353,4434],{},[2942,6355,6356,6359],{},[2960,6357,6358],{},"Pattern search",[2960,6360,6361,6347,6363],{},[1859,6362,4531],{},[1859,6364,4570],{},[2942,6366,6367,6370],{},[2960,6368,6369],{},"NULL check",[2960,6371,6372,6347,6374],{},[1859,6373,4731],{},[1859,6375,4751],{},[2942,6377,6378,6381],{},[2960,6379,6380],{},"List membership",[2960,6382,6383,6347,6385],{},[1859,6384,4860],{},[1859,6386,4880],{},[2942,6388,6389,6392],{},[2960,6390,6391],{},"Regex match",[2960,6393,6394,6347,6396],{},[1859,6395,5344],{},[1859,6397,5364],{},[2942,6399,6400,6403],{},[2960,6401,6402],{},"Array containment",[2960,6404,6405,6347,6407],{},[1859,6406,5534],{},[1859,6408,5554],{},[2942,6410,6411,6414],{},[2960,6412,6413],{},"Vector similarity",[2960,6415,6416,6347,6418],{},[1859,6417,6006],{},[1859,6419,6024],{},[2942,6421,6422,6425],{},[2960,6423,6424],{},"Subquery check",[2960,6426,6427,6347,6429],{},[1859,6428,478],{},[1859,6430,5179],{},[1848,6432,584],{"id":6433},"aggregate-functions",[1774,6435,6436,6437,6439],{},"For use with ",[1859,6438,1357],{}," and aggregate expressions.",[2936,6441,6442,6452],{},[2939,6443,6444],{},[2942,6445,6446,6448,6450],{},[2945,6447,4320],{},[2945,6449,2891],{},[2945,6451,2950],{},[2955,6453,6454,6469,6484,6499,6514,6529],{},[2942,6455,6456,6461,6466],{},[2960,6457,6458],{},[1859,6459,6460],{},"AggSum",[2960,6462,6463],{},[1859,6464,6465],{},"SUM()",[2960,6467,6468],{},"Sum of values",[2942,6470,6471,6476,6481],{},[2960,6472,6473],{},[1859,6474,6475],{},"AggAvg",[2960,6477,6478],{},[1859,6479,6480],{},"AVG()",[2960,6482,6483],{},"Average of values",[2942,6485,6486,6491,6496],{},[2960,6487,6488],{},[1859,6489,6490],{},"AggMin",[2960,6492,6493],{},[1859,6494,6495],{},"MIN()",[2960,6497,6498],{},"Minimum value",[2942,6500,6501,6506,6511],{},[2960,6502,6503],{},[1859,6504,6505],{},"AggMax",[2960,6507,6508],{},[1859,6509,6510],{},"MAX()",[2960,6512,6513],{},"Maximum value",[2942,6515,6516,6521,6526],{},[2960,6517,6518],{},[1859,6519,6520],{},"AggCountField",[2960,6522,6523],{},[1859,6524,6525],{},"COUNT()",[2960,6527,6528],{},"Count of values",[2942,6530,6531,6536,6541],{},[2960,6532,6533],{},[1859,6534,6535],{},"AggCountDistinct",[2960,6537,6538],{},[1859,6539,6540],{},"COUNT(DISTINCT)",[2960,6542,6543],{},"Count of unique values",[1848,6545,633],{"id":6546},"window-functions",[1774,6548,6549],{},"For use with window expressions.",[2936,6551,6552,6562],{},[2939,6553,6554],{},[2942,6555,6556,6558,6560],{},[2945,6557,4320],{},[2945,6559,2891],{},[2945,6561,2950],{},[2955,6563,6564,6579,6594,6609,6624,6639,6654,6669],{},[2942,6565,6566,6571,6576],{},[2960,6567,6568],{},[1859,6569,6570],{},"WinRowNumber",[2960,6572,6573],{},[1859,6574,6575],{},"ROW_NUMBER()",[2960,6577,6578],{},"Sequential row number",[2942,6580,6581,6586,6591],{},[2960,6582,6583],{},[1859,6584,6585],{},"WinRank",[2960,6587,6588],{},[1859,6589,6590],{},"RANK()",[2960,6592,6593],{},"Rank with gaps",[2942,6595,6596,6601,6606],{},[2960,6597,6598],{},[1859,6599,6600],{},"WinDenseRank",[2960,6602,6603],{},[1859,6604,6605],{},"DENSE_RANK()",[2960,6607,6608],{},"Rank without gaps",[2942,6610,6611,6616,6621],{},[2960,6612,6613],{},[1859,6614,6615],{},"WinNtile",[2960,6617,6618],{},[1859,6619,6620],{},"NTILE(:param)",[2960,6622,6623],{},"Divide into n buckets",[2942,6625,6626,6631,6636],{},[2960,6627,6628],{},[1859,6629,6630],{},"WinLag",[2960,6632,6633],{},[1859,6634,6635],{},"LAG()",[2960,6637,6638],{},"Previous row value",[2942,6640,6641,6646,6651],{},[2960,6642,6643],{},[1859,6644,6645],{},"WinLead",[2960,6647,6648],{},[1859,6649,6650],{},"LEAD()",[2960,6652,6653],{},"Next row value",[2942,6655,6656,6661,6666],{},[2960,6657,6658],{},[1859,6659,6660],{},"WinFirstValue",[2960,6662,6663],{},[1859,6664,6665],{},"FIRST_VALUE()",[2960,6667,6668],{},"First value in window",[2942,6670,6671,6676,6681],{},[2960,6672,6673],{},[1859,6674,6675],{},"WinLastValue",[2960,6677,6678],{},[1859,6679,6680],{},"LAST_VALUE()",[2960,6682,6683],{},"Last value in window",[1848,6685,658],{"id":6686},"frame-bounds",[1774,6688,6689],{},"For window frame specifications.",[2936,6691,6692,6700],{},[2939,6693,6694],{},[2942,6695,6696,6698],{},[2945,6697,4320],{},[2945,6699,2891],{},[2955,6701,6702,6714,6726],{},[2942,6703,6704,6709],{},[2960,6705,6706],{},[1859,6707,6708],{},"FrameUnboundedPreceding",[2960,6710,6711],{},[1859,6712,6713],{},"UNBOUNDED PRECEDING",[2942,6715,6716,6721],{},[2960,6717,6718],{},[1859,6719,6720],{},"FrameCurrentRow",[2960,6722,6723],{},[1859,6724,6725],{},"CURRENT ROW",[2942,6727,6728,6733],{},[2960,6729,6730],{},[1859,6731,6732],{},"FrameUnboundedFollowing",[2960,6734,6735],{},[1859,6736,6737],{},"UNBOUNDED FOLLOWING",[1848,6739,1673],{"id":6740},"cast-types",[1774,6742,6743,6744,1872],{},"For type casting with ",[1859,6745,6746],{},"Cast()",[2936,6748,6749,6758],{},[2939,6750,6751],{},[2942,6752,6753,6755],{},[2945,6754,4320],{},[2945,6756,6757],{},"PostgreSQL Type",[2955,6759,6760,6772,6784,6796,6808,6820,6832,6844,6856,6868,6880,6892,6904,6916,6928,6940,6952],{},[2942,6761,6762,6767],{},[2960,6763,6764],{},[1859,6765,6766],{},"CastText",[2960,6768,6769],{},[1859,6770,6771],{},"TEXT",[2942,6773,6774,6779],{},[2960,6775,6776],{},[1859,6777,6778],{},"CastInteger",[2960,6780,6781],{},[1859,6782,6783],{},"INTEGER",[2942,6785,6786,6791],{},[2960,6787,6788],{},[1859,6789,6790],{},"CastBigint",[2960,6792,6793],{},[1859,6794,6795],{},"BIGINT",[2942,6797,6798,6803],{},[2960,6799,6800],{},[1859,6801,6802],{},"CastSmallint",[2960,6804,6805],{},[1859,6806,6807],{},"SMALLINT",[2942,6809,6810,6815],{},[2960,6811,6812],{},[1859,6813,6814],{},"CastNumeric",[2960,6816,6817],{},[1859,6818,6819],{},"NUMERIC",[2942,6821,6822,6827],{},[2960,6823,6824],{},[1859,6825,6826],{},"CastReal",[2960,6828,6829],{},[1859,6830,6831],{},"REAL",[2942,6833,6834,6839],{},[2960,6835,6836],{},[1859,6837,6838],{},"CastDoublePrecision",[2960,6840,6841],{},[1859,6842,6843],{},"DOUBLE PRECISION",[2942,6845,6846,6851],{},[2960,6847,6848],{},[1859,6849,6850],{},"CastBoolean",[2960,6852,6853],{},[1859,6854,6855],{},"BOOLEAN",[2942,6857,6858,6863],{},[2960,6859,6860],{},[1859,6861,6862],{},"CastDate",[2960,6864,6865],{},[1859,6866,6867],{},"DATE",[2942,6869,6870,6875],{},[2960,6871,6872],{},[1859,6873,6874],{},"CastTime",[2960,6876,6877],{},[1859,6878,6879],{},"TIME",[2942,6881,6882,6887],{},[2960,6883,6884],{},[1859,6885,6886],{},"CastTimestamp",[2960,6888,6889],{},[1859,6890,6891],{},"TIMESTAMP",[2942,6893,6894,6899],{},[2960,6895,6896],{},[1859,6897,6898],{},"CastTimestampTZ",[2960,6900,6901],{},[1859,6902,6903],{},"TIMESTAMPTZ",[2942,6905,6906,6911],{},[2960,6907,6908],{},[1859,6909,6910],{},"CastInterval",[2960,6912,6913],{},[1859,6914,6915],{},"INTERVAL",[2942,6917,6918,6923],{},[2960,6919,6920],{},[1859,6921,6922],{},"CastUUID",[2960,6924,6925],{},[1859,6926,6927],{},"UUID",[2942,6929,6930,6935],{},[2960,6931,6932],{},[1859,6933,6934],{},"CastJSON",[2960,6936,6937],{},[1859,6938,6939],{},"JSON",[2942,6941,6942,6947],{},[2960,6943,6944],{},[1859,6945,6946],{},"CastJSONB",[2960,6948,6949],{},[1859,6950,6951],{},"JSONB",[2942,6953,6954,6959],{},[2960,6955,6956],{},[1859,6957,6958],{},"CastBytea",[2960,6960,6961],{},[1859,6962,6963],{},"BYTEA",[3483,6965,6966],{},"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 .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 .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 .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}",{"title":34,"searchDepth":19,"depth":19,"links":6968},[6969,6972,6975,6978,6982,6985,6988,6991,6994,6998,6999,7000,7001,7002],{"id":4308,"depth":19,"text":417,"children":6970},[6971],{"id":4450,"depth":40,"text":257},{"id":4505,"depth":19,"text":422,"children":6973},[6974],{"id":4605,"depth":40,"text":257},{"id":4705,"depth":19,"text":1584,"children":6976},[6977],{"id":4767,"depth":40,"text":257},{"id":4834,"depth":19,"text":432,"children":6979},[6980,6981],{"id":4896,"depth":40,"text":257},{"id":5005,"depth":40,"text":1601},{"id":5115,"depth":19,"text":1606,"children":6983},[6984],{"id":5189,"depth":40,"text":257},{"id":5318,"depth":19,"text":1615,"children":6986},[6987],{"id":5420,"depth":40,"text":257},{"id":5508,"depth":19,"text":1624,"children":6989},[6990],{"id":5590,"depth":40,"text":257},{"id":5702,"depth":19,"text":1633,"children":6992},[6993],{"id":5765,"depth":40,"text":257},{"id":5979,"depth":19,"text":1642,"children":6995},[6996,6997],{"id":6074,"depth":40,"text":257},{"id":6216,"depth":40,"text":1651},{"id":6305,"depth":19,"text":1656},{"id":6433,"depth":19,"text":584},{"id":6546,"depth":19,"text":633},{"id":6686,"depth":19,"text":658},{"id":6740,"depth":19,"text":1673},{},"2025-12-13T00:00:00.000Z",null,{"title":1559,"description":1561},[1725,125,7008],"Comparison","4BDp5freddhHVNMPnHmR-JljpJo16muuerbZuFlDJA4",[7011,7005],{"title":1113,"path":1112,"stem":1730,"description":1115,"children":-1},1776287738707]