1
+ import { isNil } from '@devnetic/utils'
2
+
3
+ import { tap } from '../../Support/helpers.js'
4
+ import Blueprint from './Blueprint.js'
5
+
6
+ /** @typedef {import('../Connection.js').default } Connection */
7
+ /** @typedef {import('./Grammars/Grammar.js').default } Grammar */
8
+
1
9
export default class Builder {
2
10
/**
3
11
* The database connection instance.
@@ -7,19 +15,35 @@ export default class Builder {
7
15
*/
8
16
connection
9
17
18
+ /**
19
+ * The schema grammar instance.'
20
+ *
21
+ * @protected
22
+ * @type {Grammar }
23
+ */
24
+ grammar
25
+
26
+ /**
27
+ * The Blueprint resolver callback.
28
+ *
29
+ * @protected
30
+ * @type {Function }
31
+ */
32
+ resolver
33
+
10
34
/**
11
35
* The default string length for migrations.
12
36
*
13
37
* @type {number|undefined }
14
38
*/
15
- static defaultStringLength = 255
39
+ static defaultStringLengthProperty = 255
16
40
17
41
/**
18
42
* The default relationship morph key type.
19
43
*
20
44
* @type {string }
21
45
*/
22
- static defaultMorphKeyType = 'int'
46
+ static defaultMorphKeyTypeProperty = 'int'
23
47
24
48
/**
25
49
* Create a new database Schema manager.
@@ -31,17 +55,553 @@ export default class Builder {
31
55
this . grammar = connection . getSchemaGrammar ( )
32
56
}
33
57
58
+ /**
59
+ * Set the default string length for migrations.
60
+ *
61
+ * @param {number } length
62
+ * @return {void }
63
+ */
64
+ static defaultStringLength ( length ) {
65
+ this . defaultStringLengthProperty = length
66
+ }
67
+
68
+ /**
69
+ * Set the default morph key type for migrations.
70
+ *
71
+ * @param {string } type
72
+ * @return {void }
73
+ *
74
+ * @throws {InvalidArgumentException }
75
+ */
76
+ static defaultMorphKeyType ( type ) {
77
+ if ( ! [ 'int' , 'uuid' , 'ulid' ] . includes ( type ) ) {
78
+ throw new Error ( "InvalidArgumentException: Morph key type must be 'int', 'uuid', or 'ulid'." )
79
+ }
80
+
81
+ this . defaultMorphKeyTypeProperty = type
82
+ }
83
+
84
+ /**
85
+ * Set the default morph key type for migrations to UUIDs.
86
+ *
87
+ * @return {void }
88
+ */
89
+ static morphUsingUuids ( ) {
90
+ return this . defaultMorphKeyType ( 'uid' )
91
+ }
92
+
93
+ /**
94
+ * Set the default morph key type for migrations to ULIDs.
95
+ *
96
+ * @return {void }
97
+ */
98
+ static morphUsingUlids ( ) {
99
+ return this . defaultMorphKeyType ( 'ulid' )
100
+ }
101
+
102
+ /**
103
+ * Create a database in the schema.
104
+ *
105
+ * @param {string } name
106
+ * @return {boolean }
107
+ *
108
+ * @throws {LogicException }
109
+ */
110
+ createDatabase ( name ) {
111
+ throw new Error ( 'LogicException: This database driver does not support creating databases.' )
112
+ }
113
+
114
+ /**
115
+ * Drop a database from the schema if the database exists.
116
+ *
117
+ * @param {string } name
118
+ * @return {boolean }
119
+ *
120
+ * @throws {LogicException }
121
+ */
122
+ dropDatabaseIfExists ( name ) {
123
+ throw new Error ( 'LogicException: This database driver does not support dropping databases.' )
124
+ }
125
+
126
+ /**
127
+ * Determine if the given table exists.
128
+ *
129
+ * @param {string } table
130
+ * @return {Promise<boolean> }
131
+ */
132
+ async hasTable ( table ) {
133
+ table = this . connection . getTablePrefix ( ) + table
134
+
135
+ const tables = await this . getTables ( false )
136
+
137
+ for ( const value of tables ) {
138
+ if ( table . toLowerCase ( ) === value . name . toLowerCase ( ) ) {
139
+ return true
140
+ }
141
+ }
142
+
143
+ return false
144
+ }
145
+
146
+ /**
147
+ * Determine if the given view exists.
148
+ *
149
+ * @param {string } view
150
+ * @return {Promise<boolean> }
151
+ */
152
+ async hasView ( view ) {
153
+ view = this . connection . getTablePrefix ( ) + view
154
+
155
+ const views = await this . getViews ( )
156
+
157
+ for ( const value of views ) {
158
+ if ( view . toLowerCase ( ) === value . name . toLowerCase ( ) ) {
159
+ return true
160
+ }
161
+ }
162
+
163
+ return false
164
+ }
165
+
166
+ /**
167
+ * Get the tables that belong to the database.
168
+ *
169
+ * @return {Promise<string[]> }
170
+ */
171
+ async getTables ( ) {
172
+ return this . connection . getPostProcessor ( ) . processTables (
173
+ await this . connection . select ( this . grammar . compileTables ( ) )
174
+ )
175
+ }
176
+
177
+ /**
178
+ * Get the names of the tables that belong to the database.
179
+ *
180
+ * @return {Promise<string[]> }
181
+ */
182
+ async getTableListing ( ) {
183
+ const tables = await this . getTables ( )
184
+
185
+ return tables . reduce ( ( names , table ) => {
186
+ names . push ( table . name )
187
+
188
+ return names
189
+ } , [ ] )
190
+ }
191
+
192
+ /**
193
+ * Get the views that belong to the database.
194
+ *
195
+ * @return {Promise<array> }
196
+ */
197
+ async getViews ( ) {
198
+ return this . connection . getPostProcessor ( ) . processViews (
199
+ await this . connection . select ( this . grammar . compileViews ( ) )
200
+ )
201
+ }
202
+
203
+ /**
204
+ * Get the user-defined types that belong to the database.
205
+ *
206
+ * @return {Array }
207
+ */
208
+ getTypes ( ) {
209
+ throw new Error ( 'LogicException: This database driver does not support user-defined types.' )
210
+ }
211
+
212
+ /**
213
+ * Determine if the given table has a given column.
214
+ *
215
+ * @param {string } table
216
+ * @param {string } column
217
+ * @return {Promise<boolean> }
218
+ */
219
+ async hasColumn ( table , column ) {
220
+ const columns = await this . getColumnListing ( table )
221
+
222
+ return columns
223
+ . map ( column => column . toLowerCase ( ) )
224
+ . includes ( column . toLowerCase ( ) )
225
+ }
226
+
227
+ /**
228
+ * Determine if the given table has given columns.
229
+ *
230
+ * @param {string } table
231
+ * @param {string[] } columns
232
+ * @return {Promise<boolean> }
233
+ */
234
+ async hasColumns ( table , columns ) {
235
+ const tableColumns = ( await this . getColumnListing ( table ) ) . map ( column => column . toLowerCase ( ) )
236
+
237
+ for ( const column of columns ) {
238
+ if ( ! tableColumns . includes ( column . toLowerCase ( ) ) ) {
239
+ return false
240
+ }
241
+ }
242
+
243
+ return true
244
+ }
245
+
246
+ /**
247
+ * Execute a table builder callback if the given table has a given column.
248
+ *
249
+ * @param {string } table
250
+ * @param {string } column
251
+ * @param {Function } callback
252
+ * @return {Promise<void> }
253
+ */
254
+ async whenTableHasColumn ( table , column , callback ) {
255
+ const hasColumn = await this . hasColumn ( table , column )
256
+
257
+ if ( hasColumn ) {
258
+ this . table ( table , ( table ) => callback ( table ) )
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Execute a table builder callback if the given table doesn't have a given column.
264
+ *
265
+ * @param {string } table
266
+ * @param {string } column
267
+ * @param {Function } callback
268
+ * @return {Promise<void> }
269
+ */
270
+ async whenTableDoesntHaveColumn ( table , column , callback ) {
271
+ const hasColumn = await this . hasColumn ( table , column )
272
+
273
+ if ( ! hasColumn ) {
274
+ this . table ( table , ( table ) => callback ( table ) )
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Get the data type for the given column name.
280
+ *
281
+ * @param {string } table
282
+ * @param {string } column
283
+ * @param {boolean } [fullDefinition=false]
284
+ * @return {Promise<string> }
285
+ */
286
+ async getColumnType ( table , column , fullDefinition = false ) {
287
+ const columns = await this . getColumns ( table )
288
+
289
+ for ( const value of columns ) {
290
+ if ( value . name . toLowerCase ( ) === column . toLowerCase ( ) ) {
291
+ return fullDefinition ? value . type : value . type_name
292
+ }
293
+ }
294
+
295
+ throw new Error ( `InvalidArgumentException: There is no column with name '${ column } ' on table '${ table } '.` )
296
+ }
297
+
298
+ /**
299
+ * Get the column listing for a given table.
300
+ *
301
+ * @param {string } table
302
+ * @return {Promise<string[]> }
303
+ */
304
+ async getColumnListing ( table ) {
305
+ const columnList = await this . getColumns ( table )
306
+
307
+ return columnList . reduce ( ( columns , column ) => {
308
+ columns . push ( column . name )
309
+
310
+ return columns
311
+ } , [ ] )
312
+ }
313
+
34
314
/**
35
315
* Get the columns for a given table.
36
316
*
37
317
* @param {string } table
38
- * @return {array }
318
+ * @return {Promise<Array<Record<string, any>>> }
39
319
*/
40
- getColumns ( table ) {
320
+ async getColumns ( table ) {
41
321
table = this . connection . getTablePrefix ( ) + table
42
322
43
323
return this . connection . getPostProcessor ( ) . processColumns (
44
- this . connection . selectFromWriteConnection ( this . grammar . compileColumns ( table ) )
324
+ await this . connection . select ( this . grammar . compileColumns ( table ) )
325
+ )
326
+ }
327
+
328
+ /**
329
+ * Get the indexes for a given table.
330
+ *
331
+ * @param {string } table
332
+ * @return {Promise<Array<Record<string, string>>> }
333
+ */
334
+ async getIndexes ( table ) {
335
+ table = this . connection . getTablePrefix ( ) + table
336
+
337
+ return this . connection . getPostProcessor ( ) . processIndexes (
338
+ await this . connection . select ( this . grammar . compileIndexes ( table ) )
45
339
)
46
340
}
341
+
342
+ /**
343
+ * Get the names of the indexes for a given table.
344
+ *
345
+ * @param {string } table
346
+ * @return {Promise<string[]> }
347
+ */
348
+ async getIndexListing ( table ) {
349
+ const indexes = await this . getIndexes ( table )
350
+
351
+ return indexes . reduce ( ( result , index ) => {
352
+ result . push ( index . name )
353
+
354
+ return result
355
+ } , [ ] )
356
+ }
357
+
358
+ /**
359
+ * Determine if the given table has a given index.
360
+ *
361
+ * @param {string } table
362
+ * @param {string|array } index
363
+ * @param {string|null } type
364
+ * @return {Promise<boolean> }
365
+ */
366
+ async hasIndex ( table , index , type = null ) {
367
+ type = isNil ( type ) ? type : type . toLowerCase ( )
368
+
369
+ const indexes = await this . getIndexes ( table )
370
+
371
+ for ( const value of indexes ) {
372
+ const typeMatches = isNil ( type ) ||
373
+ ( type === 'primary' && value . primary ) ||
374
+ ( type === 'unique' && value . unique ) ||
375
+ type === value . type
376
+
377
+ if ( ( value . name === index || value . columns === index ) && typeMatches ) {
378
+ return true
379
+ }
380
+ }
381
+
382
+ return false
383
+ }
384
+
385
+ /**
386
+ * Get the foreign keys for a given table.
387
+ *
388
+ * @param {string } table
389
+ * @return {Promise<string[]> }
390
+ */
391
+ async getForeignKeys ( table ) {
392
+ table = this . connection . getTablePrefix ( ) + table
393
+
394
+ return this . connection . getPostProcessor ( ) . processForeignKeys (
395
+ await this . connection . select ( this . grammar . compileForeignKeys ( table ) )
396
+ )
397
+ }
398
+
399
+ /**
400
+ * Modify a table on the schema.
401
+ *
402
+ * @param {string } table
403
+ * @param {Function } callback
404
+ * @return {void }
405
+ */
406
+ table ( table , callback ) {
407
+ this . build ( this . createBlueprint ( table , callback ) )
408
+ }
409
+
410
+ /**
411
+ * Create a new table on the schema.
412
+ *
413
+ * @param {string } table
414
+ * @param {Function } callback
415
+ * @return {void }
416
+ */
417
+ create ( table , callback ) {
418
+ this . build ( tap ( this . createBlueprint ( table ) , ( blueprint ) => {
419
+ blueprint . create ( )
420
+
421
+ callback ( blueprint )
422
+ } ) )
423
+ }
424
+
425
+ /**
426
+ * Drop a table from the schema.
427
+ *
428
+ * @param {string } table
429
+ * @return {void }
430
+ */
431
+ drop ( table ) {
432
+ this . build ( tap ( this . createBlueprint ( table ) , ( blueprint ) => {
433
+ blueprint . drop ( )
434
+ } ) )
435
+ }
436
+
437
+ /**
438
+ * Drop a table from the schema if it exists.
439
+ *
440
+ * @param {string } table
441
+ * @return {void }
442
+ */
443
+ dropIfExists ( table ) {
444
+ this . build ( tap ( this . createBlueprint ( table ) , ( blueprint ) => {
445
+ blueprint . dropIfExists ( )
446
+ } ) )
447
+ }
448
+
449
+ /**
450
+ * Drop columns from a table schema.
451
+ *
452
+ * @param {string } table
453
+ * @param {string|array } columns
454
+ * @return {void }
455
+ */
456
+ dropColumns ( table , columns ) {
457
+ this . table ( table , ( blueprint ) => {
458
+ blueprint . dropColumn ( columns )
459
+ } )
460
+ }
461
+
462
+ /**
463
+ * Drop all tables from the database.
464
+ *
465
+ * @return {void }
466
+ *
467
+ * @throws {LogicException }
468
+ */
469
+ dropAllTables ( ) {
470
+ throw new Error ( 'LogicException: This database driver does not support dropping all tables.' )
471
+ }
472
+
473
+ /**
474
+ * Drop all views from the database.
475
+ *
476
+ * @return {void }
477
+ *
478
+ * @throws {LogicException }
479
+ */
480
+ dropAllViews ( ) {
481
+ throw new Error ( 'LogicException: This database driver does not support dropping all views.' )
482
+ }
483
+
484
+ /**
485
+ * Drop all types from the database.
486
+ *
487
+ * @return {void }
488
+ *
489
+ * @throws {LogicException }
490
+ */
491
+ dropAllTypes ( ) {
492
+ throw new Error ( 'LogicException: This database driver does not support dropping all types.' )
493
+ }
494
+
495
+ /**
496
+ * Rename a table on the schema.
497
+ *
498
+ * @param {string } from
499
+ * @param {string } to
500
+ * @return {void }
501
+ */
502
+ rename ( from , to ) {
503
+ this . build ( tap ( this . createBlueprint ( from ) , ( blueprint ) => {
504
+ blueprint . rename ( to )
505
+ } ) )
506
+ }
507
+
508
+ /**
509
+ * Enable foreign key constraints.
510
+ *
511
+ * @return {Promise<boolean> }
512
+ */
513
+ enableForeignKeyConstraints ( ) {
514
+ return this . connection . statement (
515
+ this . grammar . compileEnableForeignKeyConstraints ( )
516
+ )
517
+ }
518
+
519
+ /**
520
+ * Disable foreign key constraints.
521
+ *
522
+ * @return {Promise<boolean> }
523
+ */
524
+ disableForeignKeyConstraints ( ) {
525
+ return this . connection . statement (
526
+ this . grammar . compileDisableForeignKeyConstraints ( )
527
+ )
528
+ }
529
+
530
+ /**
531
+ * Disable foreign key constraints during the execution of a callback.
532
+ *
533
+ * @param {Function } callback
534
+ * @return {any }
535
+ */
536
+ withoutForeignKeyConstraints ( callback ) {
537
+ this . disableForeignKeyConstraints ( )
538
+
539
+ try {
540
+ return callback ( )
541
+ } finally {
542
+ this . enableForeignKeyConstraints ( )
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Execute the blueprint to build / modify the table.
548
+ *
549
+ * @protected
550
+ * @param {Blueprint } blueprint
551
+ * @return {void }
552
+ */
553
+ build ( blueprint ) {
554
+ blueprint . build ( this . connection , this . grammar )
555
+ }
556
+
557
+ /**
558
+ * Create a new command set with a Closure.
559
+ *
560
+ * @protected
561
+ * @param {string } table
562
+ * @param {Function|null } [callback]
563
+ * @return {Blueprint }
564
+ */
565
+ createBlueprint ( table , callback = null ) {
566
+ const prefix = this . connection . getConfig ( 'prefix_indexes' )
567
+ ? this . connection . getConfig ( 'prefix' )
568
+ : ''
569
+
570
+ if ( ! isNil ( this . resolver ) ) {
571
+ return this . resolver ( table , callback , prefix )
572
+ }
573
+
574
+ return new Blueprint ( table , callback , prefix )
575
+ }
576
+
577
+ /**
578
+ * Get the database connection instance.
579
+ *
580
+ * @return {Connection }
581
+ */
582
+ getConnection ( ) {
583
+ return this . connection
584
+ }
585
+
586
+ /**
587
+ * Set the database connection instance.
588
+ *
589
+ * @param {Connection } connection
590
+ * @return {this }
591
+ */
592
+ setConnection ( connection ) {
593
+ this . connection = connection
594
+
595
+ return this
596
+ }
597
+
598
+ /**
599
+ * Set the Schema Blueprint resolver callback.
600
+ *
601
+ * @param {Function } resolver
602
+ * @return {void }
603
+ */
604
+ blueprintResolver ( resolver ) {
605
+ this . resolver = resolver
606
+ }
47
607
}
0 commit comments