metal-orm 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +299 -113
  2. package/docs/CHANGES.md +104 -0
  3. package/docs/advanced-features.md +92 -1
  4. package/docs/api-reference.md +13 -4
  5. package/docs/dml-operations.md +156 -0
  6. package/docs/getting-started.md +122 -55
  7. package/docs/hydration.md +78 -13
  8. package/docs/index.md +19 -14
  9. package/docs/multi-dialect-support.md +25 -0
  10. package/docs/query-builder.md +60 -0
  11. package/docs/runtime.md +105 -0
  12. package/docs/schema-definition.md +52 -1
  13. package/package.json +1 -1
  14. package/src/ast/expression.ts +38 -18
  15. package/src/builder/hydration-planner.ts +74 -74
  16. package/src/builder/select.ts +427 -395
  17. package/src/constants/sql-operator-config.ts +3 -0
  18. package/src/constants/sql.ts +38 -32
  19. package/src/index.ts +16 -8
  20. package/src/playground/features/playground/data/scenarios/types.ts +18 -15
  21. package/src/playground/features/playground/data/schema.ts +10 -10
  22. package/src/playground/features/playground/services/QueryExecutionService.ts +2 -1
  23. package/src/runtime/entity-meta.ts +52 -0
  24. package/src/runtime/entity.ts +252 -0
  25. package/src/runtime/execute.ts +36 -0
  26. package/src/runtime/hydration.ts +99 -49
  27. package/src/runtime/lazy-batch.ts +205 -0
  28. package/src/runtime/orm-context.ts +539 -0
  29. package/src/runtime/relations/belongs-to.ts +92 -0
  30. package/src/runtime/relations/has-many.ts +111 -0
  31. package/src/runtime/relations/many-to-many.ts +149 -0
  32. package/src/schema/column.ts +15 -1
  33. package/src/schema/relation.ts +82 -58
  34. package/src/schema/table.ts +34 -22
  35. package/src/schema/types.ts +76 -0
  36. package/tests/orm-runtime.test.ts +254 -0
