leoric 2.2.2 → 2.3.1
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/History.md +31 -0
- package/index.js +3 -0
- package/package.json +5 -3
- package/src/adapters/sequelize.js +9 -4
- package/src/bone.js +114 -40
- package/src/collection.js +2 -2
- package/src/constants.js +8 -1
- package/src/data_types.js +65 -12
- package/src/decorators.js +77 -0
- package/src/decorators.js.map +1 -0
- package/src/decorators.ts +96 -0
- package/src/drivers/abstract/attribute.js +17 -3
- package/src/drivers/abstract/schema.js +5 -2
- package/src/drivers/abstract/spellbook.js +15 -15
- package/src/drivers/mysql/attribute.js +11 -2
- package/src/drivers/mysql/index.js +1 -1
- package/src/drivers/mysql/schema.js +1 -1
- package/src/drivers/mysql/spellbook.js +4 -4
- package/src/drivers/postgres/index.js +1 -1
- package/src/drivers/postgres/schema.js +7 -1
- package/src/drivers/sqlite/index.js +1 -1
- package/src/drivers/sqlite/schema.js +10 -2
- package/src/drivers/sqlite/spellbook.js +1 -1
- package/src/expr_formatter.js +3 -0
- package/src/setup_hooks.js +21 -2
- package/src/spell.js +63 -29
- package/types/data_types.d.ts +15 -1
- package/types/index.d.ts +13 -7
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BelongsTo = exports.HasOne = exports.HasMany = exports.Column = void 0;
|
|
7
|
+
const data_types_1 = __importDefault(require("./data_types"));
|
|
8
|
+
const constants_1 = require("./constants");
|
|
9
|
+
require("reflect-metadata");
|
|
10
|
+
function findType(tsType) {
|
|
11
|
+
const { BIGINT, INTEGER, DATE, STRING, BOOLEAN, } = data_types_1.default;
|
|
12
|
+
switch (tsType) {
|
|
13
|
+
case BigInt:
|
|
14
|
+
return BIGINT;
|
|
15
|
+
case Number:
|
|
16
|
+
return INTEGER;
|
|
17
|
+
case Date:
|
|
18
|
+
return DATE;
|
|
19
|
+
case String:
|
|
20
|
+
return STRING;
|
|
21
|
+
case Boolean:
|
|
22
|
+
return BOOLEAN;
|
|
23
|
+
default:
|
|
24
|
+
throw new Error(`unknown typescript type ${tsType}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function Column(options = {}) {
|
|
28
|
+
return function (target, propertyKey) {
|
|
29
|
+
if (options['prototype'] instanceof data_types_1.default)
|
|
30
|
+
options = { type: options };
|
|
31
|
+
if (!('type' in options)) {
|
|
32
|
+
const tsType = Reflect.getMetadata('design:type', target, propertyKey);
|
|
33
|
+
options['type'] = findType(tsType);
|
|
34
|
+
}
|
|
35
|
+
// narrowing the type of options to ColumnOption
|
|
36
|
+
if (!('type' in options))
|
|
37
|
+
throw new Error(`unknown column options ${options}`);
|
|
38
|
+
// target refers to model prototype, an internal instance of `Bone {}`
|
|
39
|
+
const model = target.constructor;
|
|
40
|
+
const { attributes = (model.attributes = {}) } = model;
|
|
41
|
+
const { name: columnName, ...restOptions } = options;
|
|
42
|
+
attributes[propertyKey] = { ...restOptions, columnName };
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
exports.Column = Column;
|
|
46
|
+
const { hasMany, hasOne, belongsTo } = constants_1.ASSOCIATE_METADATA_MAP;
|
|
47
|
+
function HasMany(options) {
|
|
48
|
+
return function (target, propertyKey) {
|
|
49
|
+
const model = target.constructor;
|
|
50
|
+
Reflect.defineMetadata(hasMany, {
|
|
51
|
+
...Reflect.getMetadata(hasMany, model),
|
|
52
|
+
[propertyKey]: options,
|
|
53
|
+
}, model);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
exports.HasMany = HasMany;
|
|
57
|
+
function HasOne(options) {
|
|
58
|
+
return function (target, propertyKey) {
|
|
59
|
+
const model = target.constructor;
|
|
60
|
+
Reflect.defineMetadata(hasOne, {
|
|
61
|
+
...Reflect.getMetadata(hasOne, model),
|
|
62
|
+
[propertyKey]: options,
|
|
63
|
+
}, model);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
exports.HasOne = HasOne;
|
|
67
|
+
function BelongsTo(options) {
|
|
68
|
+
return function (target, propertyKey) {
|
|
69
|
+
const model = target.constructor;
|
|
70
|
+
Reflect.defineMetadata(belongsTo, {
|
|
71
|
+
...Reflect.getMetadata(belongsTo, model),
|
|
72
|
+
[propertyKey]: options,
|
|
73
|
+
}, model);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
exports.BelongsTo = BelongsTo;
|
|
77
|
+
//# sourceMappingURL=decorators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["decorators.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import Bone from './bone';
|
|
2
|
+
import DataType from './data_types';
|
|
3
|
+
import { ASSOCIATE_METADATA_MAP } from './constants';
|
|
4
|
+
import 'reflect-metadata';
|
|
5
|
+
|
|
6
|
+
type DataTypes<T> = {
|
|
7
|
+
[Property in keyof T as Exclude<Property, "toSqlString">]: T[Property];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ColumnOption {
|
|
11
|
+
type?: DataTypes<DataType>;
|
|
12
|
+
name?: string;
|
|
13
|
+
defaultValue?: null | boolean | number | string | Date | JSON;
|
|
14
|
+
allowNull?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function findType(tsType) {
|
|
18
|
+
const {
|
|
19
|
+
BIGINT, INTEGER,
|
|
20
|
+
DATE,
|
|
21
|
+
STRING,
|
|
22
|
+
BOOLEAN,
|
|
23
|
+
} = DataType;
|
|
24
|
+
|
|
25
|
+
switch (tsType) {
|
|
26
|
+
case BigInt:
|
|
27
|
+
return BIGINT;
|
|
28
|
+
case Number:
|
|
29
|
+
return INTEGER;
|
|
30
|
+
case Date:
|
|
31
|
+
return DATE;
|
|
32
|
+
case String:
|
|
33
|
+
return STRING;
|
|
34
|
+
case Boolean:
|
|
35
|
+
return BOOLEAN;
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`unknown typescript type ${tsType}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function Column(options: ColumnOption | DataTypes<DataType> = {}) {
|
|
42
|
+
return function(target: Bone, propertyKey: string) {
|
|
43
|
+
if (options['prototype'] instanceof DataType) options = { type: options };
|
|
44
|
+
|
|
45
|
+
if (!('type' in options)) {
|
|
46
|
+
const tsType = Reflect.getMetadata('design:type', target, propertyKey);
|
|
47
|
+
options['type'] = findType(tsType);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// narrowing the type of options to ColumnOption
|
|
51
|
+
if (!('type' in options)) throw new Error(`unknown column options ${options}`);
|
|
52
|
+
|
|
53
|
+
// target refers to model prototype, an internal instance of `Bone {}`
|
|
54
|
+
const model = target.constructor;
|
|
55
|
+
const { attributes = (model.attributes = {}) } = model;
|
|
56
|
+
const { name: columnName, ...restOptions } = options;
|
|
57
|
+
attributes[propertyKey] = { ...restOptions, columnName };
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface AssociateOptions {
|
|
62
|
+
className?: string;
|
|
63
|
+
foreignKey?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { hasMany, hasOne, belongsTo } = ASSOCIATE_METADATA_MAP;
|
|
67
|
+
|
|
68
|
+
export function HasMany(options?: AssociateOptions) {
|
|
69
|
+
return function(target: Bone, propertyKey: string) {
|
|
70
|
+
const model = target.constructor;
|
|
71
|
+
Reflect.defineMetadata(hasMany, {
|
|
72
|
+
...Reflect.getMetadata(hasMany, model),
|
|
73
|
+
[propertyKey]: options,
|
|
74
|
+
}, model);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function HasOne(options?: AssociateOptions) {
|
|
79
|
+
return function(target: Bone, propertyKey: string) {
|
|
80
|
+
const model = target.constructor;
|
|
81
|
+
Reflect.defineMetadata(hasOne, {
|
|
82
|
+
...Reflect.getMetadata(hasOne, model),
|
|
83
|
+
[propertyKey]: options,
|
|
84
|
+
}, model);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function BelongsTo(options?: AssociateOptions) {
|
|
89
|
+
return function(target: Bone, propertyKey: string) {
|
|
90
|
+
const model = target.constructor;
|
|
91
|
+
Reflect.defineMetadata(belongsTo, {
|
|
92
|
+
...Reflect.getMetadata(belongsTo, model),
|
|
93
|
+
[propertyKey]: options,
|
|
94
|
+
}, model);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -9,6 +9,7 @@ const { snakeCase } = require('../../utils/string');
|
|
|
9
9
|
* @param {string} dataType
|
|
10
10
|
*/
|
|
11
11
|
function findJsType(DataTypes, type, dataType) {
|
|
12
|
+
if (type instanceof DataTypes.VIRTUAL) return '';
|
|
12
13
|
if (type instanceof DataTypes.BOOLEAN) return Boolean;
|
|
13
14
|
if (type instanceof DataTypes.JSON) return JSON;
|
|
14
15
|
if (type instanceof DataTypes.BINARY || type instanceof DataTypes.BLOB) {
|
|
@@ -65,6 +66,8 @@ function createType(DataTypes, params) {
|
|
|
65
66
|
switch (type.constructor.name) {
|
|
66
67
|
case 'DATE':
|
|
67
68
|
return new DataType(type.precision, type.timezone);
|
|
69
|
+
case 'DECIMAL':
|
|
70
|
+
return new DataType(type.precision, type.scale);
|
|
68
71
|
case 'TINYINT':
|
|
69
72
|
case 'SMALLINT':
|
|
70
73
|
case 'MEDIUMINT':
|
|
@@ -72,6 +75,8 @@ function createType(DataTypes, params) {
|
|
|
72
75
|
case 'BIGINT':
|
|
73
76
|
case 'BINARY':
|
|
74
77
|
case 'VARBINARY':
|
|
78
|
+
case 'CHAR':
|
|
79
|
+
case 'VARCHAR':
|
|
75
80
|
return new DataType(type.length);
|
|
76
81
|
default:
|
|
77
82
|
return new DataType();
|
|
@@ -99,17 +104,26 @@ class Attribute {
|
|
|
99
104
|
}
|
|
100
105
|
const type = createType(DataTypes, params);
|
|
101
106
|
const dataType = params.dataType || type.dataType;
|
|
107
|
+
let { defaultValue = null } = params;
|
|
108
|
+
try {
|
|
109
|
+
// normalize column defaults like `'0'` or `CURRENT_TIMESTAMP`
|
|
110
|
+
defaultValue = type.cast(type.uncast(defaultValue));
|
|
111
|
+
} catch {
|
|
112
|
+
defaultValue = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
Object.assign(this, {
|
|
103
116
|
name,
|
|
104
|
-
columnName,
|
|
105
117
|
primaryKey: false,
|
|
106
|
-
defaultValue: null,
|
|
107
118
|
allowNull: !params.primaryKey,
|
|
108
|
-
columnType: type.toSqlString().toLowerCase(),
|
|
109
119
|
...params,
|
|
120
|
+
columnName: params.columnName || columnName,
|
|
121
|
+
columnType: type.toSqlString().toLowerCase(),
|
|
110
122
|
type,
|
|
123
|
+
defaultValue,
|
|
111
124
|
dataType,
|
|
112
125
|
jsType: findJsType(DataTypes, type, dataType),
|
|
126
|
+
virtual: type.virtual,
|
|
113
127
|
});
|
|
114
128
|
}
|
|
115
129
|
|
|
@@ -19,9 +19,12 @@ module.exports = {
|
|
|
19
19
|
const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
|
|
20
20
|
|
|
21
21
|
const actions = Object.keys(attributes).map(name => {
|
|
22
|
-
const
|
|
22
|
+
const options = attributes[name];
|
|
23
|
+
// { [columnName]: { remove: true } }
|
|
24
|
+
if (options.remove) return `DROP COLUMN ${escapeId(name)}`;
|
|
25
|
+
const attribute = new Attribute(name, options);
|
|
23
26
|
return [
|
|
24
|
-
|
|
27
|
+
options.modify ? 'MODIFY COLUMN' : 'ADD COLUMN',
|
|
25
28
|
attribute.toSqlString(),
|
|
26
29
|
].join(' ');
|
|
27
30
|
});
|
|
@@ -146,7 +146,7 @@ function qualify(spell) {
|
|
|
146
146
|
const baseName = Model.tableAlias;
|
|
147
147
|
const clarify = node => {
|
|
148
148
|
if (node.type === 'id' && !node.qualifiers) {
|
|
149
|
-
if (Model.
|
|
149
|
+
if (Model.columnAttributes[node.value]) node.qualifiers = [baseName];
|
|
150
150
|
}
|
|
151
151
|
};
|
|
152
152
|
|
|
@@ -335,7 +335,7 @@ function formatDelete(spell) {
|
|
|
335
335
|
* @param {Spell} spell
|
|
336
336
|
*/
|
|
337
337
|
function formatInsert(spell) {
|
|
338
|
-
const { Model, sets,
|
|
338
|
+
const { Model, sets, columnAttributes: optAttrs, updateOnDuplicate } = spell;
|
|
339
339
|
const { shardingKey } = Model;
|
|
340
340
|
const { createdAt } = Model.timestamps;
|
|
341
341
|
const { escapeId } = Model.driver;
|
|
@@ -345,22 +345,22 @@ function formatInsert(spell) {
|
|
|
345
345
|
let values = [];
|
|
346
346
|
let placeholders = [];
|
|
347
347
|
if (Array.isArray(sets)) {
|
|
348
|
-
// merge records to get the big picture of involved
|
|
348
|
+
// merge records to get the big picture of involved columnAttributes
|
|
349
349
|
const involved = sets.reduce((result, entry) => {
|
|
350
350
|
return Object.assign(result, entry);
|
|
351
351
|
}, {});
|
|
352
|
-
const
|
|
352
|
+
const columnAttributes = [];
|
|
353
353
|
if (optAttrs) {
|
|
354
354
|
for (const name in optAttrs) {
|
|
355
|
-
if (involved.hasOwnProperty(name))
|
|
355
|
+
if (involved.hasOwnProperty(name)) columnAttributes.push(columnAttributes[name]);
|
|
356
356
|
}
|
|
357
357
|
} else {
|
|
358
358
|
for (const name in involved) {
|
|
359
|
-
|
|
359
|
+
columnAttributes.push(Model.columnAttributes[name]);
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
for (const entry of
|
|
363
|
+
for (const entry of columnAttributes) {
|
|
364
364
|
columns.push(entry.columnName);
|
|
365
365
|
if (updateOnDuplicate && createdAt && entry.name === createdAt
|
|
366
366
|
&& !(Array.isArray(updateOnDuplicate) && updateOnDuplicate.includes(createdAt))) continue;
|
|
@@ -371,11 +371,11 @@ function formatInsert(spell) {
|
|
|
371
371
|
if (shardingKey && entry[shardingKey] == null) {
|
|
372
372
|
throw new Error(`Sharding key ${Model.table}.${shardingKey} cannot be NULL.`);
|
|
373
373
|
}
|
|
374
|
-
for (const attribute of
|
|
374
|
+
for (const attribute of columnAttributes) {
|
|
375
375
|
const { name } = attribute;
|
|
376
376
|
values.push(entry[name]);
|
|
377
377
|
}
|
|
378
|
-
placeholders.push(`(${new Array(
|
|
378
|
+
placeholders.push(`(${new Array(columnAttributes.length).fill('?').join(',')})`);
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
} else {
|
|
@@ -488,7 +488,7 @@ function formatUpdate(spell) {
|
|
|
488
488
|
function formatUpdateOnDuplicate(spell, columns) {
|
|
489
489
|
const { updateOnDuplicate, uniqueKeys, Model } = spell;
|
|
490
490
|
if (!updateOnDuplicate) return '';
|
|
491
|
-
const {
|
|
491
|
+
const { columnAttributes, primaryColumn } = Model;
|
|
492
492
|
const { escapeId } = Model.driver;
|
|
493
493
|
const actualUniqueKeys = [];
|
|
494
494
|
|
|
@@ -499,9 +499,9 @@ function formatUpdateOnDuplicate(spell, columns) {
|
|
|
499
499
|
} else {
|
|
500
500
|
// conflict_target must be unique
|
|
501
501
|
// get all unique keys
|
|
502
|
-
if (
|
|
503
|
-
for (const key in
|
|
504
|
-
const att =
|
|
502
|
+
if (columnAttributes) {
|
|
503
|
+
for (const key in columnAttributes) {
|
|
504
|
+
const att = columnAttributes[key];
|
|
505
505
|
// use the first unique key
|
|
506
506
|
if (att.unique) {
|
|
507
507
|
actualUniqueKeys.push(escapeId(att.columnName));
|
|
@@ -515,9 +515,9 @@ function formatUpdateOnDuplicate(spell, columns) {
|
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
if (Array.isArray(updateOnDuplicate) && updateOnDuplicate.length) {
|
|
518
|
-
columns = updateOnDuplicate.map(column => (
|
|
518
|
+
columns = updateOnDuplicate.map(column => (columnAttributes[column] && columnAttributes[column].columnName )|| column);
|
|
519
519
|
} else if (!columns.length) {
|
|
520
|
-
columns = Object.values(
|
|
520
|
+
columns = Object.values(columnAttributes).map(({ columnName }) => columnName);
|
|
521
521
|
}
|
|
522
522
|
const updateKeys = columns.map((column) => `${escapeId(column)}=EXCLUDED.${escapeId(column)}`);
|
|
523
523
|
|
|
@@ -15,8 +15,13 @@ class MysqlAttribute extends Attribute {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
toSqlString() {
|
|
18
|
-
const {
|
|
19
|
-
|
|
18
|
+
const {
|
|
19
|
+
columnName,
|
|
20
|
+
type, columnType,
|
|
21
|
+
allowNull, defaultValue,
|
|
22
|
+
primaryKey,
|
|
23
|
+
comment,
|
|
24
|
+
} = this;
|
|
20
25
|
|
|
21
26
|
const chunks = [
|
|
22
27
|
escapeId(columnName),
|
|
@@ -33,6 +38,10 @@ class MysqlAttribute extends Attribute {
|
|
|
33
38
|
chunks.push(`DEFAULT ${escape(defaultValue)}`);
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
if (typeof comment === 'string') {
|
|
42
|
+
chunks.push(`COMMENT ${escape(comment)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
return chunks.join(' ');
|
|
37
46
|
}
|
|
38
47
|
}
|
|
@@ -99,8 +99,8 @@ class MysqlDriver extends AbstractDriver {
|
|
|
99
99
|
if (!opts.connection) connection.release();
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
logger.tryLogQuery(sql, calculateDuration(start), opts);
|
|
103
102
|
const [ results, fields ] = result;
|
|
103
|
+
logger.tryLogQuery(sql, calculateDuration(start), opts, results);
|
|
104
104
|
if (fields) return { rows: results, fields };
|
|
105
105
|
return results;
|
|
106
106
|
}
|
|
@@ -38,7 +38,7 @@ module.exports = {
|
|
|
38
38
|
columns.push({
|
|
39
39
|
columnName: row.column_name,
|
|
40
40
|
columnType: row.column_type,
|
|
41
|
-
|
|
41
|
+
comment: row.column_comment,
|
|
42
42
|
defaultValue: row.column_default,
|
|
43
43
|
dataType: row.data_type,
|
|
44
44
|
allowNull: row.is_nullable === 'YES',
|
|
@@ -37,19 +37,19 @@ module.exports = {
|
|
|
37
37
|
const { updateOnDuplicate, Model } = spell;
|
|
38
38
|
if (!updateOnDuplicate) return null;
|
|
39
39
|
const { escapeId } = Model.driver;
|
|
40
|
-
const {
|
|
40
|
+
const { columnAttributes, primaryColumn } = Model;
|
|
41
41
|
|
|
42
42
|
if (Array.isArray(updateOnDuplicate) && updateOnDuplicate.length) {
|
|
43
|
-
columns = updateOnDuplicate.map(column => (
|
|
43
|
+
columns = updateOnDuplicate.map(column => (columnAttributes[column] && columnAttributes[column].columnName ) || column)
|
|
44
44
|
.filter(column => column !== primaryColumn);
|
|
45
45
|
} else if (!columns.length) {
|
|
46
|
-
columns = Object.values(
|
|
46
|
+
columns = Object.values(columnAttributes).map(attribute => attribute.columnName).filter(column => column !== primaryColumn);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const sets = [];
|
|
50
50
|
// Make sure the correct LAST_INSERT_ID is returned.
|
|
51
51
|
// - https://stackoverflow.com/questions/778534/mysql-on-duplicate-key-last-insert-id
|
|
52
|
-
// if insert
|
|
52
|
+
// if insert columnAttributes include primary column, `primaryKey = LAST_INSERT_ID(primaryKey)` is not need any more
|
|
53
53
|
if (!columns.includes(primaryColumn)) {
|
|
54
54
|
sets.push(`${escapeId(primaryColumn)} = LAST_INSERT_ID(${escapeId(primaryColumn)})`);
|
|
55
55
|
}
|
|
@@ -143,7 +143,7 @@ class PostgresDriver extends AbstractDriver {
|
|
|
143
143
|
if (!spell.connection) connection.release();
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
logger.tryLogQuery(formatted, calculateDuration(start), spell);
|
|
146
|
+
logger.tryLogQuery(formatted, calculateDuration(start), spell, result);
|
|
147
147
|
return result;
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -28,6 +28,10 @@ function formatAddColumn(driver, columnName, attribute) {
|
|
|
28
28
|
return `ADD COLUMN ${attribute.toSqlString()}`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function formatDropColumn(driver, columnName) {
|
|
32
|
+
return `DROP COLUMN ${driver.escapeId(columnName)}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
module.exports = {
|
|
32
36
|
...schema,
|
|
33
37
|
|
|
@@ -85,7 +89,9 @@ module.exports = {
|
|
|
85
89
|
const { escapeId } = this;
|
|
86
90
|
const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
|
|
87
91
|
const actions = Object.keys(changes).map(name => {
|
|
88
|
-
const
|
|
92
|
+
const options = changes[name];
|
|
93
|
+
if (options.remove) return formatDropColumn(this, name);
|
|
94
|
+
const attribute = new Attribute(name, options);
|
|
89
95
|
const { columnName } = attribute;;
|
|
90
96
|
return attribute.modify
|
|
91
97
|
? formatAlterColumns(this, columnName, attribute).join(', ')
|
|
@@ -108,7 +108,8 @@ module.exports = {
|
|
|
108
108
|
for (let i = 0; i < tables.length; i++) {
|
|
109
109
|
const table = tables[i];
|
|
110
110
|
const { rows } = results[i];
|
|
111
|
-
const columns = rows.map(
|
|
111
|
+
const columns = rows.map(row => {
|
|
112
|
+
const { name, type, notnull, dflt_value, pk } = row;
|
|
112
113
|
const columnType = type.toLowerCase();
|
|
113
114
|
const [, dataType, precision ] = columnType.match(rColumnType);
|
|
114
115
|
const primaryKey = pk === 1;
|
|
@@ -145,6 +146,8 @@ module.exports = {
|
|
|
145
146
|
const { escapeId } = this;
|
|
146
147
|
const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
|
|
147
148
|
const attributes = Object.keys(changes).map(name => {
|
|
149
|
+
const options = changes[name];
|
|
150
|
+
if (options.remove) return { columnName: name, remove: true };
|
|
148
151
|
return new Attribute(name, changes[name]);
|
|
149
152
|
});
|
|
150
153
|
|
|
@@ -157,7 +160,12 @@ module.exports = {
|
|
|
157
160
|
// SQLite can only add one column a time
|
|
158
161
|
// - https://www.sqlite.org/lang_altertable.html
|
|
159
162
|
for (const attribute of attributes) {
|
|
160
|
-
|
|
163
|
+
if (attribute.remove) {
|
|
164
|
+
const { columnName } = attribute;
|
|
165
|
+
await this.query(chunks.concat(`DROP COLUMN ${this.escapeId(columnName)}`).join(' '));
|
|
166
|
+
} else {
|
|
167
|
+
await this.query(chunks.concat(`ADD COLUMN ${attribute.toSqlString()}`).join(' '));
|
|
168
|
+
}
|
|
161
169
|
}
|
|
162
170
|
},
|
|
163
171
|
|
|
@@ -8,7 +8,7 @@ function renameSelectExpr(spell) {
|
|
|
8
8
|
|
|
9
9
|
for (const token of columns) {
|
|
10
10
|
if (token.type == 'id') {
|
|
11
|
-
if (!token.qualifiers && Model.
|
|
11
|
+
if (!token.qualifiers && Model.columnAttributes[token.value]) {
|
|
12
12
|
token.qualifiers = [Model.tableAlias];
|
|
13
13
|
}
|
|
14
14
|
whitelist.add(token.qualifiers[0]);
|
package/src/expr_formatter.js
CHANGED
|
@@ -203,6 +203,9 @@ function coerceLiteral(spell, ast) {
|
|
|
203
203
|
const attribute = model && model.attributes[firstArg.value];
|
|
204
204
|
|
|
205
205
|
if (!attribute) return;
|
|
206
|
+
if (attribute.virtual) {
|
|
207
|
+
throw new Error(`unable to use virtual attribute ${attribute.name} in model ${model.name}`);
|
|
208
|
+
}
|
|
206
209
|
|
|
207
210
|
for (const arg of args.slice(1)) {
|
|
208
211
|
if (arg.type === 'literal') {
|
package/src/setup_hooks.js
CHANGED
|
@@ -129,9 +129,28 @@ function addHook(target, hookName, func) {
|
|
|
129
129
|
if (useHooks && type === hookType.BEFORE) {
|
|
130
130
|
// this.change(key) or this.attributeChanged(key) should work at before update
|
|
131
131
|
if (method === 'update' && typeof arguments[0] === 'object' && !arguments[0] != null) {
|
|
132
|
-
|
|
132
|
+
const values = arguments[0];
|
|
133
|
+
const fields = arguments[1] && arguments[1].fields && arguments[1].fields.length? arguments[1].fields : [];
|
|
134
|
+
const originalRaw = {};
|
|
135
|
+
const changeRaw = {};
|
|
136
|
+
for (const name in values) {
|
|
137
|
+
if ((!fields.length || fields.includes(name)) && this.hasAttribute(name)) {
|
|
138
|
+
originalRaw[name] = this.attribute(name);
|
|
139
|
+
this[name] = values[name];
|
|
140
|
+
changeRaw[name] = this.attribute(name);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
await func.apply(this, args);
|
|
144
|
+
// revert instance after before hooks
|
|
145
|
+
Object.keys(originalRaw).forEach((key) => {
|
|
146
|
+
const current = this.attribute(key);
|
|
147
|
+
// raw[key] may changed in beforeUpdate hooks
|
|
148
|
+
if (current !== originalRaw[key] && current !== changeRaw[key]) return;
|
|
149
|
+
this.attribute(key, originalRaw[key]);
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
await func.apply(this, args);
|
|
133
153
|
}
|
|
134
|
-
await func.apply(this, args);
|
|
135
154
|
}
|
|
136
155
|
const res = await instanceOriginFunc.call(this, ...arguments);
|
|
137
156
|
if (useHooks && type === hookType.AFTER) {
|