create-phoenixjs 0.1.4 → 0.1.5
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.
- package/package.json +1 -1
- package/template/config/database.ts +13 -1
- package/template/database/migrations/2024_01_01_000000_create_test_users_cli_table.ts +16 -0
- package/template/database/migrations/20260108164611_TestCliMigration.ts +16 -0
- package/template/database/migrations/2026_01_08_16_46_11_CreateTestMigrationsTable.ts +21 -0
- package/template/framework/cli/artisan.ts +12 -0
- package/template/framework/database/DatabaseManager.ts +133 -0
- package/template/framework/database/connection/Connection.ts +71 -0
- package/template/framework/database/connection/ConnectionFactory.ts +30 -0
- package/template/framework/database/connection/PostgresConnection.ts +159 -0
- package/template/framework/database/console/MakeMigrationCommand.ts +58 -0
- package/template/framework/database/console/MigrateCommand.ts +32 -0
- package/template/framework/database/console/MigrateResetCommand.ts +31 -0
- package/template/framework/database/console/MigrateRollbackCommand.ts +31 -0
- package/template/framework/database/console/MigrateStatusCommand.ts +38 -0
- package/template/framework/database/migrations/DatabaseMigrationRepository.ts +122 -0
- package/template/framework/database/migrations/Migration.ts +5 -0
- package/template/framework/database/migrations/MigrationRepository.ts +46 -0
- package/template/framework/database/migrations/Migrator.ts +249 -0
- package/template/framework/database/migrations/index.ts +4 -0
- package/template/framework/database/orm/BelongsTo.ts +246 -0
- package/template/framework/database/orm/BelongsToMany.ts +570 -0
- package/template/framework/database/orm/Builder.ts +160 -0
- package/template/framework/database/orm/EagerLoadingBuilder.ts +324 -0
- package/template/framework/database/orm/HasMany.ts +303 -0
- package/template/framework/database/orm/HasManyThrough.ts +282 -0
- package/template/framework/database/orm/HasOne.ts +201 -0
- package/template/framework/database/orm/HasOneThrough.ts +281 -0
- package/template/framework/database/orm/Model.ts +1766 -0
- package/template/framework/database/orm/Relation.ts +342 -0
- package/template/framework/database/orm/Scope.ts +14 -0
- package/template/framework/database/orm/SoftDeletes.ts +160 -0
- package/template/framework/database/orm/index.ts +54 -0
- package/template/framework/database/orm/scopes/SoftDeletingScope.ts +58 -0
- package/template/framework/database/pagination/LengthAwarePaginator.ts +55 -0
- package/template/framework/database/pagination/Paginator.ts +110 -0
- package/template/framework/database/pagination/index.ts +2 -0
- package/template/framework/database/query/Builder.ts +918 -0
- package/template/framework/database/query/DB.ts +139 -0
- package/template/framework/database/query/grammars/Grammar.ts +430 -0
- package/template/framework/database/query/grammars/PostgresGrammar.ts +224 -0
- package/template/framework/database/query/grammars/index.ts +6 -0
- package/template/framework/database/query/index.ts +8 -0
- package/template/framework/database/query/types.ts +196 -0
- package/template/framework/database/schema/Blueprint.ts +478 -0
- package/template/framework/database/schema/Schema.ts +149 -0
- package/template/framework/database/schema/SchemaBuilder.ts +152 -0
- package/template/framework/database/schema/grammars/PostgresSchemaGrammar.ts +293 -0
- package/template/framework/database/schema/grammars/index.ts +5 -0
- package/template/framework/database/schema/index.ts +9 -0
- package/template/package.json +4 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS ORM - DB Facade
|
|
3
|
+
*
|
|
4
|
+
* Static facade for database operations, providing a Laravel-like
|
|
5
|
+
* DB::table('users') interface for query building.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Connection } from '../connection/Connection';
|
|
9
|
+
import type { Grammar } from './grammars/Grammar';
|
|
10
|
+
import type { Binding } from './types';
|
|
11
|
+
import { Builder } from './Builder';
|
|
12
|
+
import { RawExpression } from './types';
|
|
13
|
+
import { PostgresGrammar } from './grammars/PostgresGrammar';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* DB Facade
|
|
17
|
+
*
|
|
18
|
+
* Provides a static interface for database operations.
|
|
19
|
+
* Must be initialized with a connection before use.
|
|
20
|
+
*/
|
|
21
|
+
export class DB {
|
|
22
|
+
/** The database connection */
|
|
23
|
+
private static connection: Connection | null = null;
|
|
24
|
+
|
|
25
|
+
/** The grammar instance */
|
|
26
|
+
private static grammar: Grammar | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the DB facade with a connection
|
|
30
|
+
*/
|
|
31
|
+
static init(connection: Connection, grammar?: Grammar): void {
|
|
32
|
+
DB.connection = connection;
|
|
33
|
+
DB.grammar = grammar || new PostgresGrammar();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set the database connection
|
|
38
|
+
*/
|
|
39
|
+
static setConnection(connection: Connection): void {
|
|
40
|
+
DB.connection = connection;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Set the grammar
|
|
45
|
+
*/
|
|
46
|
+
static setGrammar(grammar: Grammar): void {
|
|
47
|
+
DB.grammar = grammar;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the current connection
|
|
52
|
+
*/
|
|
53
|
+
static getConnection(): Connection {
|
|
54
|
+
if (!DB.connection) {
|
|
55
|
+
throw new Error('DB facade not initialized. Call DB.init(connection) first.');
|
|
56
|
+
}
|
|
57
|
+
return DB.connection;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the current grammar
|
|
62
|
+
*/
|
|
63
|
+
static getGrammar(): Grammar {
|
|
64
|
+
if (!DB.grammar) {
|
|
65
|
+
DB.grammar = new PostgresGrammar();
|
|
66
|
+
}
|
|
67
|
+
return DB.grammar;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Begin a fluent query against a database table
|
|
72
|
+
*/
|
|
73
|
+
static table(name: string, alias?: string): Builder {
|
|
74
|
+
const connection = DB.getConnection();
|
|
75
|
+
const grammar = DB.getGrammar();
|
|
76
|
+
const builder = new Builder(connection, grammar, name);
|
|
77
|
+
|
|
78
|
+
if (alias) {
|
|
79
|
+
builder.table(name, alias);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return builder;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Execute a raw SQL query
|
|
87
|
+
*/
|
|
88
|
+
static async query<T = Record<string, unknown>>(sql: string, bindings: unknown[] = []): Promise<T[]> {
|
|
89
|
+
return DB.getConnection().query<T>(sql, bindings);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Execute a raw SQL statement
|
|
94
|
+
*/
|
|
95
|
+
static async execute(sql: string, bindings: unknown[] = []): Promise<number> {
|
|
96
|
+
return DB.getConnection().execute(sql, bindings);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get a single row from a raw query
|
|
101
|
+
*/
|
|
102
|
+
static async get<T = Record<string, unknown>>(sql: string, bindings: unknown[] = []): Promise<T | null> {
|
|
103
|
+
return DB.getConnection().get<T>(sql, bindings);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Execute operations within a transaction
|
|
108
|
+
*/
|
|
109
|
+
static async transaction<T>(callback: (tx: unknown) => Promise<T>): Promise<T> {
|
|
110
|
+
return DB.getConnection().transaction(callback);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create a new Builder instance with a custom connection
|
|
115
|
+
*/
|
|
116
|
+
static withConnection(connection: Connection): Builder {
|
|
117
|
+
const grammar = DB.getGrammar();
|
|
118
|
+
return new Builder(connection, grammar);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a raw expression for use in queries
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* DB.table('users').selectRaw('count(*) as user_count')
|
|
126
|
+
* DB.table('orders').selectRaw('SUM(total) as total_sales')
|
|
127
|
+
*/
|
|
128
|
+
static raw(expression: string, bindings: Binding[] = []): RawExpression {
|
|
129
|
+
return new RawExpression(expression, bindings);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reset the DB facade (useful for testing)
|
|
134
|
+
*/
|
|
135
|
+
static reset(): void {
|
|
136
|
+
DB.connection = null;
|
|
137
|
+
DB.grammar = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoenixJS ORM - Abstract Grammar Class
|
|
3
|
+
*
|
|
4
|
+
* Base class for SQL grammar compilation. Defines the contract for all
|
|
5
|
+
* database-specific grammar implementations. The Grammar is responsible
|
|
6
|
+
* for converting internal query representations to database-specific SQL syntax.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
QueryComponents,
|
|
11
|
+
CompiledQuery,
|
|
12
|
+
InsertValues,
|
|
13
|
+
UpdateValues,
|
|
14
|
+
Binding,
|
|
15
|
+
WhereClause,
|
|
16
|
+
JoinClause,
|
|
17
|
+
OrderClause,
|
|
18
|
+
} from '../types';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Abstract Grammar base class
|
|
22
|
+
*
|
|
23
|
+
* Subclasses must implement database-specific SQL generation.
|
|
24
|
+
*/
|
|
25
|
+
export abstract class Grammar {
|
|
26
|
+
/**
|
|
27
|
+
* The grammar-specific components for SELECT statements
|
|
28
|
+
*/
|
|
29
|
+
protected selectComponents: (keyof QueryComponents)[] = [
|
|
30
|
+
'aggregate',
|
|
31
|
+
'columns',
|
|
32
|
+
'table',
|
|
33
|
+
'joins',
|
|
34
|
+
'wheres',
|
|
35
|
+
'groups',
|
|
36
|
+
'havings',
|
|
37
|
+
'orders',
|
|
38
|
+
'limit',
|
|
39
|
+
'offset',
|
|
40
|
+
'lock',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compile a SELECT query into SQL
|
|
45
|
+
*/
|
|
46
|
+
compileSelect(query: QueryComponents): CompiledQuery {
|
|
47
|
+
const bindings: Binding[] = [];
|
|
48
|
+
const sql = this.concatenate(this.compileComponents(query, bindings));
|
|
49
|
+
return { sql: sql.trim(), bindings };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Compile the components of a query
|
|
54
|
+
*/
|
|
55
|
+
protected compileComponents(query: QueryComponents, bindings: Binding[]): string[] {
|
|
56
|
+
const segments: string[] = [];
|
|
57
|
+
|
|
58
|
+
for (const component of this.selectComponents) {
|
|
59
|
+
const value = query[component];
|
|
60
|
+
if (value !== undefined && value !== null) {
|
|
61
|
+
const method = `compile${this.capitalize(component)}` as keyof this;
|
|
62
|
+
if (typeof this[method] === 'function') {
|
|
63
|
+
const sql = (this[method] as (query: QueryComponents, bindings: Binding[]) => string)(
|
|
64
|
+
query,
|
|
65
|
+
bindings
|
|
66
|
+
);
|
|
67
|
+
if (sql) {
|
|
68
|
+
segments.push(sql);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return segments;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Compile the "select" portion of the query
|
|
79
|
+
*/
|
|
80
|
+
protected compileColumns(query: QueryComponents, bindings: Binding[]): string {
|
|
81
|
+
// If an aggregate is set, skip columns (aggregate already includes SELECT)
|
|
82
|
+
if (query.aggregate) {
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const select = query.distinct ? 'SELECT DISTINCT' : 'SELECT';
|
|
87
|
+
const columns: string[] = [];
|
|
88
|
+
|
|
89
|
+
// Add regular columns
|
|
90
|
+
if (query.columns.length > 0) {
|
|
91
|
+
columns.push(this.columnize(query.columns));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Add raw select expressions
|
|
95
|
+
if (query.rawSelects.length > 0) {
|
|
96
|
+
for (const raw of query.rawSelects) {
|
|
97
|
+
columns.push(raw.expression);
|
|
98
|
+
bindings.push(...raw.bindings);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (columns.length === 0) {
|
|
103
|
+
return `${select} *`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return `${select} ${columns.join(', ')}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Compile the "from" portion of the query
|
|
111
|
+
*/
|
|
112
|
+
protected compileTable(query: QueryComponents, _bindings: Binding[]): string {
|
|
113
|
+
const table = this.wrapTable(query.table);
|
|
114
|
+
if (query.alias) {
|
|
115
|
+
return `FROM ${table} AS ${this.wrap(query.alias)}`;
|
|
116
|
+
}
|
|
117
|
+
return `FROM ${table}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Compile the aggregate function portion
|
|
122
|
+
*/
|
|
123
|
+
protected compileAggregate(query: QueryComponents, _bindings: Binding[]): string {
|
|
124
|
+
if (!query.aggregate) return '';
|
|
125
|
+
|
|
126
|
+
const column =
|
|
127
|
+
query.aggregate.column === '*' ? '*' : this.wrap(query.aggregate.column);
|
|
128
|
+
return `SELECT ${query.aggregate.function.toUpperCase()}(${column})`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Compile the "join" portions of the query
|
|
133
|
+
*/
|
|
134
|
+
protected compileJoins(query: QueryComponents, _bindings: Binding[]): string {
|
|
135
|
+
if (query.joins.length === 0) return '';
|
|
136
|
+
|
|
137
|
+
return query.joins.map((join) => this.compileJoin(join)).join(' ');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Compile a single join clause
|
|
142
|
+
*/
|
|
143
|
+
protected compileJoin(join: JoinClause): string {
|
|
144
|
+
const table = this.wrapTable(join.table);
|
|
145
|
+
const joinType = this.getJoinType(join.type);
|
|
146
|
+
|
|
147
|
+
// Cross join doesn't have ON clause
|
|
148
|
+
if (join.type === 'cross') {
|
|
149
|
+
return `${joinType} JOIN ${table}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const first = this.wrap(join.first);
|
|
153
|
+
const second = this.wrap(join.second);
|
|
154
|
+
|
|
155
|
+
return `${joinType} JOIN ${table} ON ${first} ${join.operator} ${second}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get the join type keyword
|
|
160
|
+
*/
|
|
161
|
+
protected getJoinType(type: JoinClause['type']): string {
|
|
162
|
+
switch (type) {
|
|
163
|
+
case 'inner':
|
|
164
|
+
return 'INNER';
|
|
165
|
+
case 'left':
|
|
166
|
+
return 'LEFT';
|
|
167
|
+
case 'right':
|
|
168
|
+
return 'RIGHT';
|
|
169
|
+
case 'cross':
|
|
170
|
+
return 'CROSS';
|
|
171
|
+
default:
|
|
172
|
+
return 'INNER';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Compile the "where" portions of the query
|
|
178
|
+
*/
|
|
179
|
+
compileWheres(query: QueryComponents, bindings: Binding[]): string {
|
|
180
|
+
if (query.wheres.length === 0) return '';
|
|
181
|
+
|
|
182
|
+
const sql = query.wheres
|
|
183
|
+
.map((where, index) => {
|
|
184
|
+
const prefix = index === 0 ? '' : `${where.boolean.toUpperCase()} `;
|
|
185
|
+
return prefix + this.compileWhere(where, bindings);
|
|
186
|
+
})
|
|
187
|
+
.join(' ');
|
|
188
|
+
|
|
189
|
+
return `WHERE ${sql}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Compile a single where clause
|
|
194
|
+
*/
|
|
195
|
+
protected compileWhere(where: WhereClause, bindings: Binding[]): string {
|
|
196
|
+
switch (where.type) {
|
|
197
|
+
case 'basic':
|
|
198
|
+
return this.compileBasicWhere(where, bindings);
|
|
199
|
+
case 'in':
|
|
200
|
+
return this.compileInWhere(where, bindings);
|
|
201
|
+
case 'null':
|
|
202
|
+
return this.compileNullWhere(where);
|
|
203
|
+
case 'between':
|
|
204
|
+
return this.compileBetweenWhere(where, bindings);
|
|
205
|
+
case 'raw':
|
|
206
|
+
return this.compileRawWhere(where, bindings);
|
|
207
|
+
case 'nested':
|
|
208
|
+
return this.compileNestedWhere(where, bindings);
|
|
209
|
+
default:
|
|
210
|
+
throw new Error(`Unknown where type: ${where.type}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Compile a basic where clause
|
|
216
|
+
*/
|
|
217
|
+
protected abstract compileBasicWhere(where: WhereClause, bindings: Binding[]): string;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Compile a where in clause
|
|
221
|
+
*/
|
|
222
|
+
protected abstract compileInWhere(where: WhereClause, bindings: Binding[]): string;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Compile a where null clause
|
|
226
|
+
*/
|
|
227
|
+
protected compileNullWhere(where: WhereClause): string {
|
|
228
|
+
const column = this.wrap(where.column!);
|
|
229
|
+
return where.not ? `${column} IS NOT NULL` : `${column} IS NULL`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Compile a where between clause
|
|
234
|
+
*/
|
|
235
|
+
protected abstract compileBetweenWhere(where: WhereClause, bindings: Binding[]): string;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Compile a raw where clause
|
|
239
|
+
*/
|
|
240
|
+
protected compileRawWhere(where: WhereClause, bindings: Binding[]): string {
|
|
241
|
+
if (where.bindings) {
|
|
242
|
+
bindings.push(...where.bindings);
|
|
243
|
+
}
|
|
244
|
+
return where.sql!;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Compile a nested where clause
|
|
249
|
+
*/
|
|
250
|
+
protected compileNestedWhere(where: WhereClause, bindings: Binding[]): string {
|
|
251
|
+
if (!where.nested || where.nested.length === 0) return '';
|
|
252
|
+
|
|
253
|
+
const nestedSql = where.nested
|
|
254
|
+
.map((nested, index) => {
|
|
255
|
+
const prefix = index === 0 ? '' : `${nested.boolean.toUpperCase()} `;
|
|
256
|
+
return prefix + this.compileWhere(nested, bindings);
|
|
257
|
+
})
|
|
258
|
+
.join(' ');
|
|
259
|
+
|
|
260
|
+
return `(${nestedSql})`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Compile the "group by" portions of the query
|
|
265
|
+
*/
|
|
266
|
+
protected compileGroups(query: QueryComponents, bindings: Binding[]): string {
|
|
267
|
+
const groups: string[] = [];
|
|
268
|
+
|
|
269
|
+
// Add regular group by columns
|
|
270
|
+
if (query.groups.length > 0) {
|
|
271
|
+
groups.push(this.columnize(query.groups));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Add raw group by expressions
|
|
275
|
+
if (query.rawGroups.length > 0) {
|
|
276
|
+
for (const raw of query.rawGroups) {
|
|
277
|
+
groups.push(raw.expression);
|
|
278
|
+
bindings.push(...raw.bindings);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (groups.length === 0) return '';
|
|
283
|
+
|
|
284
|
+
return `GROUP BY ${groups.join(', ')}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Compile the "having" portions of the query
|
|
289
|
+
*/
|
|
290
|
+
protected compileHavings(query: QueryComponents, bindings: Binding[]): string {
|
|
291
|
+
if (query.havings.length === 0) return '';
|
|
292
|
+
|
|
293
|
+
const sql = query.havings
|
|
294
|
+
.map((having, index) => {
|
|
295
|
+
const prefix = index === 0 ? '' : `${having.boolean.toUpperCase()} `;
|
|
296
|
+
return prefix + this.compileWhere(having, bindings);
|
|
297
|
+
})
|
|
298
|
+
.join(' ');
|
|
299
|
+
|
|
300
|
+
return `HAVING ${sql}`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Compile the "order by" portions of the query
|
|
305
|
+
*/
|
|
306
|
+
protected compileOrders(query: QueryComponents, bindings: Binding[]): string {
|
|
307
|
+
if (query.orders.length === 0) return '';
|
|
308
|
+
|
|
309
|
+
// Add order bindings (for raw order expressions)
|
|
310
|
+
if (query.orderBindings.length > 0) {
|
|
311
|
+
bindings.push(...query.orderBindings);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return `ORDER BY ${query.orders.map((order) => this.compileOrder(order)).join(', ')}`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Compile a single order clause
|
|
319
|
+
*/
|
|
320
|
+
protected compileOrder(order: OrderClause): string {
|
|
321
|
+
// Raw order expressions are used as-is
|
|
322
|
+
if (order.raw) {
|
|
323
|
+
return order.column;
|
|
324
|
+
}
|
|
325
|
+
return `${this.wrap(order.column)} ${order.direction.toUpperCase()}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Compile the "limit" portions of the query
|
|
330
|
+
*/
|
|
331
|
+
/**
|
|
332
|
+
* Compile the "limit" portions of the query
|
|
333
|
+
*/
|
|
334
|
+
protected compileLimit(query: QueryComponents, bindings: Binding[]): string {
|
|
335
|
+
if (query.limit === undefined) return '';
|
|
336
|
+
|
|
337
|
+
bindings.push(query.limit);
|
|
338
|
+
return `LIMIT ${this.parameterize([query.limit], bindings.length)}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Compile the "offset" portions of the query
|
|
343
|
+
*/
|
|
344
|
+
protected compileOffset(query: QueryComponents, bindings: Binding[]): string {
|
|
345
|
+
if (query.offset === undefined) return '';
|
|
346
|
+
|
|
347
|
+
bindings.push(query.offset);
|
|
348
|
+
return `OFFSET ${this.parameterize([query.offset], bindings.length)}`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Compile the lock clause
|
|
353
|
+
*/
|
|
354
|
+
protected compileLock(query: QueryComponents, _bindings: Binding[]): string {
|
|
355
|
+
if (!query.lock) return '';
|
|
356
|
+
return query.lock.toUpperCase();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Compile an INSERT statement
|
|
361
|
+
*/
|
|
362
|
+
abstract compileInsert(query: QueryComponents, values: InsertValues | InsertValues[]): CompiledQuery;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Compile an INSERT statement with RETURNING
|
|
366
|
+
*/
|
|
367
|
+
abstract compileInsertReturning(
|
|
368
|
+
query: QueryComponents,
|
|
369
|
+
values: InsertValues | InsertValues[],
|
|
370
|
+
returning: string[]
|
|
371
|
+
): CompiledQuery;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Compile an UPDATE statement
|
|
375
|
+
*/
|
|
376
|
+
abstract compileUpdate(query: QueryComponents, values: UpdateValues): CompiledQuery;
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Compile a DELETE statement
|
|
380
|
+
*/
|
|
381
|
+
abstract compileDelete(query: QueryComponents): CompiledQuery;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Compile a truncate table statement
|
|
385
|
+
*/
|
|
386
|
+
abstract compileTruncate(table: string): string;
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Wrap a table in keyword identifiers
|
|
390
|
+
*/
|
|
391
|
+
wrapTable(table: string): string {
|
|
392
|
+
return this.wrap(table);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Wrap a value in keyword identifiers
|
|
397
|
+
*/
|
|
398
|
+
abstract wrap(value: string): string;
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Convert an array of column names into a delimited string
|
|
402
|
+
*/
|
|
403
|
+
columnize(columns: string[]): string {
|
|
404
|
+
return columns.map((column) => this.wrap(column)).join(', ');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Create query parameter placeholders for values
|
|
409
|
+
*/
|
|
410
|
+
abstract parameterize(values: Binding[], startIndex?: number): string;
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get the grammar's parameter prefix
|
|
414
|
+
*/
|
|
415
|
+
abstract getParameterPrefix(): string;
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Concatenate an array of segments into a single string
|
|
419
|
+
*/
|
|
420
|
+
protected concatenate(segments: string[]): string {
|
|
421
|
+
return segments.filter((segment) => segment !== '').join(' ');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Capitalize the first letter of a string
|
|
426
|
+
*/
|
|
427
|
+
protected capitalize(str: string): string {
|
|
428
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
429
|
+
}
|
|
430
|
+
}
|