query-interface.js
5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
'use strict';
const DataTypes = require('../../data-types');
const QueryTypes = require('../../query-types');
const _ = require('lodash');
/**
Returns an object that handles Postgres special needs to do certain queries.
@class QueryInterface
@static
@private
*/
/**
* Ensure enum and their values.
*
* @param {QueryInterface} qi
* @param {string} tableName Name of table to create
* @param {object} attributes Object representing a list of normalized table attributes
* @param {object} [options]
* @param {Model} [model]
*
* @returns {Promise}
* @private
*/
async function ensureEnums(qi, tableName, attributes, options, model) {
const keys = Object.keys(attributes);
const keyLen = keys.length;
let sql = '';
let promises = [];
let i = 0;
for (i = 0; i < keyLen; i++) {
const attribute = attributes[keys[i]];
const type = attribute.type;
if (
type instanceof DataTypes.ENUM ||
type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM //ARRAY sub type is ENUM
) {
sql = qi.QueryGenerator.pgListEnums(tableName, attribute.field || keys[i], options);
promises.push(qi.sequelize.query(
sql,
Object.assign({}, options, { plain: true, raw: true, type: QueryTypes.SELECT })
));
}
}
const results = await Promise.all(promises);
promises = [];
let enumIdx = 0;
// This little function allows us to re-use the same code that prepends or appends new value to enum array
const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => {
const valueOptions = _.clone(options);
valueOptions.before = null;
valueOptions.after = null;
switch (position) {
case 'after':
valueOptions.after = relativeValue;
break;
case 'before':
default:
valueOptions.before = relativeValue;
break;
}
promises.splice(spliceStart, 0, () => {
return qi.sequelize.query(qi.QueryGenerator.pgEnumAdd(
tableName, field, value, valueOptions
), valueOptions);
});
};
for (i = 0; i < keyLen; i++) {
const attribute = attributes[keys[i]];
const type = attribute.type;
const enumType = type.type || type;
const field = attribute.field || keys[i];
if (
type instanceof DataTypes.ENUM ||
type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM
) {
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
promises.push(() => {
return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), Object.assign({}, options, { raw: true }));
});
} else if (!!results[enumIdx] && !!model) {
const enumVals = qi.QueryGenerator.fromArray(results[enumIdx].enum_value);
const vals = enumType.values;
// Going through already existing values allows us to make queries that depend on those values
// We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values
// Then we append the rest of new values AFTER the latest already existing value
// E.g.: [1,2] -> [0,2,1] ==> [1,0,2]
// E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4]
// E.g.: [1] -> [0,2,3] ==> [1,0,2,3]
let lastOldEnumValue;
let rightestPosition = -1;
for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) {
const enumVal = enumVals[oldIndex];
const newIdx = vals.indexOf(enumVal);
lastOldEnumValue = enumVal;
if (newIdx === -1) {
continue;
}
const newValuesBefore = vals.slice(0, newIdx);
const promisesLength = promises.length;
// we go in reverse order so we could stop when we meet old value
for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) {
if (~enumVals.indexOf(newValuesBefore[reverseIdx])) {
break;
}
addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength);
}
// we detect the most 'right' position of old value in new enum array so we can append new values to it
if (newIdx > rightestPosition) {
rightestPosition = newIdx;
}
}
if (lastOldEnumValue && rightestPosition < vals.length - 1) {
const remainingEnumValues = vals.slice(rightestPosition + 1);
for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) {
addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after');
}
}
enumIdx++;
}
}
}
const result = await promises
.reduce(async (promise, asyncFunction) => await asyncFunction(await promise), Promise.resolve());
// If ENUM processed, then refresh OIDs
if (promises.length) {
await qi.sequelize.dialect.connectionManager._refreshDynamicOIDs();
}
return result;
}
exports.ensureEnums = ensureEnums;