leoric 2.3.2 → 2.4.0
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 +13 -0
- package/Readme.md +1 -1
- package/index.js +7 -0
- package/package.json +3 -1
- package/src/bone.js +7 -8
- package/src/drivers/abstract/index.js +192 -1
- package/src/drivers/abstract/spellbook.js +403 -412
- package/src/drivers/index.js +15 -4
- package/src/drivers/mysql/index.js +101 -10
- package/src/drivers/mysql/spellbook.js +13 -11
- package/src/drivers/postgres/index.js +103 -109
- package/src/drivers/postgres/spellbook.js +9 -9
- package/src/drivers/postgres/sqlstring.js +124 -0
- package/src/drivers/sqlite/index.js +124 -13
- package/src/drivers/sqlite/spellbook.js +6 -6
- package/src/drivers/sqlite/sqlstring.js +88 -0
- package/src/hint.js +2 -1
- package/src/realm.js +7 -5
- package/src/spell.js +2 -4
- package/types/hint.d.ts +96 -0
- package/types/index.d.ts +246 -20
- package/src/drivers/abstract/schema.js +0 -143
- package/src/drivers/mysql/schema.js +0 -98
- package/src/drivers/postgres/schema.js +0 -125
- package/src/drivers/sqlite/schema.js +0 -211
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const SqlString = require('sqlstring');
|
|
4
|
-
|
|
5
|
-
const schema = require('../abstract/schema');
|
|
6
|
-
const Attribute = require('./attribute');
|
|
7
|
-
const { heresql } = require('../../utils/string');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* - https://www.postgresql.org/docs/9.1/sql-altertable.html
|
|
11
|
-
* @param {string} columnName
|
|
12
|
-
* @param {Object} attribute
|
|
13
|
-
*/
|
|
14
|
-
function formatAlterColumns(driver, columnName, attribute) {
|
|
15
|
-
const { allowNull, type, defaultValue } = attribute;
|
|
16
|
-
const sets = [
|
|
17
|
-
`TYPE ${type.toSqlString()}`,
|
|
18
|
-
allowNull ? 'DROP NOT NULL' : 'SET NOT NULL',
|
|
19
|
-
defaultValue == null
|
|
20
|
-
? 'DROP DEFAULT'
|
|
21
|
-
: `SET DEFAULT ${SqlString.escape(defaultValue)}`,
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
return sets.map(entry => `ALTER COLUMN ${driver.escapeId(columnName)} ${entry}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function formatAddColumn(driver, columnName, attribute) {
|
|
28
|
-
return `ADD COLUMN ${attribute.toSqlString()}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function formatDropColumn(driver, columnName) {
|
|
32
|
-
return `DROP COLUMN ${driver.escapeId(columnName)}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
module.exports = {
|
|
36
|
-
...schema,
|
|
37
|
-
|
|
38
|
-
async querySchemaInfo(database, tables) {
|
|
39
|
-
tables = [].concat(tables);
|
|
40
|
-
const text = heresql(`
|
|
41
|
-
SELECT columns.*,
|
|
42
|
-
constraints.constraint_type
|
|
43
|
-
FROM information_schema.columns AS columns
|
|
44
|
-
LEFT JOIN information_schema.key_column_usage AS usage
|
|
45
|
-
ON columns.table_catalog = usage.table_catalog
|
|
46
|
-
AND columns.table_name = usage.table_name
|
|
47
|
-
AND columns.column_name = usage.column_name
|
|
48
|
-
LEFT JOIN information_schema.table_constraints AS constraints
|
|
49
|
-
ON usage.constraint_name = constraints.constraint_name
|
|
50
|
-
WHERE columns.table_catalog = $1 AND columns.table_name = ANY($2)
|
|
51
|
-
`);
|
|
52
|
-
|
|
53
|
-
const { pool } = this;
|
|
54
|
-
const { rows } = await pool.query(text, [database, tables]);
|
|
55
|
-
const schemaInfo = {};
|
|
56
|
-
|
|
57
|
-
for (const row of rows) {
|
|
58
|
-
const tableName = row.table_name;
|
|
59
|
-
const columns = schemaInfo[tableName] || (schemaInfo[tableName] = []);
|
|
60
|
-
let { data_type: dataType, character_maximum_length: length } = row;
|
|
61
|
-
|
|
62
|
-
if (dataType === 'character varying') dataType = 'varchar';
|
|
63
|
-
if (dataType === 'timestamp without time zone') dataType = 'timestamp';
|
|
64
|
-
|
|
65
|
-
let columnDefault = row.column_default;
|
|
66
|
-
if (/^NULL::/i.test(columnDefault)) columnDefault = null;
|
|
67
|
-
if (dataType === 'boolean') columnDefault = columnDefault === 'true';
|
|
68
|
-
|
|
69
|
-
const primaryKey = row.constraint_type === 'PRIMARY KEY';
|
|
70
|
-
const precision = row.datetime_precision;
|
|
71
|
-
|
|
72
|
-
columns.push({
|
|
73
|
-
columnName: row.column_name,
|
|
74
|
-
columnType: length > 0 ? `${dataType}(${length})` : dataType,
|
|
75
|
-
defaultValue: primaryKey ? null : columnDefault,
|
|
76
|
-
dataType,
|
|
77
|
-
allowNull: row.is_nullable !== 'NO',
|
|
78
|
-
// https://www.postgresql.org/docs/9.5/infoschema-table-constraints.html
|
|
79
|
-
primaryKey,
|
|
80
|
-
unique: row.constraint_type === 'UNIQUE',
|
|
81
|
-
datetimePrecision: precision === 6 ? null : precision,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return schemaInfo;
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
async alterTable(table, changes) {
|
|
89
|
-
const { escapeId } = this;
|
|
90
|
-
const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
|
|
91
|
-
const actions = Object.keys(changes).map(name => {
|
|
92
|
-
const options = changes[name];
|
|
93
|
-
if (options.remove) return formatDropColumn(this, name);
|
|
94
|
-
const attribute = new Attribute(name, options);
|
|
95
|
-
const { columnName } = attribute;;
|
|
96
|
-
return attribute.modify
|
|
97
|
-
? formatAlterColumns(this, columnName, attribute).join(', ')
|
|
98
|
-
: formatAddColumn(this, columnName, attribute);
|
|
99
|
-
});
|
|
100
|
-
chunks.push(actions.join(', '));
|
|
101
|
-
await this.query(chunks.join(' '));
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
async changeColumn(table, column, params) {
|
|
105
|
-
const { escapeId } = this;
|
|
106
|
-
const attribute = new Attribute(column, params);
|
|
107
|
-
const { columnName } = attribute;
|
|
108
|
-
const alterColumns = formatAlterColumns(this, columnName, attribute);
|
|
109
|
-
const sql = heresql(`ALTER TABLE ${escapeId(table)} ${alterColumns.join(', ')}`);
|
|
110
|
-
await this.query(sql);
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Truncate table
|
|
115
|
-
* @param {string} table the name of the table to truncate
|
|
116
|
-
* @param {Object} [opts={}] extra truncation options
|
|
117
|
-
* @param {object} [opts.restartIdentity] restart sequences owned by the table
|
|
118
|
-
*/
|
|
119
|
-
async truncateTable(table, opts = {}) {
|
|
120
|
-
const { escapeId } = this;
|
|
121
|
-
const chunks = [ `TRUNCATE TABLE ${escapeId(table)}` ];
|
|
122
|
-
if (opts.restartIdentity) chunks.push('RESTART IDENTITY');
|
|
123
|
-
await this.query(chunks.join(' '));
|
|
124
|
-
},
|
|
125
|
-
};
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const debug = require('debug')('leoric');
|
|
4
|
-
const SqlString = require('sqlstring');
|
|
5
|
-
|
|
6
|
-
const { parseExpr } = require('../../expr');
|
|
7
|
-
const { heresql } = require('../../utils/string');
|
|
8
|
-
const schema = require('../abstract/schema');
|
|
9
|
-
const Attribute = require('./attribute');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Schema altering commands other than RENAME COLUMN or ADD COLUMN
|
|
13
|
-
* - https://www.sqlite.org/lang_altertable.html
|
|
14
|
-
* @param {string} table
|
|
15
|
-
* @param {Object} attributes the changed attributes
|
|
16
|
-
*/
|
|
17
|
-
async function alterTableWithChangeColumn(driver, table, changes) {
|
|
18
|
-
const { escapeId } = driver;
|
|
19
|
-
const schemaInfo = await driver.querySchemaInfo(null, table);
|
|
20
|
-
const columns = schemaInfo[table];
|
|
21
|
-
|
|
22
|
-
const changeMap = changes.reduce((result, entry) => {
|
|
23
|
-
result[entry.columnName] = entry;
|
|
24
|
-
return result;
|
|
25
|
-
}, {});
|
|
26
|
-
|
|
27
|
-
const newAttributes = [];
|
|
28
|
-
for (const column of columns) {
|
|
29
|
-
const { columnName } = column;
|
|
30
|
-
const change = changeMap[columnName];
|
|
31
|
-
if (!change || !change.remove) {
|
|
32
|
-
newAttributes.push(Object.assign(column, change));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
for (const attribute of changes) {
|
|
37
|
-
if (!attribute.modify && !attribute.remove) {
|
|
38
|
-
newAttributes.push(attribute);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const newColumns = [];
|
|
43
|
-
for (const attribute of newAttributes) {
|
|
44
|
-
const { columnName, defaultValue } = attribute;
|
|
45
|
-
const change = changeMap[columnName];
|
|
46
|
-
if (!change || change.modify) {
|
|
47
|
-
newColumns.push(escapeId(columnName));
|
|
48
|
-
} else {
|
|
49
|
-
newColumns.push(SqlString.escape(defaultValue));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const connection = await driver.getConnection();
|
|
54
|
-
await connection.query('BEGIN');
|
|
55
|
-
try {
|
|
56
|
-
const newTable = `new_${table}`;
|
|
57
|
-
await driver.createTable(newTable, newAttributes, { connection });
|
|
58
|
-
await connection.query(heresql(`
|
|
59
|
-
INSERT INTO ${escapeId(newTable)}
|
|
60
|
-
SELECT ${newColumns.join(', ')}
|
|
61
|
-
FROM ${escapeId(table)}
|
|
62
|
-
`));
|
|
63
|
-
await connection.query(`DROP TABLE ${escapeId(table)}`);
|
|
64
|
-
await connection.query(heresql(`
|
|
65
|
-
ALTER TABLE ${escapeId(newTable)}
|
|
66
|
-
RENAME TO ${escapeId(table)}
|
|
67
|
-
`));
|
|
68
|
-
await connection.query('COMMIT');
|
|
69
|
-
} catch (err) {
|
|
70
|
-
await connection.query('ROLLBACK');
|
|
71
|
-
throw err;
|
|
72
|
-
} finally {
|
|
73
|
-
await connection.release();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// eslint-disable-next-line no-unused-vars
|
|
78
|
-
function parseDefaultValue(text, type) {
|
|
79
|
-
if (typeof text !== 'string') return text;
|
|
80
|
-
if (type === 'boolean') return text === 'true';
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const ast = parseExpr(text);
|
|
84
|
-
if (ast.type === 'literal') {
|
|
85
|
-
return ast.value;
|
|
86
|
-
}
|
|
87
|
-
} catch (err) {
|
|
88
|
-
debug('[parseDefaultValue] [%s] %s', text, err);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return text;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
module.exports = {
|
|
95
|
-
...schema,
|
|
96
|
-
|
|
97
|
-
async querySchemaInfo(database, tables) {
|
|
98
|
-
tables = [].concat(tables);
|
|
99
|
-
|
|
100
|
-
const queries = tables.map(table => {
|
|
101
|
-
return this.query(`PRAGMA table_info(${this.escapeId(table)})`);
|
|
102
|
-
});
|
|
103
|
-
const results = await Promise.all(queries);
|
|
104
|
-
const schemaInfo = {};
|
|
105
|
-
const rColumnType = /^(\w+)(?:\(([^)]+)\))?/i;
|
|
106
|
-
const rDateType = /(?:date|datetime|timestamp)/i;
|
|
107
|
-
|
|
108
|
-
for (let i = 0; i < tables.length; i++) {
|
|
109
|
-
const table = tables[i];
|
|
110
|
-
const { rows } = results[i];
|
|
111
|
-
const columns = rows.map(row => {
|
|
112
|
-
const { name, type, notnull, dflt_value, pk } = row;
|
|
113
|
-
const columnType = type.toLowerCase();
|
|
114
|
-
const [, dataType, precision ] = columnType.match(rColumnType);
|
|
115
|
-
const primaryKey = pk === 1;
|
|
116
|
-
|
|
117
|
-
const result = {
|
|
118
|
-
columnName: name,
|
|
119
|
-
columnType,
|
|
120
|
-
defaultValue: parseDefaultValue(dflt_value, type),
|
|
121
|
-
dataType: dataType,
|
|
122
|
-
allowNull: primaryKey ? false : notnull == 0,
|
|
123
|
-
primaryKey,
|
|
124
|
-
datetimePrecision: rDateType.test(dataType) ? parseInt(precision, 10) : null,
|
|
125
|
-
};
|
|
126
|
-
return result;
|
|
127
|
-
});
|
|
128
|
-
if (columns.length > 0) schemaInfo[table] = columns;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return schemaInfo;
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
async createTable(table, attributes, opts = {}) {
|
|
135
|
-
const { escapeId } = this;
|
|
136
|
-
const chunks = [ `CREATE TABLE ${escapeId(table)}` ];
|
|
137
|
-
const columns = Object.keys(attributes).map(name => {
|
|
138
|
-
const attribute = new Attribute(name, attributes[name]);
|
|
139
|
-
return attribute.toSqlString();
|
|
140
|
-
});
|
|
141
|
-
chunks.push(`(${columns.join(', ')})`);
|
|
142
|
-
await this.query(chunks.join(' '), [], opts);
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
async alterTable(table, changes) {
|
|
146
|
-
const { escapeId } = this;
|
|
147
|
-
const chunks = [ `ALTER TABLE ${escapeId(table)}` ];
|
|
148
|
-
const attributes = Object.keys(changes).map(name => {
|
|
149
|
-
const options = changes[name];
|
|
150
|
-
if (options.remove) return { columnName: name, remove: true };
|
|
151
|
-
return new Attribute(name, changes[name]);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// SQLite doesn't support altering column attributes with MODIFY COLUMN and adding a PRIMARY KEY column
|
|
155
|
-
if (attributes.some(entry => entry.modify || entry.primaryKey)) {
|
|
156
|
-
await alterTableWithChangeColumn(this, table, attributes);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// SQLite can only add one column a time
|
|
161
|
-
// - https://www.sqlite.org/lang_altertable.html
|
|
162
|
-
for (const attribute of attributes) {
|
|
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
|
-
}
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
async addColumn(table, name, params) {
|
|
173
|
-
const { escapeId } = this;
|
|
174
|
-
const attribute = new Attribute(name, params);
|
|
175
|
-
const sql = heresql(`
|
|
176
|
-
ALTER TABLE ${escapeId(table)}
|
|
177
|
-
ADD COLUMN ${attribute.toSqlString()}
|
|
178
|
-
`);
|
|
179
|
-
await this.query(sql);
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
async changeColumn(table, name, params) {
|
|
183
|
-
const attribute = new Attribute(name, params);
|
|
184
|
-
const schemaInfo = await this.querySchemaInfo(null, table);
|
|
185
|
-
const columns = schemaInfo[table];
|
|
186
|
-
|
|
187
|
-
for (const entry of columns) {
|
|
188
|
-
if (entry.columnName === attribute.columnName) {
|
|
189
|
-
Object.assign(entry, attribute, { modify: true });
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
await this.alterTable(table, columns);
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
async removeColumn(table, name) {
|
|
197
|
-
const attribute = new Attribute(name);
|
|
198
|
-
attribute.remove = true;
|
|
199
|
-
const changes = [ attribute ];
|
|
200
|
-
await alterTableWithChangeColumn(this, table, changes);
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* SQLite has only got implicit table truncation.
|
|
205
|
-
* - https://sqlite.org/lang_delete.html#the_truncate_optimization
|
|
206
|
-
*/
|
|
207
|
-
async truncateTable(table) {
|
|
208
|
-
const { escapeId } = this;
|
|
209
|
-
await this.query(`DELETE FROM ${escapeId(table)}`);
|
|
210
|
-
}
|
|
211
|
-
};
|