@@ -0,0 +1,156 @@
1
+ # DML Operations
2
+
3
+ MetalORM provides comprehensive support for Data Manipulation Language (DML) operations including INSERT, UPDATE, and DELETE queries.
4
+
5
+ ## INSERT Operations
6
+
7
+ The `InsertQueryBuilder` allows you to insert data into your tables with full type safety.
8
+
9
+ ### Basic Insert
10
+
11
+ ```typescript
12
+ import { InsertQueryBuilder } from 'metal-orm';
13
+
14
+ const query = new InsertQueryBuilder(users)
15
+ .values({
16
+ name: 'John Doe',
17
+ email: 'john@example.com',
18
+ createdAt: new Date()
19
+ });
20
+
21
+ const { sql, params } = query.compile(new MySqlDialect());
22
+ ```
23
+
24
+ ### Multi-row Insert
25
+
26
+ ```typescript
27
+ const query = new InsertQueryBuilder(users)
28
+ .values([
29
+ { name: 'John Doe', email: 'john@example.com' },
30
+ { name: 'Jane Smith', email: 'jane@example.com' }
31
+ ]);
32
+ ```
33
+
34
+ ### RETURNING Clause
35
+
36
+ Some databases support returning inserted data:
37
+
38
+ ```typescript
39
+ const query = new InsertQueryBuilder(users)
40
+ .values({ name: 'John Doe', email: 'john@example.com' })
41
+ .returning(users.columns.id, users.columns.name);
42
+ ```
43
+
44
+ ## UPDATE Operations
45
+
46
+ The `UpdateQueryBuilder` provides a fluent API for updating records.
47
+
48
+ ### Basic Update
49
+
50
+ ```typescript
51
+ const query = new UpdateQueryBuilder(users)
52
+ .set({ name: 'John Updated', email: 'john.updated@example.com' })
53
+ .where(eq(users.columns.id, 1));
54
+ ```
55
+
56
+ ### Conditional Update
57
+
58
+ ```typescript
59
+ const query = new UpdateQueryBuilder(users)
60
+ .set({ lastLogin: new Date() })
61
+ .where(and(
62
+ eq(users.columns.id, 1),
63
+ isNull(users.columns.deletedAt)
64
+ ));
65
+ ```
66
+
67
+ ### RETURNING Clause
68
+
69
+ ```typescript
70
+ const query = new UpdateQueryBuilder(users)
71
+ .set({ status: 'active' })
72
+ .where(eq(users.columns.id, 1))
73
+ .returning(users.columns.id, users.columns.status);
74
+ ```
75
+
76
+ ## DELETE Operations
77
+
78
+ The `DeleteQueryBuilder` allows you to delete records with safety.
79
+
80
+ ### Basic Delete
81
+
82
+ ```typescript
83
+ const query = new DeleteQueryBuilder(users)
84
+ .where(eq(users.columns.id, 1));
85
+ ```
86
+
87
+ ### Conditional Delete
88
+
89
+ ```typescript
90
+ const query = new DeleteQueryBuilder(users)
91
+ .where(and(
92
+ eq(users.columns.status, 'inactive'),
93
+ lt(users.columns.lastLogin, new Date('2023-01-01'))
94
+ ));
95
+ ```
96
+
97
+ ### RETURNING Clause
98
+
99
+ ```typescript
100
+ const query = new DeleteQueryBuilder(users)
101
+ .where(eq(users.columns.id, 1))
102
+ .returning(users.columns.id, users.columns.name);
103
+ ```
104
+
105
+ ## Multi-Dialect Support
106
+
107
+ All DML operations support the same multi-dialect compilation as SELECT queries:
108
+
109
+ ```typescript
110
+ // MySQL
111
+ const mysqlResult = query.compile(new MySqlDialect());
112
+
113
+ // SQLite
114
+ const sqliteResult = query.compile(new SQLiteDialect());
115
+
116
+ // SQL Server
117
+ const mssqlResult = query.compile(new MSSQLDialect());
118
+
119
+ // PostgreSQL
120
+ const postgresResult = query.compile(new PostgresDialect());
121
+ ```
122
+
123
+ ## Best Practices
124
+
125
+ 1. **Always use WHERE clauses**: For UPDATE and DELETE operations, always include WHERE clauses to avoid accidental mass updates/deletes.
126
+
127
+ 2. **Use RETURNING for verification**: When supported by your database, use RETURNING clauses to verify what was affected.
128
+
129
+ 3. **Batch operations**: For large datasets, consider batching INSERT operations to avoid parameter limits.
130
+
131
+ 4. **Transaction safety**: Wrap DML operations in transactions when performing multiple related operations.
132
+
133
+ ## Using the Unit of Work (optional)
134
+
135
+ If you're using `OrmContext`, you don't have to manually build `InsertQueryBuilder` / `UpdateQueryBuilder` / `DeleteQueryBuilder` for every change.
136
+
137
+ Instead, you can:
138
+
139
+ 1. Load entities via the query builder + `execute(ctx)`.
140
+ 2. Modify fields and relations in memory.
141
+ 3. Call `ctx.saveChanges()` once.
142
+
143
+ ```ts
144
+ const [user] = await new SelectQueryBuilder(users)
145
+ .select({ id: users.columns.id, name: users.columns.name })
146
+ .includeLazy('posts')
147
+ .where(eq(users.columns.id, 1))
148
+ .execute(ctx);
149
+
150
+ user.name = 'Updated Name';
151
+ user.posts.add({ title: 'New from runtime' });
152
+
153
+ await ctx.saveChanges();
154
+ ```
155
+
156
+ Internally, MetalORM uses the same DML ASTs and dialect compilers described above to generate INSERT, UPDATE, DELETE, and pivot operations.
@@ -1,25 +1,46 @@
1
1
  # Getting Started
