Skip to content

Commit dbfd410

Browse files
authoredAug 10, 2024··
Merge pull request #20 from aagamezl/aa/feat/complete-schema-builder
feat(builder): complete schema builder
2 parents 2aa0c65 + 90d7694 commit dbfd410

File tree

4 files changed

+573
-14
lines changed

4 files changed

+573
-14
lines changed
 

‎src/Illuminate/Database/Query/Processors/Processor.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export default class Processor {
1111
/**
1212
* Process the results of a columns query.
1313
*
14-
* @param {Array<Object>} results
15-
* @return {Array<Object>}
14+
* @param {Array<Record<string, any>>} results
15+
* @return {Array<Record<string, any>>}
1616
*/
1717
processColumns (results) {
1818
return results
@@ -40,8 +40,8 @@ export default class Processor {
4040
/**
4141
* Process the results of a foreign keys query.
4242
*
43-
* @param {unknown[]} results
44-
* @return {unknown[]}
43+
* @param {Array<Record<string, any>>} results
44+
* @return {Array<Record<string, any>>}
4545
*/
4646
processForeignKeys (results) {
4747
return results
@@ -50,8 +50,8 @@ export default class Processor {
5050
/**
5151
* Process the results of an indexes query.
5252
*
53-
* @param {unknown[]} results
54-
* @return {unknown[]}
53+
* @param {Record<string, any>[]} results
54+
* @return {Record<string, any>[]}
5555
*/
5656
processIndexes (results) {
5757
return results

‎src/Illuminate/Database/Schema/Blueprint.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ export default class Blueprint extends mix().use(Macroable) {
719719
* @returns {ColumnDefinition}
720720
*/
721721
char (column, length = null) {
722-
length = length !== null ? length : Builder.defaultStringLength
722+
length = length !== null ? length : Builder.defaultStringLengthProperty
723723

724724
return this.addColumn('char', column, { length })
725725
}
@@ -732,7 +732,7 @@ export default class Blueprint extends mix().use(Macroable) {
732732
* @returns {ColumnDefinition}
733733
*/
734734
string (column, length = null) {
735-
length = length !== null ? length : Builder.defaultStringLength
735+
length = length !== null ? length : Builder.defaultStringLengthProperty
736736

737737
return this.addColumn('string', column, { length })
738738
}

‎src/Illuminate/Database/Schema/Builder.js

+565-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
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+
19
export default class Builder {
210
/**
311
* The database connection instance.
@@ -7,19 +15,35 @@ export default class Builder {
715
*/
816
connection
917

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+
1034
/**
1135
* The default string length for migrations.
1236
*
1337
* @type {number|undefined}
1438
*/
15-
static defaultStringLength = 255
39+
static defaultStringLengthProperty = 255
1640

1741
/**
1842
* The default relationship morph key type.
1943
*
2044
* @type {string}
2145
*/
22-
static defaultMorphKeyType = 'int'
46+
static defaultMorphKeyTypeProperty = 'int'
2347

2448
/**
2549
* Create a new database Schema manager.
@@ -31,17 +55,553 @@ export default class Builder {
3155
this.grammar = connection.getSchemaGrammar()
3256
}
3357

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+
34314
/**
35315
* Get the columns for a given table.
36316
*
37317
* @param {string} table
38-
* @return {array}
318+
* @return {Promise<Array<Record<string, any>>>}
39319
*/
40-
getColumns (table) {
320+
async getColumns (table) {
41321
table = this.connection.getTablePrefix() + table
42322

43323
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))
45339
)
46340
}
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+
}
47607
}

‎src/Illuminate/Database/Schema/Grammars/Grammar.js

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Fluent from '../../../Support/Fluent.js'
44
import { CustomException, ucfirst } from '../../../Support/helpers.js'
55
import { Expression } from '../../Query/internal.js'
66

7-
/** @typedef {import('../../Query/Expression.js').default} Expression */
87
/** @typedef {import('../../Connection.js').default} Connection */
98

109
export default class Grammar extends BaseGrammar {

0 commit comments

Comments
 (0)
Please sign in to comment.