masterrecord 0.3.29 → 0.3.30
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/Entity/entityModel.js +9 -1
- package/Migrations/migrationMySQLQuery.js +14 -0
- package/Migrations/migrationPostgresQuery.js +14 -0
- package/Migrations/migrationSQLiteQuery.js +14 -0
- package/Migrations/migrationTemplate.js +38 -0
- package/Migrations/migrations.js +107 -3
- package/Migrations/schema.js +66 -0
- package/package.json +1 -1
- package/readme.md +157 -7
package/Entity/entityModel.js
CHANGED
|
@@ -111,8 +111,16 @@ class EntityModel {
|
|
|
111
111
|
|
|
112
112
|
unique(){
|
|
113
113
|
this.obj.unique = true; // yes
|
|
114
|
-
return this;
|
|
114
|
+
return this;
|
|
115
|
+
|
|
116
|
+
}
|
|
115
117
|
|
|
118
|
+
index(indexName){
|
|
119
|
+
if(!this.obj.indexes){
|
|
120
|
+
this.obj.indexes = [];
|
|
121
|
+
}
|
|
122
|
+
this.obj.indexes.push(indexName || true);
|
|
123
|
+
return this;
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
// this means that it can be an empty field
|
|
@@ -232,6 +232,20 @@ class migrationMySQLQuery {
|
|
|
232
232
|
return `ALTER TABLE \`${table.tableName}\` RENAME COLUMN \`${table.name}\` TO \`${table.newName}\``
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
createIndex(indexInfo){
|
|
236
|
+
const indexName = indexInfo.indexName === true
|
|
237
|
+
? `idx_${indexInfo.tableName.toLowerCase()}_${indexInfo.columnName.toLowerCase()}`
|
|
238
|
+
: indexInfo.indexName;
|
|
239
|
+
return `CREATE INDEX \`${indexName}\` ON \`${indexInfo.tableName}\`(\`${indexInfo.columnName}\`)`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
dropIndex(indexInfo){
|
|
243
|
+
const indexName = indexInfo.indexName === true
|
|
244
|
+
? `idx_${indexInfo.tableName.toLowerCase()}_${indexInfo.columnName.toLowerCase()}`
|
|
245
|
+
: indexInfo.indexName;
|
|
246
|
+
return `DROP INDEX \`${indexName}\` ON \`${indexInfo.tableName}\``;
|
|
247
|
+
}
|
|
248
|
+
|
|
235
249
|
/**
|
|
236
250
|
* SEED DATA METHODS
|
|
237
251
|
* Support for inserting seed data during migrations
|
|
@@ -219,6 +219,20 @@ class migrationPostgresQuery {
|
|
|
219
219
|
return `ALTER TABLE "${table.tableName}" RENAME COLUMN "${table.name}" TO "${table.newName}"`;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
createIndex(indexInfo){
|
|
223
|
+
const indexName = indexInfo.indexName === true
|
|
224
|
+
? `idx_${indexInfo.tableName.toLowerCase()}_${indexInfo.columnName.toLowerCase()}`
|
|
225
|
+
: indexInfo.indexName;
|
|
226
|
+
return `CREATE INDEX IF NOT EXISTS "${indexName}" ON "${indexInfo.tableName}"("${indexInfo.columnName}")`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
dropIndex(indexInfo){
|
|
230
|
+
const indexName = indexInfo.indexName === true
|
|
231
|
+
? `idx_${indexInfo.tableName.toLowerCase()}_${indexInfo.columnName.toLowerCase()}`
|
|
232
|
+
: indexInfo.indexName;
|
|
233
|
+
return `DROP INDEX IF EXISTS "${indexName}"`;
|
|
234
|
+
}
|
|
235
|
+
|
|
222
236
|
/**
|
|
223
237
|
* SEED DATA METHODS
|
|
224
238
|
* Support for inserting seed data during migrations
|
|
@@ -187,6 +187,20 @@ class migrationSQLiteQuery {
|
|
|
187
187
|
return `ALTER TABLE ${table.tableName} RENAME COLUMN ${table.name} TO ${table.newName}`
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
createIndex(indexInfo){
|
|
191
|
+
const indexName = indexInfo.indexName === true
|
|
192
|
+
? `idx_${indexInfo.tableName.toLowerCase()}_${indexInfo.columnName.toLowerCase()}`
|
|
193
|
+
: indexInfo.indexName;
|
|
194
|
+
return `CREATE INDEX IF NOT EXISTS ${indexName} ON ${indexInfo.tableName}(${indexInfo.columnName})`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
dropIndex(indexInfo){
|
|
198
|
+
const indexName = indexInfo.indexName === true
|
|
199
|
+
? `idx_${indexInfo.tableName.toLowerCase()}_${indexInfo.columnName.toLowerCase()}`
|
|
200
|
+
: indexInfo.indexName;
|
|
201
|
+
return `DROP INDEX IF EXISTS ${indexName}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
190
204
|
/**
|
|
191
205
|
* SEED DATA METHODS
|
|
192
206
|
* Support for inserting seed data during migrations
|
|
@@ -82,6 +82,44 @@ module.exports = ${this.name};
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
createIndex(type, indexInfo){
|
|
86
|
+
const indexName = indexInfo.indexName === true
|
|
87
|
+
? `idx_${indexInfo.tableName.toLowerCase()}_${indexInfo.columnName.toLowerCase()}`
|
|
88
|
+
: indexInfo.indexName;
|
|
89
|
+
|
|
90
|
+
const indexInfoStr = JSON.stringify({
|
|
91
|
+
tableName: indexInfo.tableName,
|
|
92
|
+
columnName: indexInfo.columnName,
|
|
93
|
+
indexName: indexInfo.indexName
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if(type === "up"){
|
|
97
|
+
this.#up += os.EOL + ` this.createIndex(${indexInfoStr});`
|
|
98
|
+
}
|
|
99
|
+
else{
|
|
100
|
+
this.#down += os.EOL + ` this.dropIndex(${indexInfoStr});`
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
dropIndex(type, indexInfo){
|
|
105
|
+
const indexName = indexInfo.indexName === true
|
|
106
|
+
? `idx_${indexInfo.tableName.toLowerCase()}_${indexInfo.columnName.toLowerCase()}`
|
|
107
|
+
: indexInfo.indexName;
|
|
108
|
+
|
|
109
|
+
const indexInfoStr = JSON.stringify({
|
|
110
|
+
tableName: indexInfo.tableName,
|
|
111
|
+
columnName: indexInfo.columnName,
|
|
112
|
+
indexName: indexInfo.indexName
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if(type === "up"){
|
|
116
|
+
this.#up += os.EOL + ` this.dropIndex(${indexInfoStr});`
|
|
117
|
+
}
|
|
118
|
+
else{
|
|
119
|
+
this.#down += os.EOL + ` this.createIndex(${indexInfoStr});`
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
85
123
|
}
|
|
86
124
|
|
|
87
125
|
module.exports = MigrationTemplate;
|
package/Migrations/migrations.js
CHANGED
|
@@ -24,7 +24,9 @@ class Migrations{
|
|
|
24
24
|
newColumns : [],
|
|
25
25
|
newTables : [],
|
|
26
26
|
deletedColumns : [],
|
|
27
|
-
updatedColumns : []
|
|
27
|
+
updatedColumns : [],
|
|
28
|
+
newIndexes : [],
|
|
29
|
+
deletedIndexes : []
|
|
28
30
|
}
|
|
29
31
|
tables.push(table);
|
|
30
32
|
});
|
|
@@ -38,7 +40,9 @@ class Migrations{
|
|
|
38
40
|
newColumns : [],
|
|
39
41
|
newTables : [],
|
|
40
42
|
deletedColumns : [],
|
|
41
|
-
updatedColumns : []
|
|
43
|
+
updatedColumns : [],
|
|
44
|
+
newIndexes : [],
|
|
45
|
+
deletedIndexes : []
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
oldSchema.forEach(function (oldItem, index) {
|
|
@@ -156,11 +160,101 @@ class Migrations{
|
|
|
156
160
|
#buildMigrationObject(oldSchema, newSchema){
|
|
157
161
|
|
|
158
162
|
var tables = this.#organizeSchemaByTables(oldSchema, newSchema);
|
|
159
|
-
|
|
163
|
+
|
|
160
164
|
tables = this.#findNewTables(tables);
|
|
161
165
|
tables = this.#findNewColumns(tables);
|
|
162
166
|
tables = this.#findDeletedColumns(tables);
|
|
163
167
|
tables = this.#findUpdatedColumns(tables);
|
|
168
|
+
tables = this.#findNewIndexes(tables);
|
|
169
|
+
tables = this.#findDeletedIndexes(tables);
|
|
170
|
+
return tables;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#findNewIndexes(tables){
|
|
174
|
+
tables.forEach(function (item, index) {
|
|
175
|
+
if(item.new && item.old){
|
|
176
|
+
Object.keys(item.new).forEach(function (key) {
|
|
177
|
+
if(typeof item.new[key] === "object" && item.new[key].indexes){
|
|
178
|
+
var columnName = item.new[key].name;
|
|
179
|
+
var newIndexes = item.new[key].indexes;
|
|
180
|
+
|
|
181
|
+
// Check if this column existed before
|
|
182
|
+
var oldColumn = null;
|
|
183
|
+
Object.keys(item.old).forEach(function (oldKey) {
|
|
184
|
+
if(typeof item.old[oldKey] === "object" && item.old[oldKey].name === columnName){
|
|
185
|
+
oldColumn = item.old[oldKey];
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// If column didn't exist before, or didn't have indexes, all indexes are new
|
|
190
|
+
if(!oldColumn || !oldColumn.indexes){
|
|
191
|
+
newIndexes.forEach(function(indexName){
|
|
192
|
+
item.newIndexes.push({
|
|
193
|
+
tableName: item.name,
|
|
194
|
+
columnName: columnName,
|
|
195
|
+
indexName: indexName
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
// Check for new indexes that weren't in the old column
|
|
200
|
+
newIndexes.forEach(function(indexName){
|
|
201
|
+
if(!oldColumn.indexes.includes(indexName)){
|
|
202
|
+
item.newIndexes.push({
|
|
203
|
+
tableName: item.name,
|
|
204
|
+
columnName: columnName,
|
|
205
|
+
indexName: indexName
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return tables;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#findDeletedIndexes(tables){
|
|
218
|
+
tables.forEach(function (item, index) {
|
|
219
|
+
if(item.new && item.old){
|
|
220
|
+
Object.keys(item.old).forEach(function (key) {
|
|
221
|
+
if(typeof item.old[key] === "object" && item.old[key].indexes){
|
|
222
|
+
var columnName = item.old[key].name;
|
|
223
|
+
var oldIndexes = item.old[key].indexes;
|
|
224
|
+
|
|
225
|
+
// Check if this column still exists
|
|
226
|
+
var newColumn = null;
|
|
227
|
+
Object.keys(item.new).forEach(function (newKey) {
|
|
228
|
+
if(typeof item.new[newKey] === "object" && item.new[newKey].name === columnName){
|
|
229
|
+
newColumn = item.new[newKey];
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// If column doesn't exist anymore, or doesn't have indexes, all indexes are deleted
|
|
234
|
+
if(!newColumn || !newColumn.indexes){
|
|
235
|
+
oldIndexes.forEach(function(indexName){
|
|
236
|
+
item.deletedIndexes.push({
|
|
237
|
+
tableName: item.name,
|
|
238
|
+
columnName: columnName,
|
|
239
|
+
indexName: indexName
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
// Check for indexes that were removed
|
|
244
|
+
oldIndexes.forEach(function(indexName){
|
|
245
|
+
if(!newColumn.indexes.includes(indexName)){
|
|
246
|
+
item.deletedIndexes.push({
|
|
247
|
+
tableName: item.name,
|
|
248
|
+
columnName: columnName,
|
|
249
|
+
indexName: indexName
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
164
258
|
return tables;
|
|
165
259
|
}
|
|
166
260
|
|
|
@@ -302,6 +396,8 @@ class Migrations{
|
|
|
302
396
|
(t.newColumns && t.newColumns.length) ||
|
|
303
397
|
(t.deletedColumns && t.deletedColumns.length) ||
|
|
304
398
|
(t.updatedColumns && t.updatedColumns.length) ||
|
|
399
|
+
(t.newIndexes && t.newIndexes.length) ||
|
|
400
|
+
(t.deletedIndexes && t.deletedIndexes.length) ||
|
|
305
401
|
(t.old === null) || (t.new === null)){
|
|
306
402
|
return true;
|
|
307
403
|
}
|
|
@@ -348,6 +444,14 @@ class Migrations{
|
|
|
348
444
|
}
|
|
349
445
|
});
|
|
350
446
|
|
|
447
|
+
item.newIndexes.forEach(function (indexInfo, index) {
|
|
448
|
+
MT.createIndex("up", indexInfo);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
item.deletedIndexes.forEach(function (indexInfo, index) {
|
|
452
|
+
MT.dropIndex("up", indexInfo);
|
|
453
|
+
});
|
|
454
|
+
|
|
351
455
|
});
|
|
352
456
|
|
|
353
457
|
return MT.get();
|
package/Migrations/schema.js
CHANGED
|
@@ -110,6 +110,22 @@ class schema{
|
|
|
110
110
|
var query = queryBuilder.createTable(table);
|
|
111
111
|
this.context._execute(query);
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
// Create indexes for columns that have .index() defined
|
|
115
|
+
const self = this;
|
|
116
|
+
Object.keys(table).forEach(function(key){
|
|
117
|
+
if(typeof table[key] === "object" && table[key].indexes && !key.startsWith('__')){
|
|
118
|
+
const columnName = table[key].name;
|
|
119
|
+
table[key].indexes.forEach(function(indexName){
|
|
120
|
+
const indexInfo = {
|
|
121
|
+
tableName: tableName,
|
|
122
|
+
columnName: columnName,
|
|
123
|
+
indexName: indexName
|
|
124
|
+
};
|
|
125
|
+
self.createIndex(indexInfo);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
113
129
|
}
|
|
114
130
|
}else{
|
|
115
131
|
console.log("Table that you're trying to create is undefined. Please check if there are any changes that need to be made");
|
|
@@ -394,6 +410,56 @@ class schema{
|
|
|
394
410
|
}
|
|
395
411
|
}
|
|
396
412
|
|
|
413
|
+
createIndex(indexInfo){
|
|
414
|
+
if(indexInfo){
|
|
415
|
+
if(this.context.isSQLite){
|
|
416
|
+
var sqliteQuery = require("./migrationSQLiteQuery");
|
|
417
|
+
var queryBuilder = new sqliteQuery();
|
|
418
|
+
var query = queryBuilder.createIndex(indexInfo);
|
|
419
|
+
this.context._execute(query);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if(this.context.isMySQL){
|
|
423
|
+
var sqlquery = require("./migrationMySQLQuery");
|
|
424
|
+
var queryBuilder = new sqlquery();
|
|
425
|
+
var query = queryBuilder.createIndex(indexInfo);
|
|
426
|
+
this.context._execute(query);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if(this.context.isPostgres){
|
|
430
|
+
var postgresQuery = require("./migrationPostgresQuery");
|
|
431
|
+
var queryBuilder = new postgresQuery();
|
|
432
|
+
var query = queryBuilder.createIndex(indexInfo);
|
|
433
|
+
this.context._execute(query);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
dropIndex(indexInfo){
|
|
439
|
+
if(indexInfo){
|
|
440
|
+
if(this.context.isSQLite){
|
|
441
|
+
var sqliteQuery = require("./migrationSQLiteQuery");
|
|
442
|
+
var queryBuilder = new sqliteQuery();
|
|
443
|
+
var query = queryBuilder.dropIndex(indexInfo);
|
|
444
|
+
this.context._execute(query);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if(this.context.isMySQL){
|
|
448
|
+
var sqlquery = require("./migrationMySQLQuery");
|
|
449
|
+
var queryBuilder = new sqlquery();
|
|
450
|
+
var query = queryBuilder.dropIndex(indexInfo);
|
|
451
|
+
this.context._execute(query);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if(this.context.isPostgres){
|
|
455
|
+
var postgresQuery = require("./migrationPostgresQuery");
|
|
456
|
+
var queryBuilder = new postgresQuery();
|
|
457
|
+
var query = queryBuilder.dropIndex(indexInfo);
|
|
458
|
+
this.context._execute(query);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
397
463
|
seed(tableName, rows){
|
|
398
464
|
if(!tableName || !rows){ return; }
|
|
399
465
|
const items = Array.isArray(rows) ? rows : [rows];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.30",
|
|
4
4
|
"description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
|
|
5
5
|
"main": "MasterRecord.js",
|
|
6
6
|
"bin": {
|
package/readme.md
CHANGED
|
@@ -1790,6 +1790,116 @@ try {
|
|
|
1790
1790
|
|
|
1791
1791
|
---
|
|
1792
1792
|
|
|
1793
|
+
## Field Constraints & Indexes
|
|
1794
|
+
|
|
1795
|
+
Define database constraints and performance indexes using the fluent API:
|
|
1796
|
+
|
|
1797
|
+
```javascript
|
|
1798
|
+
class User {
|
|
1799
|
+
id(db) {
|
|
1800
|
+
db.integer().primary().auto();
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
email(db) {
|
|
1804
|
+
db.string()
|
|
1805
|
+
.notNullable()
|
|
1806
|
+
.unique()
|
|
1807
|
+
.index(); // Creates performance index
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
username(db) {
|
|
1811
|
+
db.string()
|
|
1812
|
+
.notNullable()
|
|
1813
|
+
.index('idx_username_custom'); // Custom index name
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
status(db) {
|
|
1817
|
+
db.string().nullable();
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
created_at(db) {
|
|
1821
|
+
db.timestamp().default('CURRENT_TIMESTAMP');
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
```
|
|
1825
|
+
|
|
1826
|
+
### Available Constraint Methods
|
|
1827
|
+
|
|
1828
|
+
- `.notNullable()` - Column cannot be NULL
|
|
1829
|
+
- `.nullable()` - Column can be NULL (default)
|
|
1830
|
+
- `.unique()` - Unique constraint (enforces uniqueness at DB level)
|
|
1831
|
+
- `.index()` - Creates performance index (auto-generated name: `idx_tablename_columnname`)
|
|
1832
|
+
- `.index('custom_name')` - Creates index with custom name
|
|
1833
|
+
- `.primary()` - Primary key (automatically indexed)
|
|
1834
|
+
- `.default(value)` - Default value
|
|
1835
|
+
|
|
1836
|
+
### Index vs Unique Constraint
|
|
1837
|
+
|
|
1838
|
+
**Understanding the difference:**
|
|
1839
|
+
|
|
1840
|
+
- `.unique()` creates a UNIQUE constraint (prevents duplicate values, enforces data integrity)
|
|
1841
|
+
- `.index()` creates a performance index (improves query speed, allows duplicates)
|
|
1842
|
+
- You can use both together: `.unique().index()` creates a unique index for both integrity and performance
|
|
1843
|
+
|
|
1844
|
+
**Examples:**
|
|
1845
|
+
|
|
1846
|
+
```javascript
|
|
1847
|
+
// Email must be unique (no performance index)
|
|
1848
|
+
email(db) {
|
|
1849
|
+
db.string().notNullable().unique();
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// Username indexed for fast lookups (allows duplicates)
|
|
1853
|
+
username(db) {
|
|
1854
|
+
db.string().notNullable().index();
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// Email with both unique constraint AND performance index
|
|
1858
|
+
email(db) {
|
|
1859
|
+
db.string().notNullable().unique().index();
|
|
1860
|
+
}
|
|
1861
|
+
```
|
|
1862
|
+
|
|
1863
|
+
### Automatic Index Migration
|
|
1864
|
+
|
|
1865
|
+
When you add `.index()` to a field, MasterRecord automatically generates migration code:
|
|
1866
|
+
|
|
1867
|
+
```javascript
|
|
1868
|
+
// In your entity
|
|
1869
|
+
class User {
|
|
1870
|
+
email(db) {
|
|
1871
|
+
db.string().notNullable().index();
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// Generated migration (automatic)
|
|
1876
|
+
class Migration_20250101 extends masterrecord.schema {
|
|
1877
|
+
async up(table) {
|
|
1878
|
+
this.init(table);
|
|
1879
|
+
this.createIndex({
|
|
1880
|
+
tableName: 'User',
|
|
1881
|
+
columnName: 'email',
|
|
1882
|
+
indexName: 'idx_user_email'
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
async down(table) {
|
|
1887
|
+
this.init(table);
|
|
1888
|
+
this.dropIndex({
|
|
1889
|
+
tableName: 'User',
|
|
1890
|
+
columnName: 'email',
|
|
1891
|
+
indexName: 'idx_user_email'
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
```
|
|
1896
|
+
|
|
1897
|
+
**Rollback support:**
|
|
1898
|
+
|
|
1899
|
+
Migrations automatically include rollback logic. Running `masterrecord migrate down` will drop all indexes created by that migration.
|
|
1900
|
+
|
|
1901
|
+
---
|
|
1902
|
+
|
|
1793
1903
|
## Business Logic Validation
|
|
1794
1904
|
|
|
1795
1905
|
Add validators to your entity definitions for automatic validation on property assignment.
|
|
@@ -2301,20 +2411,60 @@ await db.saveChanges(); // Batch insert
|
|
|
2301
2411
|
|
|
2302
2412
|
### 3. Use Indexes
|
|
2303
2413
|
|
|
2414
|
+
Define indexes directly in your entity using `.index()`:
|
|
2415
|
+
|
|
2304
2416
|
```javascript
|
|
2305
2417
|
class User {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2418
|
+
id(db) {
|
|
2419
|
+
db.integer().primary().auto(); // Primary keys are automatically indexed
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
email(db) {
|
|
2423
|
+
db.string()
|
|
2424
|
+
.notNullable()
|
|
2425
|
+
.unique()
|
|
2426
|
+
.index(); // Creates: idx_user_email
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
last_name(db) {
|
|
2430
|
+
db.string().index(); // Creates: idx_user_last_name
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
status(db) {
|
|
2434
|
+
db.string().index('idx_user_status'); // Custom index name
|
|
2311
2435
|
}
|
|
2312
2436
|
}
|
|
2437
|
+
```
|
|
2438
|
+
|
|
2439
|
+
**Migration automatically generates:**
|
|
2313
2440
|
|
|
2314
|
-
|
|
2315
|
-
//
|
|
2441
|
+
```javascript
|
|
2442
|
+
// In migration file (generated automatically)
|
|
2443
|
+
this.createIndex({
|
|
2444
|
+
tableName: 'User',
|
|
2445
|
+
columnName: 'email',
|
|
2446
|
+
indexName: 'idx_user_email'
|
|
2447
|
+
});
|
|
2316
2448
|
```
|
|
2317
2449
|
|
|
2450
|
+
**Rollback support:**
|
|
2451
|
+
|
|
2452
|
+
```javascript
|
|
2453
|
+
// Down migration automatically includes
|
|
2454
|
+
this.dropIndex({
|
|
2455
|
+
tableName: 'User',
|
|
2456
|
+
columnName: 'email',
|
|
2457
|
+
indexName: 'idx_user_email'
|
|
2458
|
+
});
|
|
2459
|
+
```
|
|
2460
|
+
|
|
2461
|
+
**Best practices:**
|
|
2462
|
+
- Index columns used in WHERE clauses
|
|
2463
|
+
- Index foreign key columns for join performance
|
|
2464
|
+
- Don't over-index - each index adds write overhead
|
|
2465
|
+
- Primary keys are automatically indexed (no need for `.index()`)
|
|
2466
|
+
- Use `.unique()` for data integrity, `.index()` for query performance
|
|
2467
|
+
|
|
2318
2468
|
### 4. Limit Result Sets
|
|
2319
2469
|
|
|
2320
2470
|
```javascript
|