2
2
 
3
- This guide will help you get started with MetalORM.
3
+ This guide walks you through:
4
4
 
5
- ## Installation
5
+ 1. Using MetalORM as a **simple query builder**.
6
+ 2. Hydrating relations into nested objects.
7
+ 3. Taking a first look at the **OrmContext** runtime.
6
8
 
7
- You can install MetalORM using npm, yarn, or pnpm:
9
+ ## 1. Installation
8
10
 
9
11
  ```bash
10
- # npm
11
12
  npm install metal-orm
12
-
13
- # yarn
13
+ # or
14
14
  yarn add metal-orm
15
-
16
- # pnpm
15
+ # or
17
16
  pnpm add metal-orm
18
17
  ```
19
18
 
20
- ## Quick Start
19
+ ## 2. Your first query (builder only)
20
+
21
+ ```typescript
22
+ import { defineTable, col, SelectQueryBuilder, eq, MySqlDialect } from 'metal-orm';
23
+
24
+ const todos = defineTable('todos', {
25
+ id: col.int().primaryKey(),
26
+ title: col.varchar(255).notNull(),
27
+ done: col.boolean().default(false),
28
+ });
29
+
30
+ const query = new SelectQueryBuilder(todos)
31
+ .select({
32
+ id: todos.columns.id,
33
+ title: todos.columns.title,
34
+ })
35
+ .where(eq(todos.columns.done, false));
36
+
37
+ const dialect = new MySqlDialect();
38
+ const { sql, params } = query.compile(dialect);
39
+
40
+ // Execute `sql` + `params` with your DB driver.
41
+ ```
21
42
 
22
- Here's a complete example to get you started:
43
+ ## 3. Adding relations & hydration
23
44
 
24
45
  ```typescript
25
46
  import {
@@ -29,59 +50,50 @@ import {
29
50
  SelectQueryBuilder,
30
51
  eq,
31
52
  count,
53
+ rowNumber,
32
54
  hydrateRows,
33
- MySqlDialect
34
55
  } from 'metal-orm';
35
56
 
36
- // 1. Define your schema
37
- const posts = defineTable(
38
- 'posts',
39
- {
40
- id: col.int().primaryKey(),
41
- title: col.varchar(255).notNull(),
42
- content: col.text(),
43
- userId: col.int().notNull(),
44
- createdAt: col.timestamp().default('CURRENT_TIMESTAMP'),
45
- updatedAt: col.timestamp()
46
- }
47
- );
57
+ const posts = defineTable('posts', {
58
+ id: col.int().primaryKey(),
59
+ title: col.varchar(255).notNull(),
60
+ userId: col.int().notNull(),
61
+ createdAt: col.timestamp().default('CURRENT_TIMESTAMP'),
62
+ });
48
63
 
49
- const users = defineTable(
50
- 'users',
51
- {
52
- id: col.int().primaryKey(),
53
- name: col.varchar(255).notNull(),
54
- email: col.varchar(255).unique(),
55
- createdAt: col.timestamp().default('CURRENT_TIMESTAMP')
56
- },
57
- {
58
- posts: hasMany(posts, 'userId')
59
- }
60
- );
64
+ const users = defineTable('users', {
65
+ id: col.int().primaryKey(),
66
+ name: col.varchar(255).notNull(),
67
+ email: col.varchar(255).unique(),
68
+ }, {
69
+ posts: hasMany(posts, 'userId'),
70
+ });
61
71
 
62
- // 2. Build your query
72
+ // Build a query with relation & window function
63
73
  const builder = new SelectQueryBuilder(users)
64
74
  .select({
65
75
  id: users.columns.id,
66
76
  name: users.columns.name,
67
77
  email: users.columns.email,
68
- totalPosts: count(posts.columns.id)
78
+ postCount: count(posts.columns.id),
79
+ rank: rowNumber(), // window function helper
69
80
  })
70
81
  .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
71
82
  .groupBy(users.columns.id, users.columns.name, users.columns.email)
72
83
  .orderBy(count(posts.columns.id), 'DESC')
73
- .limit(20)
84
+ .limit(10)
74
85
  .include('posts', {
75
- columns: [posts.columns.id, posts.columns.title, posts.columns.createdAt]
76
- });
86
+ columns: [posts.columns.id, posts.columns.title, posts.columns.createdAt],
87
+ }); // eager relation for hydration
77
88
 
78
- // 3. Compile to SQL
79
- const dialect = new MySqlDialect();
80
89
  const { sql, params } = builder.compile(dialect);
90
+ const [rows] = await connection.execute(sql, params);
81
91
 
82
- // 4. Execute and hydrate
83
- const rows = await connection.execute(sql, params);
84
- const hydrated = hydrateRows(rows, builder.getHydrationPlan());
92
+ // Turn flat rows into nested objects
93
+ const hydrated = hydrateRows(
94
+ rows as Record<string, unknown>[],
95
+ builder.getHydrationPlan(),
96
+ );
85
97
 
86
98
  console.log(hydrated);
87
99
  // [
@@ -89,16 +101,71 @@ console.log(hydrated);
89
101
  // id: 1,
90
102
  // name: 'John Doe',
91
103
  // email: 'john@example.com',
92
- // totalPosts: 15,
104
+ // postCount: 15,
105
+ // rank: 1,
93
106
  // posts: [
94
- // {
95
- // id: 101,
96
- // title: 'Latest Post',
97
- // createdAt: '2023-05-15T10:00:00.000Z'
98
- // },
99
- // // ... more posts
100
- // ]
101
- // }
102
- // // ... more users
107
+ // { id: 101, title: 'Latest Post', createdAt: '2023-05-15T10:00:00Z' },
108
+ // // ...
109
+ // ],
110
+ // },
111
+ // // ...
103
112
  // ]
104
113
  ```
