@nymphjs/driver-sqlite3 1.0.0-beta.6 → 1.0.0-beta.61
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/CHANGELOG.md +250 -0
- package/dist/SQLite3Driver.d.ts +54 -16
- package/dist/SQLite3Driver.js +437 -162
- package/dist/SQLite3Driver.js.map +1 -1
- package/dist/SQLite3Driver.test.js +43 -6
- package/dist/SQLite3Driver.test.js.map +1 -1
- package/dist/conf/d.d.ts +59 -1
- package/dist/conf/defaults.js +3 -1
- package/dist/conf/defaults.js.map +1 -1
- package/package.json +13 -13
- package/src/SQLite3Driver.test.ts +43 -8
- package/src/SQLite3Driver.ts +675 -391
- package/src/conf/d.ts +35 -2
- package/src/conf/defaults.ts +3 -1
- package/tsconfig.json +1 -1
- package/typedoc.json +4 -0
package/dist/SQLite3Driver.js
CHANGED
|
@@ -7,6 +7,16 @@ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
|
7
7
|
const nymph_1 = require("@nymphjs/nymph");
|
|
8
8
|
const guid_1 = require("@nymphjs/guid");
|
|
9
9
|
const conf_1 = require("./conf");
|
|
10
|
+
class InternalStore {
|
|
11
|
+
constructor(link) {
|
|
12
|
+
this.connected = false;
|
|
13
|
+
this.transactionsStarted = 0;
|
|
14
|
+
this.link = link;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The SQLite3 Nymph database driver.
|
|
19
|
+
*/
|
|
10
20
|
class SQLite3Driver extends nymph_1.NymphDriver {
|
|
11
21
|
static escape(input) {
|
|
12
22
|
if (input.indexOf('\x00') !== -1) {
|
|
@@ -14,95 +24,201 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
14
24
|
}
|
|
15
25
|
return '"' + input.replace(/"/g, () => '""') + '"';
|
|
16
26
|
}
|
|
17
|
-
constructor(config) {
|
|
27
|
+
constructor(config, store) {
|
|
18
28
|
super();
|
|
19
|
-
this.connected = false;
|
|
20
|
-
this.transactionsStarted = 0;
|
|
21
29
|
this.config = { ...conf_1.SQLite3DriverConfigDefaults, ...config };
|
|
30
|
+
if (this.config.filename === ':memory:') {
|
|
31
|
+
this.config.explicitWrite = true;
|
|
32
|
+
}
|
|
22
33
|
this.prefix = this.config.prefix;
|
|
23
|
-
|
|
34
|
+
if (store) {
|
|
35
|
+
this.store = store;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.connect();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* This is used internally by Nymph. Don't call it yourself.
|
|
43
|
+
*
|
|
44
|
+
* @returns A clone of this instance.
|
|
45
|
+
*/
|
|
46
|
+
clone() {
|
|
47
|
+
return new SQLite3Driver(this.config, this.store);
|
|
24
48
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Connect to the SQLite3 database.
|
|
51
|
+
*
|
|
52
|
+
* @returns Whether this instance is connected to a SQLite3 database.
|
|
53
|
+
*/
|
|
54
|
+
connect() {
|
|
55
|
+
if (this.store && this.store.connected) {
|
|
56
|
+
return Promise.resolve(true);
|
|
57
|
+
}
|
|
58
|
+
// Connecting
|
|
59
|
+
this._connect(false);
|
|
60
|
+
return Promise.resolve(this.store.connected);
|
|
61
|
+
}
|
|
62
|
+
_connect(write) {
|
|
63
|
+
const { filename, fileMustExist, timeout, explicitWrite, wal, verbose } = this.config;
|
|
64
|
+
try {
|
|
65
|
+
const setOptions = (link) => {
|
|
66
|
+
// Set database and connection options.
|
|
67
|
+
if (wal) {
|
|
68
|
+
link.pragma('journal_mode = WAL;');
|
|
69
|
+
}
|
|
70
|
+
link.pragma('encoding = "UTF-8";');
|
|
71
|
+
link.pragma('foreign_keys = 1;');
|
|
72
|
+
link.pragma('case_sensitive_like = 1;');
|
|
73
|
+
for (let pragma of this.config.pragmas) {
|
|
74
|
+
link.pragma(pragma);
|
|
75
|
+
}
|
|
76
|
+
// Create the preg_match and regexp functions.
|
|
77
|
+
link.function('regexp', { deterministic: true }, ((pattern, subject) => (this.posixRegexMatch(pattern, subject) ? 1 : 0)));
|
|
78
|
+
};
|
|
79
|
+
let link;
|
|
28
80
|
try {
|
|
29
|
-
|
|
30
|
-
readonly,
|
|
81
|
+
link = new better_sqlite3_1.default(filename, {
|
|
82
|
+
readonly: !explicitWrite && !write,
|
|
31
83
|
fileMustExist,
|
|
32
84
|
timeout,
|
|
33
85
|
verbose,
|
|
34
86
|
});
|
|
35
|
-
this.connected = true;
|
|
36
|
-
this.link.pragma('encoding = "UTF-8";');
|
|
37
|
-
this.link.pragma('foreign_keys = 1;');
|
|
38
|
-
this.link.pragma('case_sensitive_like = 1;');
|
|
39
|
-
this.link.function('regexp', { deterministic: true }, (pattern, subject) => this.posixRegexMatch(pattern, subject) ? 1 : 0);
|
|
40
87
|
}
|
|
41
88
|
catch (e) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
89
|
+
if (e.code === 'SQLITE_CANTOPEN' &&
|
|
90
|
+
!explicitWrite &&
|
|
91
|
+
!write &&
|
|
92
|
+
!this.config.fileMustExist) {
|
|
93
|
+
// This happens when the file doesn't exist and we attempt to open it
|
|
94
|
+
// readonly.
|
|
95
|
+
// First open it in write mode.
|
|
96
|
+
const writeLink = new better_sqlite3_1.default(filename, {
|
|
97
|
+
readonly: false,
|
|
98
|
+
fileMustExist,
|
|
99
|
+
timeout,
|
|
100
|
+
verbose,
|
|
101
|
+
});
|
|
102
|
+
setOptions(writeLink);
|
|
103
|
+
writeLink.close();
|
|
104
|
+
// Now open in readonly.
|
|
105
|
+
link = new better_sqlite3_1.default(filename, {
|
|
106
|
+
readonly: true,
|
|
107
|
+
fileMustExist,
|
|
108
|
+
timeout,
|
|
109
|
+
verbose,
|
|
110
|
+
});
|
|
45
111
|
}
|
|
46
112
|
else {
|
|
47
|
-
throw
|
|
113
|
+
throw e;
|
|
48
114
|
}
|
|
49
115
|
}
|
|
116
|
+
if (!this.store) {
|
|
117
|
+
if (write) {
|
|
118
|
+
throw new Error('Tried to open in write without opening in read first.');
|
|
119
|
+
}
|
|
120
|
+
this.store = new InternalStore(link);
|
|
121
|
+
}
|
|
122
|
+
else if (write) {
|
|
123
|
+
this.store.linkWrite = link;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this.store.link = link;
|
|
127
|
+
}
|
|
128
|
+
this.store.connected = true;
|
|
129
|
+
setOptions(link);
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
if (this.store) {
|
|
133
|
+
this.store.connected = false;
|
|
134
|
+
}
|
|
135
|
+
if (filename === ':memory:') {
|
|
136
|
+
throw new nymph_1.NotConfiguredError("It seems the config hasn't been set up correctly. Could not connect: " +
|
|
137
|
+
e?.message);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
throw new nymph_1.UnableToConnectError('Could not connect: ' + e?.message);
|
|
141
|
+
}
|
|
50
142
|
}
|
|
51
|
-
return this.connected;
|
|
52
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Disconnect from the SQLite3 database.
|
|
146
|
+
*
|
|
147
|
+
* @returns Whether this instance is connected to a SQLite3 database.
|
|
148
|
+
*/
|
|
53
149
|
async disconnect() {
|
|
54
|
-
if (this.connected) {
|
|
55
|
-
this.
|
|
56
|
-
|
|
57
|
-
|
|
150
|
+
if (this.store.connected) {
|
|
151
|
+
if (this.store.linkWrite && !this.config.explicitWrite) {
|
|
152
|
+
this.store.linkWrite.exec('PRAGMA optimize;');
|
|
153
|
+
this.store.linkWrite.close();
|
|
154
|
+
this.store.linkWrite = undefined;
|
|
155
|
+
}
|
|
156
|
+
if (this.config.explicitWrite) {
|
|
157
|
+
this.store.link.exec('PRAGMA optimize;');
|
|
158
|
+
}
|
|
159
|
+
this.store.link.close();
|
|
160
|
+
this.store.transactionsStarted = 0;
|
|
161
|
+
this.store.connected = false;
|
|
58
162
|
}
|
|
59
|
-
return this.connected;
|
|
163
|
+
return this.store.connected;
|
|
60
164
|
}
|
|
61
165
|
async inTransaction() {
|
|
62
|
-
return this.transactionsStarted > 0;
|
|
166
|
+
return this.store.transactionsStarted > 0;
|
|
63
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Check connection status.
|
|
170
|
+
*
|
|
171
|
+
* @returns Whether this instance is connected to a SQLite3 database.
|
|
172
|
+
*/
|
|
64
173
|
isConnected() {
|
|
65
|
-
return this.connected;
|
|
66
|
-
}
|
|
67
|
-
checkReadOnlyMode() {
|
|
68
|
-
if (this.config.readonly) {
|
|
69
|
-
throw new nymph_1.InvalidParametersError('Attempt to write to SQLite3 DB in read only mode.');
|
|
70
|
-
}
|
|
174
|
+
return this.store.connected;
|
|
71
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Create entity tables in the database.
|
|
178
|
+
*
|
|
179
|
+
* @param etype The entity type to create a table for. If this is blank, the default tables are created.
|
|
180
|
+
*/
|
|
72
181
|
createTables(etype = null) {
|
|
73
|
-
this.checkReadOnlyMode();
|
|
74
182
|
this.startTransaction('nymph-tablecreation');
|
|
75
183
|
try {
|
|
76
184
|
if (etype != null) {
|
|
185
|
+
// Create the entity table.
|
|
77
186
|
this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid" CHARACTER(24) PRIMARY KEY, "tags" TEXT, "cdate" REAL NOT NULL, "mdate" REAL NOT NULL);`);
|
|
78
187
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("cdate");`);
|
|
79
188
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("mdate");`);
|
|
80
189
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("tags");`);
|
|
190
|
+
// Create the data table.
|
|
81
191
|
this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "value" TEXT NOT NULL, PRIMARY KEY("guid", "name"));`);
|
|
82
192
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid");`);
|
|
83
193
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name");`);
|
|
84
194
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_value`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("value");`);
|
|
85
195
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid__name_user`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid") WHERE "name" = \'user\';`);
|
|
86
196
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid__name_group`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid") WHERE "name" = \'group\';`);
|
|
197
|
+
// Create the comparisons table.
|
|
87
198
|
this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "truthy" INTEGER, "string" TEXT, "number" REAL, PRIMARY KEY("guid", "name"));`);
|
|
88
199
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid");`);
|
|
89
200
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("name");`);
|
|
90
201
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_name__truthy`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("name") WHERE "truthy" = 1;`);
|
|
202
|
+
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_string`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("string");`);
|
|
203
|
+
// Create the references table.
|
|
91
204
|
this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "reference" CHARACTER(24) NOT NULL, PRIMARY KEY("guid", "name", "reference"));`);
|
|
92
205
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid");`);
|
|
93
206
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("name");`);
|
|
94
207
|
this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_reference`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("reference");`);
|
|
208
|
+
// Create the unique strings table.
|
|
209
|
+
this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "unique" TEXT NOT NULL UNIQUE, PRIMARY KEY("guid", "unique"));`);
|
|
95
210
|
}
|
|
96
211
|
else {
|
|
212
|
+
// Create the UID table.
|
|
97
213
|
this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name" TEXT PRIMARY KEY NOT NULL, "cur_uid" INTEGER NOT NULL);`);
|
|
98
214
|
}
|
|
99
|
-
this.commit('nymph-tablecreation');
|
|
100
|
-
return true;
|
|
101
215
|
}
|
|
102
216
|
catch (e) {
|
|
103
217
|
this.rollback('nymph-tablecreation');
|
|
104
218
|
throw e;
|
|
105
219
|
}
|
|
220
|
+
this.commit('nymph-tablecreation');
|
|
221
|
+
return true;
|
|
106
222
|
}
|
|
107
223
|
query(runQuery, query, etypes = []) {
|
|
108
224
|
try {
|
|
@@ -124,29 +240,42 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
124
240
|
throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
|
|
125
241
|
}
|
|
126
242
|
}
|
|
243
|
+
else if (errorCode === 'SQLITE_CONSTRAINT_UNIQUE' &&
|
|
244
|
+
errorMsg.match(/^UNIQUE constraint failed: /)) {
|
|
245
|
+
throw new nymph_1.EntityUniqueConstraintError(`Unique constraint violation.`);
|
|
246
|
+
}
|
|
127
247
|
else {
|
|
128
248
|
throw new nymph_1.QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query);
|
|
129
249
|
}
|
|
130
250
|
}
|
|
131
251
|
}
|
|
132
252
|
queryIter(query, { etypes = [], params = {}, } = {}) {
|
|
133
|
-
return this.query(() => this.
|
|
253
|
+
return this.query(() => (this.store.linkWrite || this.store.link)
|
|
254
|
+
.prepare(query)
|
|
255
|
+
.iterate(params), `${query} -- ${JSON.stringify(params)}`, etypes);
|
|
134
256
|
}
|
|
135
257
|
queryGet(query, { etypes = [], params = {}, } = {}) {
|
|
136
|
-
return this.query(() => this.link.prepare(query).get(params), `${query} -- ${JSON.stringify(params)}`, etypes);
|
|
258
|
+
return this.query(() => (this.store.linkWrite || this.store.link).prepare(query).get(params), `${query} -- ${JSON.stringify(params)}`, etypes);
|
|
137
259
|
}
|
|
138
260
|
queryRun(query, { etypes = [], params = {}, } = {}) {
|
|
139
|
-
return this.query(() => this.link.prepare(query).run(params), `${query} -- ${JSON.stringify(params)}`, etypes);
|
|
261
|
+
return this.query(() => (this.store.linkWrite || this.store.link).prepare(query).run(params), `${query} -- ${JSON.stringify(params)}`, etypes);
|
|
140
262
|
}
|
|
141
263
|
async commit(name) {
|
|
142
264
|
if (name == null || typeof name !== 'string' || name.length === 0) {
|
|
143
265
|
throw new nymph_1.InvalidParametersError('Transaction commit attempted without a name.');
|
|
144
266
|
}
|
|
145
|
-
if (this.transactionsStarted === 0) {
|
|
267
|
+
if (this.store.transactionsStarted === 0) {
|
|
146
268
|
return true;
|
|
147
269
|
}
|
|
148
270
|
this.queryRun(`RELEASE SAVEPOINT ${SQLite3Driver.escape(name)};`);
|
|
149
|
-
this.transactionsStarted--;
|
|
271
|
+
this.store.transactionsStarted--;
|
|
272
|
+
if (this.store.transactionsStarted === 0 &&
|
|
273
|
+
this.store.linkWrite &&
|
|
274
|
+
!this.config.explicitWrite) {
|
|
275
|
+
this.store.linkWrite.exec('PRAGMA optimize;');
|
|
276
|
+
this.store.linkWrite.close();
|
|
277
|
+
this.store.linkWrite = undefined;
|
|
278
|
+
}
|
|
150
279
|
return true;
|
|
151
280
|
}
|
|
152
281
|
async deleteEntityByID(guid, className) {
|
|
@@ -159,7 +288,6 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
159
288
|
EntityClass = className;
|
|
160
289
|
}
|
|
161
290
|
const etype = EntityClass.ETYPE;
|
|
162
|
-
this.checkReadOnlyMode();
|
|
163
291
|
await this.startTransaction('nymph-delete');
|
|
164
292
|
try {
|
|
165
293
|
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=@guid;`, {
|
|
@@ -186,27 +314,36 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
186
314
|
guid,
|
|
187
315
|
},
|
|
188
316
|
});
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
317
|
+
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
|
|
318
|
+
etypes: [etype],
|
|
319
|
+
params: {
|
|
320
|
+
guid,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
194
323
|
}
|
|
195
324
|
catch (e) {
|
|
325
|
+
this.nymph.config.debugError('sqlite3', `Delete entity error: "${e}"`);
|
|
196
326
|
await this.rollback('nymph-delete');
|
|
197
327
|
throw e;
|
|
198
328
|
}
|
|
329
|
+
await this.commit('nymph-delete');
|
|
330
|
+
// Remove any cached versions of this entity.
|
|
331
|
+
if (this.nymph.config.cache) {
|
|
332
|
+
this.cleanCache(guid);
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
199
335
|
}
|
|
200
336
|
async deleteUID(name) {
|
|
201
337
|
if (!name) {
|
|
202
338
|
throw new nymph_1.InvalidParametersError('Name not given for UID');
|
|
203
339
|
}
|
|
204
|
-
this.
|
|
340
|
+
await this.startTransaction('nymph-delete-uid');
|
|
205
341
|
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
|
|
206
342
|
params: {
|
|
207
343
|
name,
|
|
208
344
|
},
|
|
209
345
|
});
|
|
346
|
+
await this.commit('nymph-delete-uid');
|
|
210
347
|
return true;
|
|
211
348
|
}
|
|
212
349
|
async exportEntities(writeLine) {
|
|
@@ -220,6 +357,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
220
357
|
writeLine('# UIDs');
|
|
221
358
|
writeLine('#');
|
|
222
359
|
writeLine('');
|
|
360
|
+
// Export UIDs.
|
|
223
361
|
let uids = this.queryIter(`SELECT * FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} ORDER BY "name";`);
|
|
224
362
|
for (const uid of uids) {
|
|
225
363
|
writeLine(`<${uid.name}>[${uid.cur_uid}]`);
|
|
@@ -229,6 +367,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
229
367
|
writeLine('# Entities');
|
|
230
368
|
writeLine('#');
|
|
231
369
|
writeLine('');
|
|
370
|
+
// Get the etypes.
|
|
232
371
|
const tables = this.queryIter("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;");
|
|
233
372
|
const etypes = [];
|
|
234
373
|
for (const table of tables) {
|
|
@@ -237,6 +376,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
237
376
|
}
|
|
238
377
|
}
|
|
239
378
|
for (const etype of etypes) {
|
|
379
|
+
// Export entities.
|
|
240
380
|
const dataIterator = this.queryIter(`SELECT e.*, d."name" AS "dname", d."value" AS "dvalue", c."string", c."number" FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} e LEFT JOIN ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} d USING ("guid") INNER JOIN ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} c USING ("guid", "name") ORDER BY e."guid";`)[Symbol.iterator]();
|
|
241
381
|
let datum = dataIterator.next();
|
|
242
382
|
while (!datum.done) {
|
|
@@ -248,6 +388,8 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
248
388
|
writeLine(`\tcdate=${JSON.stringify(cdate)}`);
|
|
249
389
|
writeLine(`\tmdate=${JSON.stringify(mdate)}`);
|
|
250
390
|
if (datum.value.dname != null) {
|
|
391
|
+
// This do will keep going and adding the data until the
|
|
392
|
+
// next entity is reached. datum will end on the next entity.
|
|
251
393
|
do {
|
|
252
394
|
const value = datum.value.dvalue === 'N'
|
|
253
395
|
? JSON.stringify(datum.value.number)
|
|
@@ -259,12 +401,23 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
259
401
|
} while (!datum.done && datum.value.guid === guid);
|
|
260
402
|
}
|
|
261
403
|
else {
|
|
404
|
+
// Make sure that datum is incremented :)
|
|
262
405
|
datum = dataIterator.next();
|
|
263
406
|
}
|
|
264
407
|
}
|
|
265
408
|
}
|
|
266
409
|
return;
|
|
267
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Generate the SQLite3 query.
|
|
413
|
+
* @param options The options array.
|
|
414
|
+
* @param formattedSelectors The formatted selector array.
|
|
415
|
+
* @param etype
|
|
416
|
+
* @param count Used to track internal params.
|
|
417
|
+
* @param params Used to store internal params.
|
|
418
|
+
* @param subquery Whether only a subquery should be returned.
|
|
419
|
+
* @returns The SQL query.
|
|
420
|
+
*/
|
|
268
421
|
makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = []) {
|
|
269
422
|
if (typeof options.class?.alterOptions === 'function') {
|
|
270
423
|
options = options.class.alterOptions(options);
|
|
@@ -274,8 +427,9 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
274
427
|
const cTable = `c${tableSuffix}`;
|
|
275
428
|
const fTable = `f${tableSuffix}`;
|
|
276
429
|
const ieTable = `ie${tableSuffix}`;
|
|
430
|
+
const sTable = `s${tableSuffix}`;
|
|
277
431
|
const sort = options.sort ?? 'cdate';
|
|
278
|
-
const queryParts = this.iterateSelectorsForQuery(formattedSelectors, (key, value, typeIsOr, typeIsNot) => {
|
|
432
|
+
const queryParts = this.iterateSelectorsForQuery(formattedSelectors, ({ key, value, typeIsOr, typeIsNot }) => {
|
|
279
433
|
const clauseNot = key.startsWith('!');
|
|
280
434
|
let curQuery = '';
|
|
281
435
|
for (const curValue of value) {
|
|
@@ -1019,18 +1173,31 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1019
1173
|
return curQuery;
|
|
1020
1174
|
});
|
|
1021
1175
|
let sortBy;
|
|
1176
|
+
let sortByInner;
|
|
1177
|
+
let sortJoin = '';
|
|
1178
|
+
const order = options.reverse ? ' DESC' : '';
|
|
1022
1179
|
switch (sort) {
|
|
1023
1180
|
case 'mdate':
|
|
1024
|
-
sortBy =
|
|
1181
|
+
sortBy = `${eTable}."mdate"${order}`;
|
|
1182
|
+
sortByInner = `${ieTable}."mdate"${order}`;
|
|
1025
1183
|
break;
|
|
1026
1184
|
case 'cdate':
|
|
1185
|
+
sortBy = `${eTable}."cdate"${order}`;
|
|
1186
|
+
sortByInner = `${ieTable}."cdate"${order}`;
|
|
1187
|
+
break;
|
|
1027
1188
|
default:
|
|
1028
|
-
|
|
1189
|
+
const name = `param${++count.i}`;
|
|
1190
|
+
sortJoin = `LEFT JOIN (
|
|
1191
|
+
SELECT "guid", "string", "number"
|
|
1192
|
+
FROM ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)}
|
|
1193
|
+
WHERE "name"=@${name}
|
|
1194
|
+
ORDER BY "number"${order}, "string"${order}
|
|
1195
|
+
) ${sTable} USING ("guid")`;
|
|
1196
|
+
sortBy = `${sTable}."number"${order}, ${sTable}."string"${order}`;
|
|
1197
|
+
sortByInner = sortBy;
|
|
1198
|
+
params[name] = sort;
|
|
1029
1199
|
break;
|
|
1030
1200
|
}
|
|
1031
|
-
if (options.reverse) {
|
|
1032
|
-
sortBy += ' DESC';
|
|
1033
|
-
}
|
|
1034
1201
|
let query;
|
|
1035
1202
|
if (queryParts.length) {
|
|
1036
1203
|
if (subquery) {
|
|
@@ -1063,8 +1230,9 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1063
1230
|
else if (options.return === 'guid') {
|
|
1064
1231
|
query = `SELECT "guid"
|
|
1065
1232
|
FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
|
|
1233
|
+
${sortJoin}
|
|
1066
1234
|
WHERE (${whereClause})
|
|
1067
|
-
ORDER BY ${
|
|
1235
|
+
ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
|
|
1068
1236
|
}
|
|
1069
1237
|
else {
|
|
1070
1238
|
query = `SELECT
|
|
@@ -1079,13 +1247,15 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1079
1247
|
FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
|
|
1080
1248
|
LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
|
|
1081
1249
|
INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} ${cTable} USING ("guid", "name")
|
|
1250
|
+
${sortJoin}
|
|
1082
1251
|
INNER JOIN (
|
|
1083
1252
|
SELECT "guid"
|
|
1084
1253
|
FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
|
|
1254
|
+
${sortJoin}
|
|
1085
1255
|
WHERE (${whereClause})
|
|
1086
|
-
ORDER BY ${
|
|
1256
|
+
ORDER BY ${sortByInner}${limit}${offset}
|
|
1087
1257
|
) ${fTable} USING ("guid")
|
|
1088
|
-
ORDER BY ${
|
|
1258
|
+
ORDER BY ${sortBy}, ${eTable}."guid"`;
|
|
1089
1259
|
}
|
|
1090
1260
|
}
|
|
1091
1261
|
}
|
|
@@ -1117,7 +1287,8 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1117
1287
|
else if (options.return === 'guid') {
|
|
1118
1288
|
query = `SELECT "guid"
|
|
1119
1289
|
FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
|
|
1120
|
-
|
|
1290
|
+
${sortJoin}
|
|
1291
|
+
ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
|
|
1121
1292
|
}
|
|
1122
1293
|
else {
|
|
1123
1294
|
if (limit || offset) {
|
|
@@ -1133,12 +1304,14 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1133
1304
|
FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
|
|
1134
1305
|
LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
|
|
1135
1306
|
INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} c USING ("guid", "name")
|
|
1307
|
+
${sortJoin}
|
|
1136
1308
|
INNER JOIN (
|
|
1137
1309
|
SELECT "guid"
|
|
1138
1310
|
FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
|
|
1139
|
-
|
|
1311
|
+
${sortJoin}
|
|
1312
|
+
ORDER BY ${sortByInner}${limit}${offset}
|
|
1140
1313
|
) ${fTable} USING ("guid")
|
|
1141
|
-
ORDER BY ${
|
|
1314
|
+
ORDER BY ${sortBy}, ${eTable}."guid"`;
|
|
1142
1315
|
}
|
|
1143
1316
|
else {
|
|
1144
1317
|
query = `SELECT
|
|
@@ -1153,7 +1326,8 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1153
1326
|
FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
|
|
1154
1327
|
LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
|
|
1155
1328
|
INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} ${cTable} USING ("guid", "name")
|
|
1156
|
-
|
|
1329
|
+
${sortJoin}
|
|
1330
|
+
ORDER BY ${sortBy}, ${eTable}."guid"`;
|
|
1157
1331
|
}
|
|
1158
1332
|
}
|
|
1159
1333
|
}
|
|
@@ -1175,10 +1349,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1175
1349
|
};
|
|
1176
1350
|
}
|
|
1177
1351
|
async getEntities(options = {}, ...selectors) {
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
getEntitiesSync(options = {}, ...selectors) {
|
|
1181
|
-
const { result, process } = this.getEntitesRowLike(options, selectors, (options, formattedSelectors, etype) => this.performQuery(options, formattedSelectors, etype), () => {
|
|
1352
|
+
const { result, process } = this.getEntitiesRowLike(options, selectors, ({ options, selectors, etype }) => this.performQuery(options, selectors, etype), () => {
|
|
1182
1353
|
const next = result.next();
|
|
1183
1354
|
return next.done ? null : next.value;
|
|
1184
1355
|
}, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
|
|
@@ -1210,106 +1381,158 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1210
1381
|
});
|
|
1211
1382
|
return result?.cur_uid ?? null;
|
|
1212
1383
|
}
|
|
1213
|
-
async import(filename) {
|
|
1214
|
-
this.checkReadOnlyMode();
|
|
1384
|
+
async import(filename, transaction) {
|
|
1215
1385
|
try {
|
|
1216
1386
|
return this.importFromFile(filename, async (guid, tags, sdata, etype) => {
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
etypes: [etype],
|
|
1225
|
-
params: {
|
|
1226
|
-
guid,
|
|
1227
|
-
},
|
|
1228
|
-
});
|
|
1229
|
-
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=@guid;`, {
|
|
1230
|
-
etypes: [etype],
|
|
1231
|
-
params: {
|
|
1232
|
-
guid,
|
|
1233
|
-
},
|
|
1234
|
-
});
|
|
1235
|
-
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
|
|
1236
|
-
etypes: [etype],
|
|
1237
|
-
params: {
|
|
1238
|
-
guid,
|
|
1239
|
-
},
|
|
1240
|
-
});
|
|
1241
|
-
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`, {
|
|
1242
|
-
etypes: [etype],
|
|
1243
|
-
params: {
|
|
1244
|
-
guid,
|
|
1245
|
-
tags: ',' + tags.join(',') + ',',
|
|
1246
|
-
cdate: Number(JSON.parse(sdata.cdate)),
|
|
1247
|
-
mdate: Number(JSON.parse(sdata.mdate)),
|
|
1248
|
-
},
|
|
1249
|
-
});
|
|
1250
|
-
delete sdata.cdate;
|
|
1251
|
-
delete sdata.mdate;
|
|
1252
|
-
for (const name in sdata) {
|
|
1253
|
-
const value = sdata[name];
|
|
1254
|
-
const uvalue = JSON.parse(value);
|
|
1255
|
-
if (value === undefined) {
|
|
1256
|
-
continue;
|
|
1257
|
-
}
|
|
1258
|
-
const storageValue = typeof uvalue === 'number'
|
|
1259
|
-
? 'N'
|
|
1260
|
-
: typeof uvalue === 'string'
|
|
1261
|
-
? 'S'
|
|
1262
|
-
: value;
|
|
1263
|
-
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`, {
|
|
1387
|
+
try {
|
|
1388
|
+
await this.startTransaction(`nymph-import-entity-${guid}`);
|
|
1389
|
+
const cdate = Number(JSON.parse(sdata.cdate));
|
|
1390
|
+
delete sdata.cdate;
|
|
1391
|
+
const mdate = Number(JSON.parse(sdata.mdate));
|
|
1392
|
+
delete sdata.mdate;
|
|
1393
|
+
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=@guid;`, {
|
|
1264
1394
|
etypes: [etype],
|
|
1265
1395
|
params: {
|
|
1266
1396
|
guid,
|
|
1267
|
-
name,
|
|
1268
|
-
storageValue,
|
|
1269
1397
|
},
|
|
1270
1398
|
});
|
|
1271
|
-
this.queryRun(`
|
|
1399
|
+
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} WHERE "guid"=@guid;`, {
|
|
1400
|
+
etypes: [etype],
|
|
1401
|
+
params: {
|
|
1402
|
+
guid,
|
|
1403
|
+
},
|
|
1404
|
+
});
|
|
1405
|
+
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=@guid;`, {
|
|
1406
|
+
etypes: [etype],
|
|
1407
|
+
params: {
|
|
1408
|
+
guid,
|
|
1409
|
+
},
|
|
1410
|
+
});
|
|
1411
|
+
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
|
|
1412
|
+
etypes: [etype],
|
|
1413
|
+
params: {
|
|
1414
|
+
guid,
|
|
1415
|
+
},
|
|
1416
|
+
});
|
|
1417
|
+
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
|
|
1272
1418
|
etypes: [etype],
|
|
1273
1419
|
params: {
|
|
1274
1420
|
guid,
|
|
1275
|
-
name,
|
|
1276
|
-
truthy: uvalue ? 1 : 0,
|
|
1277
|
-
string: `${uvalue}`,
|
|
1278
|
-
number: Number(uvalue),
|
|
1279
1421
|
},
|
|
1280
1422
|
});
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1423
|
+
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`, {
|
|
1424
|
+
etypes: [etype],
|
|
1425
|
+
params: {
|
|
1426
|
+
guid,
|
|
1427
|
+
tags: ',' + tags.join(',') + ',',
|
|
1428
|
+
cdate,
|
|
1429
|
+
mdate,
|
|
1430
|
+
},
|
|
1431
|
+
});
|
|
1432
|
+
for (const name in sdata) {
|
|
1433
|
+
const value = sdata[name];
|
|
1434
|
+
const uvalue = JSON.parse(value);
|
|
1435
|
+
if (value === undefined) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
const storageValue = typeof uvalue === 'number'
|
|
1439
|
+
? 'N'
|
|
1440
|
+
: typeof uvalue === 'string'
|
|
1441
|
+
? 'S'
|
|
1442
|
+
: value;
|
|
1443
|
+
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`, {
|
|
1284
1444
|
etypes: [etype],
|
|
1285
1445
|
params: {
|
|
1286
1446
|
guid,
|
|
1287
1447
|
name,
|
|
1288
|
-
|
|
1448
|
+
storageValue,
|
|
1289
1449
|
},
|
|
1290
1450
|
});
|
|
1451
|
+
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid", "name", "truthy", "string", "number") VALUES (@guid, @name, @truthy, @string, @number);`, {
|
|
1452
|
+
etypes: [etype],
|
|
1453
|
+
params: {
|
|
1454
|
+
guid,
|
|
1455
|
+
name,
|
|
1456
|
+
truthy: uvalue ? 1 : 0,
|
|
1457
|
+
string: `${uvalue}`,
|
|
1458
|
+
number: Number(uvalue),
|
|
1459
|
+
},
|
|
1460
|
+
});
|
|
1461
|
+
const references = this.findReferences(value);
|
|
1462
|
+
for (const reference of references) {
|
|
1463
|
+
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid", "name", "reference") VALUES (@guid, @name, @reference);`, {
|
|
1464
|
+
etypes: [etype],
|
|
1465
|
+
params: {
|
|
1466
|
+
guid,
|
|
1467
|
+
name,
|
|
1468
|
+
reference,
|
|
1469
|
+
},
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1291
1472
|
}
|
|
1473
|
+
const uniques = await this.nymph
|
|
1474
|
+
.getEntityClassByEtype(etype)
|
|
1475
|
+
.getUniques({ guid, cdate, mdate, tags, data: {}, sdata });
|
|
1476
|
+
for (const unique of uniques) {
|
|
1477
|
+
try {
|
|
1478
|
+
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (@guid, @unique);`, {
|
|
1479
|
+
etypes: [etype],
|
|
1480
|
+
params: {
|
|
1481
|
+
guid,
|
|
1482
|
+
unique,
|
|
1483
|
+
},
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
catch (e) {
|
|
1487
|
+
if (e instanceof nymph_1.EntityUniqueConstraintError) {
|
|
1488
|
+
this.nymph.config.debugError('sqlite3', `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
|
|
1489
|
+
}
|
|
1490
|
+
throw e;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
await this.commit(`nymph-import-entity-${guid}`);
|
|
1494
|
+
}
|
|
1495
|
+
catch (e) {
|
|
1496
|
+
this.nymph.config.debugError('sqlite3', `Import entity error: "${e}"`);
|
|
1497
|
+
await this.rollback(`nymph-import-entity-${guid}`);
|
|
1498
|
+
throw e;
|
|
1292
1499
|
}
|
|
1293
1500
|
}, async (name, curUid) => {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1501
|
+
try {
|
|
1502
|
+
await this.startTransaction(`nymph-import-uid-${name}`);
|
|
1503
|
+
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
|
|
1504
|
+
params: {
|
|
1505
|
+
name,
|
|
1506
|
+
},
|
|
1507
|
+
});
|
|
1508
|
+
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
|
|
1509
|
+
params: {
|
|
1510
|
+
name,
|
|
1511
|
+
curUid,
|
|
1512
|
+
},
|
|
1513
|
+
});
|
|
1514
|
+
await this.commit(`nymph-import-uid-${name}`);
|
|
1515
|
+
}
|
|
1516
|
+
catch (e) {
|
|
1517
|
+
this.nymph.config.debugError('sqlite3', `Import UID error: "${e}"`);
|
|
1518
|
+
await this.rollback(`nymph-import-uid-${name}`);
|
|
1519
|
+
throw e;
|
|
1520
|
+
}
|
|
1305
1521
|
}, async () => {
|
|
1306
|
-
|
|
1522
|
+
if (transaction) {
|
|
1523
|
+
await this.startTransaction('nymph-import');
|
|
1524
|
+
}
|
|
1307
1525
|
}, async () => {
|
|
1308
|
-
|
|
1526
|
+
if (transaction) {
|
|
1527
|
+
await this.commit('nymph-import');
|
|
1528
|
+
}
|
|
1309
1529
|
});
|
|
1310
1530
|
}
|
|
1311
1531
|
catch (e) {
|
|
1312
|
-
|
|
1532
|
+
this.nymph.config.debugError('sqlite3', `Import error: "${e}"`);
|
|
1533
|
+
if (transaction) {
|
|
1534
|
+
await this.rollback('nymph-import');
|
|
1535
|
+
}
|
|
1313
1536
|
throw e;
|
|
1314
1537
|
}
|
|
1315
1538
|
}
|
|
@@ -1317,14 +1540,15 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1317
1540
|
if (name == null) {
|
|
1318
1541
|
throw new nymph_1.InvalidParametersError('Name not given for UID.');
|
|
1319
1542
|
}
|
|
1320
|
-
this.checkReadOnlyMode();
|
|
1321
1543
|
await this.startTransaction('nymph-newuid');
|
|
1544
|
+
let curUid = undefined;
|
|
1322
1545
|
try {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1546
|
+
curUid =
|
|
1547
|
+
this.queryGet(`SELECT "cur_uid" FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
|
|
1548
|
+
params: {
|
|
1549
|
+
name,
|
|
1550
|
+
},
|
|
1551
|
+
})?.cur_uid ?? null;
|
|
1328
1552
|
if (curUid == null) {
|
|
1329
1553
|
curUid = 1;
|
|
1330
1554
|
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
|
|
@@ -1343,41 +1567,49 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1343
1567
|
},
|
|
1344
1568
|
});
|
|
1345
1569
|
}
|
|
1346
|
-
await this.commit('nymph-newuid');
|
|
1347
|
-
return curUid;
|
|
1348
1570
|
}
|
|
1349
1571
|
catch (e) {
|
|
1572
|
+
this.nymph.config.debugError('sqlite3', `New UID error: "${e}"`);
|
|
1350
1573
|
await this.rollback('nymph-newuid');
|
|
1351
1574
|
throw e;
|
|
1352
1575
|
}
|
|
1576
|
+
await this.commit('nymph-newuid');
|
|
1577
|
+
return curUid;
|
|
1353
1578
|
}
|
|
1354
1579
|
async renameUID(oldName, newName) {
|
|
1355
1580
|
if (oldName == null || newName == null) {
|
|
1356
1581
|
throw new nymph_1.InvalidParametersError('Name not given for UID.');
|
|
1357
1582
|
}
|
|
1358
|
-
this.
|
|
1583
|
+
await this.startTransaction('nymph-rename-uid');
|
|
1359
1584
|
this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}uids`)} SET "name"=@newName WHERE "name"=@oldName;`, {
|
|
1360
1585
|
params: {
|
|
1361
1586
|
newName,
|
|
1362
1587
|
oldName,
|
|
1363
1588
|
},
|
|
1364
1589
|
});
|
|
1590
|
+
await this.commit('nymph-rename-uid');
|
|
1365
1591
|
return true;
|
|
1366
1592
|
}
|
|
1367
1593
|
async rollback(name) {
|
|
1368
1594
|
if (name == null || typeof name !== 'string' || name.length === 0) {
|
|
1369
1595
|
throw new nymph_1.InvalidParametersError('Transaction rollback attempted without a name.');
|
|
1370
1596
|
}
|
|
1371
|
-
if (this.transactionsStarted === 0) {
|
|
1597
|
+
if (this.store.transactionsStarted === 0) {
|
|
1372
1598
|
return true;
|
|
1373
1599
|
}
|
|
1374
1600
|
this.queryRun(`ROLLBACK TO SAVEPOINT ${SQLite3Driver.escape(name)};`);
|
|
1375
|
-
this.transactionsStarted--;
|
|
1601
|
+
this.store.transactionsStarted--;
|
|
1602
|
+
if (this.store.transactionsStarted === 0 &&
|
|
1603
|
+
this.store.linkWrite &&
|
|
1604
|
+
!this.config.explicitWrite) {
|
|
1605
|
+
this.store.linkWrite.exec('PRAGMA optimize;');
|
|
1606
|
+
this.store.linkWrite.close();
|
|
1607
|
+
this.store.linkWrite = undefined;
|
|
1608
|
+
}
|
|
1376
1609
|
return true;
|
|
1377
1610
|
}
|
|
1378
1611
|
async saveEntity(entity) {
|
|
1379
|
-
|
|
1380
|
-
const insertData = (guid, data, sdata, etype) => {
|
|
1612
|
+
const insertData = (guid, data, sdata, uniques, etype) => {
|
|
1381
1613
|
const runInsertQuery = (name, value, svalue) => {
|
|
1382
1614
|
if (value === undefined) {
|
|
1383
1615
|
return;
|
|
@@ -1417,6 +1649,23 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1417
1649
|
});
|
|
1418
1650
|
}
|
|
1419
1651
|
};
|
|
1652
|
+
for (const unique of uniques) {
|
|
1653
|
+
try {
|
|
1654
|
+
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (@guid, @unique);`, {
|
|
1655
|
+
etypes: [etype],
|
|
1656
|
+
params: {
|
|
1657
|
+
guid,
|
|
1658
|
+
unique,
|
|
1659
|
+
},
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
catch (e) {
|
|
1663
|
+
if (e instanceof nymph_1.EntityUniqueConstraintError) {
|
|
1664
|
+
this.nymph.config.debugError('sqlite3', `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
|
|
1665
|
+
}
|
|
1666
|
+
throw e;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1420
1669
|
for (const name in data) {
|
|
1421
1670
|
runInsertQuery(name, data[name], JSON.stringify(data[name]));
|
|
1422
1671
|
}
|
|
@@ -1424,8 +1673,13 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1424
1673
|
runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
|
|
1425
1674
|
}
|
|
1426
1675
|
};
|
|
1676
|
+
let inTransaction = false;
|
|
1427
1677
|
try {
|
|
1428
|
-
return this.saveEntityRowLike(entity, async (
|
|
1678
|
+
return this.saveEntityRowLike(entity, async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
|
|
1679
|
+
if (Object.keys(data).length === 0 &&
|
|
1680
|
+
Object.keys(sdata).length === 0) {
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1429
1683
|
this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @cdate);`, {
|
|
1430
1684
|
etypes: [etype],
|
|
1431
1685
|
params: {
|
|
@@ -1434,9 +1688,13 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1434
1688
|
cdate,
|
|
1435
1689
|
},
|
|
1436
1690
|
});
|
|
1437
|
-
insertData(guid, data, sdata, etype);
|
|
1691
|
+
insertData(guid, data, sdata, uniques, etype);
|
|
1438
1692
|
return true;
|
|
1439
|
-
}, async (entity, guid, tags, data, sdata, mdate, etype) => {
|
|
1693
|
+
}, async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
|
|
1694
|
+
if (Object.keys(data).length === 0 &&
|
|
1695
|
+
Object.keys(sdata).length === 0) {
|
|
1696
|
+
return false;
|
|
1697
|
+
}
|
|
1440
1698
|
const info = this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=@guid AND "mdate" <= @emdate;`, {
|
|
1441
1699
|
etypes: [etype],
|
|
1442
1700
|
params: {
|
|
@@ -1466,24 +1724,37 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1466
1724
|
guid,
|
|
1467
1725
|
},
|
|
1468
1726
|
});
|
|
1469
|
-
|
|
1727
|
+
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
|
|
1728
|
+
etypes: [etype],
|
|
1729
|
+
params: {
|
|
1730
|
+
guid,
|
|
1731
|
+
},
|
|
1732
|
+
});
|
|
1733
|
+
insertData(guid, data, sdata, uniques, etype);
|
|
1470
1734
|
success = true;
|
|
1471
1735
|
}
|
|
1472
1736
|
return success;
|
|
1473
1737
|
}, async () => {
|
|
1474
1738
|
await this.startTransaction('nymph-save');
|
|
1739
|
+
inTransaction = true;
|
|
1475
1740
|
}, async (success) => {
|
|
1476
|
-
if (
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1741
|
+
if (inTransaction) {
|
|
1742
|
+
inTransaction = false;
|
|
1743
|
+
if (success) {
|
|
1744
|
+
await this.commit('nymph-save');
|
|
1745
|
+
}
|
|
1746
|
+
else {
|
|
1747
|
+
await this.rollback('nymph-save');
|
|
1748
|
+
}
|
|
1481
1749
|
}
|
|
1482
1750
|
return success;
|
|
1483
1751
|
});
|
|
1484
1752
|
}
|
|
1485
1753
|
catch (e) {
|
|
1486
|
-
|
|
1754
|
+
this.nymph.config.debugError('sqlite3', `Save entity error: "${e}"`);
|
|
1755
|
+
if (inTransaction) {
|
|
1756
|
+
await this.rollback('nymph-save');
|
|
1757
|
+
}
|
|
1487
1758
|
throw e;
|
|
1488
1759
|
}
|
|
1489
1760
|
}
|
|
@@ -1491,7 +1762,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1491
1762
|
if (name == null) {
|
|
1492
1763
|
throw new nymph_1.InvalidParametersError('Name not given for UID.');
|
|
1493
1764
|
}
|
|
1494
|
-
this.
|
|
1765
|
+
await this.startTransaction('nymph-set-uid');
|
|
1495
1766
|
this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
|
|
1496
1767
|
params: {
|
|
1497
1768
|
name,
|
|
@@ -1503,14 +1774,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
|
|
|
1503
1774
|
curUid,
|
|
1504
1775
|
},
|
|
1505
1776
|
});
|
|
1777
|
+
await this.commit('nymph-set-uid');
|
|
1506
1778
|
return true;
|
|
1507
1779
|
}
|
|
1508
1780
|
async startTransaction(name) {
|
|
1509
1781
|
if (name == null || typeof name !== 'string' || name.length === 0) {
|
|
1510
1782
|
throw new nymph_1.InvalidParametersError('Transaction start attempted without a name.');
|
|
1511
1783
|
}
|
|
1784
|
+
if (!this.config.explicitWrite && !this.store.linkWrite) {
|
|
1785
|
+
this._connect(true);
|
|
1786
|
+
}
|
|
1512
1787
|
this.queryRun(`SAVEPOINT ${SQLite3Driver.escape(name)};`);
|
|
1513
|
-
this.transactionsStarted++;
|
|
1788
|
+
this.store.transactionsStarted++;
|
|
1514
1789
|
return this.nymph;
|
|
1515
1790
|
}
|
|
1516
1791
|
}
|