114
+
115
+ ## 4. A taste of the runtime (optional)
116
+
117
+ When you're ready to let MetalORM manage entities and relations, you can use the OrmContext:
118
+
119
+ ```typescript
120
+ import {
121
+ OrmContext,
122
+ MySqlDialect,
123
+ SelectQueryBuilder,
124
+ eq,
125
+ } from 'metal-orm';
126
+
127
+ // 1) Create an OrmContext for this request
128
+ const ctx = new OrmContext({
129
+ dialect: new MySqlDialect(),
130
+ db: {
131
+ async executeSql(sql, params) {
132
+ const [rows] = await connection.execute(sql, params);
133
+ // MetalORM expects columns + values; adapt as needed
134
+ return [{
135
+ columns: Object.keys(rows[0] ?? {}),
136
+ values: rows.map(row => Object.values(row)),
137
+ }];
138
+ },
139
+ },
140
+ });
141
+
142
+ // 2) Load entities with lazy relations
143
+ const [user] = await new SelectQueryBuilder(users)
144
+ .select({
145
+ id: users.columns.id,
146
+ name: users.columns.name,
147
+ email: users.columns.email,
148
+ })
149
+ .includeLazy('posts') // HasMany as a lazy collection
150
+ .includeLazy('roles') // BelongsToMany as a lazy collection
151
+ .where(eq(users.columns.id, 1))
152
+ .execute(ctx);
153
+
154
+ // user is an Entity<typeof users>
155
+ // scalar props are normal:
156
+ user.name = 'Updated Name'; // marks entity as Dirty
157
+
158
+ // relations are live collections:
159
+ const postsCollection = await user.posts.load(); // batched lazy load
160
+ const newPost = user.posts.add({ title: 'Hello from ORM mode' });
161
+
162
+ // Many-to-many via pivot:
163
+ await user.roles.syncByIds([1, 2, 3]);
164
+
165
+ // 3) Persist the entire graph
166
+ await ctx.saveChanges();
167
+ // INSERT/UPDATE/DELETE + pivot updates happen in a single Unit of Work.
168
+
169
+ ```
170
+
171
+ See [Runtime & Unit of Work](./runtime.md) for full details on entities and the Unit of Work.
package/docs/hydration.md CHANGED
@@ -1,8 +1,11 @@
1
- # Relation Hydration
1
+ # Hydration & Entities
2
2
 
3
- MetalORM can automatically transform flat database rows into nested JavaScript objects, making it easy to work with relational data.
3
+ MetalORM offers two ways to go from SQL rows to richer structures:
4
4
 
5
- ## Hydrating Results
5
+ 1. **Hydration only** – `hydrateRows(rows, plan)` → nested plain objects.
6
+ 2. **Entity runtime** – `builder.execute(ctx)` → entities with lazy relations.
7
+
8
+ ## 1. Hydrating with `hydrateRows`
6
9
 
7
10
  The `hydrateRows()` function takes an array of database rows and a hydration plan to produce nested objects. The hydration plan is generated by the `SelectQueryBuilder` when you use the `include()` method.
8
11
 
@@ -38,13 +41,75 @@ const hydrated = hydrateRows(rows, builder.getHydrationPlan());
38
41
 
39
42
  ## How it Works
40
43
 
41
- The `SelectQueryBuilder` analyzes the `include()` configuration and generates a `HydrationPlan`. This plan contains the necessary information to map the flat rows to a nested structure, including relation details and column aliases. The `hydrateRows()` function then uses this plan to efficiently process the result set.
42
-
43
- For belongs-to-many relationships you can also request pivot columns via the `pivot` option. Pivot columns are hydrated alongside each child row under the `_pivot` key, e.g.:
44
-
45
- ```typescript
46
- .include('projects', {
47
- columns: ['id', 'name', 'client'],
48
- pivot: { columns: ['assigned_at', 'role_id'] }
49
- })
50
- ```
44
+ The `SelectQueryBuilder` analyzes the `include()` configuration and generates a `HydrationPlan`. This plan contains the necessary information to map the flat rows to a nested structure, including relation details and column aliases. The `hydrateRows()` function then uses this plan to efficiently process the result set.
45
+
46
+ ## Pivot Column Hydration
47
+
48
+ For belongs-to-many relationships, you can request pivot columns via the `pivot` option. Pivot columns are hydrated alongside each child row under the `_pivot` key:
49
+
50
+ ```typescript
51
+ .include('projects', {
52
+ columns: ['id', 'name', 'client'],
53
+ pivot: { columns: ['assigned_at', 'role_id'] }
54
+ })
55
+ ```
56
+
57
+ This would hydrate to:
58
+
59
+ ```typescript
60
+ {
61
+ id: 1,
62
+ name: 'John Doe',
63
+ projects: [
64
+ {
65
+ id: 101,
66
+ name: 'Project Alpha',
67
+ client: 'Acme Corp',
68
+ _pivot: {
69
+ assigned_at: '2023-01-15T10:00:00.000Z',
70
+ role_id: 2
71
+ }
72
+ }
73
+ ]
74
+ }
75
+ ```
76
+
77
+ ### Advanced Hydration Options
78
+
79
+ You can also specify which pivot columns to include and customize the hydration:
80
+
81
+ ```typescript
82
+ .include('projects', {
83
+ columns: ['id', 'name'],
84
+ pivot: {
85
+ columns: ['assigned_at', 'role_id'],
86
+ alias: 'assignment_info' // Custom alias instead of '_pivot'
87
+ },
88
+ include: {
89
+ client: {
90
+ columns: ['id', 'name']
91
+ }
92
+ }
93
+ })
94
+
95
+ ## 2. Entities on top of hydration
96
+
97
+ When you call `.execute(ctx)` instead of compiling manually:
98
+
99
+ - MetalORM compiles and executes the query using the dialect in the `OrmContext`.
100
+ - If a `HydrationPlan` exists, it is applied.
101
+ - Each root row is wrapped in an entity proxy:
102
+ - scalar fields behave like normal properties
103
+ - relation properties expose `HasManyCollection`, `BelongsToReference`, or `ManyToManyCollection`.
104
+
105
+ ```ts
106
+ const [user] = await new SelectQueryBuilder(users)
107
+ .select({ id: users.columns.id, name: users.columns.name })
108
+ .include('posts', { columns: [posts.columns.id, posts.columns.title] }) // eager
109
+ .includeLazy('roles') // lazy
110
+ .execute(ctx);
111
+
112
+ const eagerPosts = user.posts.getItems(); // hydrated from the join
113
+ const lazyRoles = await user.roles.load(); // resolved on demand
114
+ ```
115
+ ```
package/docs/index.md CHANGED
@@ -1,31 +1,36 @@
1
1
  # Introduction to MetalORM
2
2
 
3
- MetalORM is a TypeScript-first SQL query builder designed for developers who want the power of raw SQL with the convenience of a modern ORM. It keeps SQL generation deterministic (CTEs, aggregates, window functions, EXISTS/subqueries) while letting you introspect the AST or reuse builders inside larger queries.
3
+ MetalORM is a TypeScript-first SQL toolkit that can be used as:
4
4
 
5
- ## Philosophy
5
+ 1. A **typed query builder** over a real SQL AST.
6
+ 2. A **hydration layer** for turning flat rows into nested objects.
7
+ 3. An optional **entity runtime** with lazy relations and a Unit of Work.
8
+
9
+ You can adopt these layers independently, in this order.
6
10
 
7
- MetalORM follows these core principles:
11
+ ## Philosophy
8
12
 
9
- - **Type Safety First**: Leverage TypeScript to catch errors at compile time
10
- - **SQL Transparency**: Generate predictable, readable SQL that you can inspect
11
- - **Composition Over Configuration**: Build complex queries by composing simple parts
12
- - **Zero Magic**: Explicit operations with clear AST representation
13
- - **Multi-Dialect Support**: Write once, compile to MySQL, SQLite, or SQL Server
13
+ - **Type Safety First** rely on TypeScript to catch mistakes early.:contentReference[oaicite:8]{index=8}
14
+ - **SQL Transparency** inspect the AST and generated SQL at any time.
15
+ - **Composition Over Configuration** build complex queries from small, pure pieces.
16
+ - **Zero Magic, Opt-in Runtime** the query builder and ORM runtime are separate layers.
17
+ - **Multi-Dialect** MySQL, PostgreSQL, SQLite, SQL Server out of the box.:contentReference[oaicite:9]{index=9}
14
18
 
15
19
  ## Features
16
20
 
17
- - **Declarative Schema Definition**: Define your database structure in TypeScript with full type inference.
18
- - **Rich Query Building**: A fluent API to build simple and complex queries.
19
- - **Advanced SQL Features**: Support for CTEs, window functions, subqueries, and more.
20
- - **Relation Hydration**: Automatically transform flat database rows into nested JavaScript objects.
21
- - **Multi-Dialect Support**: Compile the same query to different SQL dialects.
21
+ - Declarative schema definition with relations.
22
+ - Fluent query builder, including CTEs, window functions, subqueries, JSON, CASE.
23
+ - Relation hydration from flat result sets.
24
+ - Optional entity runtime + Unit of Work.
22
25
 
23
26
  ## Table of Contents
24
27
 
25
28
  - [Getting Started](./getting-started.md)
26
29
  - [Schema Definition](./schema-definition.md)
27
30
  - [Query Builder](./query-builder.md)
31
+ - [DML Operations](./dml-operations.md)
32
+ - [Hydration & Entities](./hydration.md)
33
+ - [Runtime & Unit of Work](./runtime.md)
28
34
  - [Advanced Features](./advanced-features.md)
29
- - [Hydration](./hydration.md)
30
35
  - [Multi-Dialect Support](./multi-dialect-support.md)
31
36
  - [API Reference](./api-reference.md)
@@ -30,5 +30,30 @@ const mssql = query.compile(new MSSQLDialect());
30
30
  - **MySQL**: `MySqlDialect`
31
31
  - **SQLite**: `SQLiteDialect`
32
32
  - **SQL Server**: `MSSQLDialect`
33
+ - **PostgreSQL**: `PostgresDialect`
33
34
 
34
35
  Each dialect handles the specific syntax and parameterization of the target database.
36
+
37
+ ### Dialect-Specific Features
38
+
39
+ ```typescript
40
+ // PostgreSQL-specific JSON operations
41
+ const query = new SelectQueryBuilder(users)
42
+ .select({
43
+ id: users.columns.id,
44
+ name: users.columns.name,
45
+ settings: jsonPath(users.columns.settings, '$.notifications')
46
+ })
47
+ .compile(new PostgresDialect());
48
+
49
+ // SQL Server TOP clause vs LIMIT
50
+ const limitedQuery = new SelectQueryBuilder(users)
51
+ .selectRaw('*')
52
+ .limit(10);
53
+
54
+ // MySQL/SQLite/PostgreSQL: SELECT * FROM users LIMIT 10
55
+ // SQL Server: SELECT TOP 10 * FROM users
56
+ ```
57
+
58
+ > **Note**: When using the runtime (`OrmContext`), the same dialects are used to generate INSERT, UPDATE, DELETE, and pivot operations in `saveChanges()`.
59
+ ```
@@ -73,3 +73,63 @@ const query = new SelectQueryBuilder(posts)
73
73
  .limit(10)
74
74
  .offset(20);
75
75
  ```
76
+
77
+ ### Window Functions
78
+
79
+ The query builder supports window functions for advanced analytics:
80
+
81
+ ```typescript
82
+ import { rowNumber, rank } from 'metal-orm';
83
+
84
+ const query = new SelectQueryBuilder(users)
85
+ .select({
86
+ id: users.columns.id,
87
+ name: users.columns.name,
88
+ rowNum: rowNumber(),
89
+ userRank: rank()
90
+ })
91
+ .partitionBy(users.columns.department)
92
+ .orderBy(users.columns.salary, 'DESC');
93
+ ```
94
+
95
+ ### CTEs (Common Table Expressions)
96
+
97
+ You can use CTEs to organize complex queries:
98
+
99
+ ```typescript
100
+ const activeUsers = new SelectQueryBuilder(users)
101
+ .selectRaw('*')
102
+ .where(gt(users.columns.lastLogin, new Date('2023-01-01')))
103
+ .as('active_users');
104
+
105
+ const query = new SelectQueryBuilder(activeUsers)
106
+ .with(activeUsers)
107
+ .selectRaw('*')
108
+ .where(eq(activeUsers.columns.id, 1));
109
+ ```
110
+
111
+ ### Subqueries
112
+
113
+ Support for subqueries in SELECT and WHERE clauses:
114
+
115
+ ```typescript
116
+ const subquery = new SelectQueryBuilder(posts)
117
+ .select({ count: count(posts.columns.id) })
118
+ .where(eq(posts.columns.userId, users.columns.id));
119
+
120
+ const query = new SelectQueryBuilder(users)
121
+ .select({
122
+ id: users.columns.id,
123
+ name: users.columns.name,
124
+ postCount: subquery
125
+ });
126
+
127
+ ## From Builder to Entities
128
+
129
+ You can keep using the query builder on its own, or plug it into the entity runtime:
130
+
131
+ - `builder.compile(dialect)` → SQL + params → driver (builder-only usage).
132
+ - `builder.execute(ctx)` → entities tracked by an `OrmContext` (runtime usage).
133
+
134
+ See [Runtime & Unit of Work](./runtime.md) for how `execute(ctx)` integrates with entities and lazy relations.
135
+ ```