@snowtop/ent 0.1.0-alpha16 → 0.1.0-alpha160-test1
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/action/action.d.ts +25 -14
- package/action/action.js +22 -7
- package/action/executor.d.ts +16 -3
- package/action/executor.js +89 -28
- package/action/experimental_action.d.ts +25 -16
- package/action/experimental_action.js +34 -14
- package/action/index.d.ts +4 -1
- package/action/index.js +7 -1
- package/action/operations.d.ts +126 -0
- package/action/operations.js +686 -0
- package/action/orchestrator.d.ts +43 -12
- package/action/orchestrator.js +461 -101
- package/action/relative_value.d.ts +47 -0
- package/action/relative_value.js +125 -0
- package/action/transaction.d.ts +10 -0
- package/action/transaction.js +23 -0
- package/auth/auth.d.ts +1 -1
- package/core/base.d.ts +56 -23
- package/core/base.js +7 -1
- package/core/clause.d.ts +103 -39
- package/core/clause.js +430 -66
- package/core/config.d.ts +13 -3
- package/core/config.js +10 -1
- package/core/const.d.ts +3 -0
- package/core/const.js +6 -0
- package/core/context.d.ts +6 -3
- package/core/context.js +22 -3
- package/core/convert.d.ts +1 -1
- package/core/date.js +1 -5
- package/core/db.d.ts +12 -8
- package/core/db.js +21 -9
- package/core/ent.d.ts +99 -95
- package/core/ent.js +550 -602
- package/core/global_schema.d.ts +7 -0
- package/core/global_schema.js +51 -0
- package/core/loaders/assoc_count_loader.d.ts +5 -2
- package/core/loaders/assoc_count_loader.js +19 -3
- package/core/loaders/assoc_edge_loader.d.ts +2 -3
- package/core/loaders/assoc_edge_loader.js +23 -17
- package/core/loaders/index.d.ts +1 -2
- package/core/loaders/index.js +1 -5
- package/core/loaders/loader.d.ts +3 -3
- package/core/loaders/loader.js +4 -21
- package/core/loaders/object_loader.d.ts +30 -9
- package/core/loaders/object_loader.js +226 -79
- package/core/loaders/query_loader.d.ts +7 -13
- package/core/loaders/query_loader.js +60 -24
- package/core/loaders/raw_count_loader.d.ts +1 -0
- package/core/loaders/raw_count_loader.js +8 -3
- package/core/logger.d.ts +1 -1
- package/core/logger.js +1 -0
- package/core/privacy.d.ts +26 -16
- package/core/privacy.js +68 -51
- package/core/query/assoc_query.d.ts +3 -2
- package/core/query/assoc_query.js +10 -2
- package/core/query/custom_clause_query.d.ts +29 -0
- package/core/query/custom_clause_query.js +105 -0
- package/core/query/custom_query.d.ts +19 -2
- package/core/query/custom_query.js +111 -13
- package/core/query/index.d.ts +1 -0
- package/core/query/index.js +3 -1
- package/core/query/query.d.ts +18 -4
- package/core/query/query.js +135 -58
- package/core/query/shared_assoc_test.d.ts +2 -1
- package/core/query/shared_assoc_test.js +186 -55
- package/core/query/shared_test.d.ts +9 -2
- package/core/query/shared_test.js +529 -236
- package/core/query_impl.d.ts +8 -0
- package/core/query_impl.js +28 -0
- package/core/viewer.d.ts +2 -0
- package/core/viewer.js +3 -1
- package/graphql/graphql.d.ts +108 -22
- package/graphql/graphql.js +183 -137
- package/graphql/graphql_field_helpers.d.ts +9 -3
- package/graphql/graphql_field_helpers.js +22 -2
- package/graphql/index.d.ts +2 -2
- package/graphql/index.js +5 -5
- package/graphql/query/connection_type.d.ts +9 -9
- package/graphql/query/shared_assoc_test.js +1 -1
- package/graphql/query/shared_edge_connection.js +1 -19
- package/graphql/scalars/orderby_direction.d.ts +2 -0
- package/graphql/scalars/orderby_direction.js +15 -0
- package/imports/dataz/example1/_auth.js +128 -47
- package/imports/dataz/example1/_viewer.js +87 -39
- package/imports/index.d.ts +7 -2
- package/imports/index.js +20 -5
- package/index.d.ts +23 -5
- package/index.js +35 -10
- package/package.json +19 -19
- package/parse_schema/parse.d.ts +33 -9
- package/parse_schema/parse.js +182 -33
- package/schema/base_schema.d.ts +13 -3
- package/schema/base_schema.js +13 -0
- package/schema/field.d.ts +78 -21
- package/schema/field.js +232 -72
- package/schema/index.d.ts +2 -2
- package/schema/index.js +7 -2
- package/schema/json_field.d.ts +16 -4
- package/schema/json_field.js +32 -2
- package/schema/schema.d.ts +109 -20
- package/schema/schema.js +42 -53
- package/schema/struct_field.d.ts +15 -3
- package/schema/struct_field.js +117 -22
- package/schema/union_field.d.ts +1 -1
- package/scripts/custom_compiler.js +12 -8
- package/scripts/custom_graphql.js +171 -64
- package/scripts/migrate_v0.1.js +36 -0
- package/scripts/move_types.js +120 -0
- package/scripts/read_schema.js +22 -7
- package/testutils/action/complex_schemas.d.ts +69 -0
- package/testutils/action/complex_schemas.js +405 -0
- package/testutils/builder.d.ts +37 -41
- package/testutils/builder.js +66 -46
- package/testutils/db/fixture.d.ts +10 -0
- package/testutils/db/fixture.js +26 -0
- package/testutils/db/{test_db.d.ts → temp_db.d.ts} +32 -8
- package/testutils/db/{test_db.js → temp_db.js} +251 -48
- package/testutils/db/value.d.ts +7 -0
- package/testutils/db/value.js +251 -0
- package/testutils/db_mock.d.ts +16 -4
- package/testutils/db_mock.js +52 -9
- package/testutils/db_time_zone.d.ts +4 -0
- package/testutils/db_time_zone.js +41 -0
- package/testutils/ent-graphql-tests/index.d.ts +7 -1
- package/testutils/ent-graphql-tests/index.js +56 -26
- package/testutils/fake_comms.js +1 -1
- package/testutils/fake_data/const.d.ts +2 -1
- package/testutils/fake_data/const.js +3 -0
- package/testutils/fake_data/fake_contact.d.ts +7 -3
- package/testutils/fake_data/fake_contact.js +13 -7
- package/testutils/fake_data/fake_event.d.ts +4 -1
- package/testutils/fake_data/fake_event.js +7 -6
- package/testutils/fake_data/fake_tag.d.ts +36 -0
- package/testutils/fake_data/fake_tag.js +89 -0
- package/testutils/fake_data/fake_user.d.ts +8 -5
- package/testutils/fake_data/fake_user.js +31 -19
- package/testutils/fake_data/index.js +5 -1
- package/testutils/fake_data/internal.d.ts +2 -0
- package/testutils/fake_data/internal.js +7 -1
- package/testutils/fake_data/tag_query.d.ts +13 -0
- package/testutils/fake_data/tag_query.js +48 -0
- package/testutils/fake_data/test_helpers.d.ts +14 -6
- package/testutils/fake_data/test_helpers.js +31 -15
- package/testutils/fake_data/user_query.d.ts +16 -6
- package/testutils/fake_data/user_query.js +72 -23
- package/testutils/fake_log.js +1 -1
- package/testutils/parse_sql.d.ts +6 -0
- package/testutils/parse_sql.js +16 -2
- package/testutils/test_edge_global_schema.d.ts +15 -0
- package/testutils/test_edge_global_schema.js +62 -0
- package/testutils/write.d.ts +2 -2
- package/testutils/write.js +33 -7
- package/tsc/ast.d.ts +15 -3
- package/tsc/ast.js +114 -23
- package/tsc/compilerOptions.js +5 -1
- package/tsc/move_generated.d.ts +1 -0
- package/tsc/move_generated.js +164 -0
- package/tsc/transform.d.ts +22 -0
- package/tsc/transform.js +182 -0
- package/tsc/transform_action.d.ts +22 -0
- package/tsc/transform_action.js +183 -0
- package/tsc/transform_ent.d.ts +17 -0
- package/tsc/transform_ent.js +60 -0
- package/tsc/transform_schema.d.ts +27 -0
- package/{scripts → tsc}/transform_schema.js +146 -117
- package/core/loaders/index_loader.d.ts +0 -14
- package/core/loaders/index_loader.js +0 -27
- package/graphql/enums.d.ts +0 -3
- package/graphql/enums.js +0 -25
- package/scripts/move_generated.js +0 -141
- package/scripts/transform_actions.js +0 -266
- package/scripts/transform_code.d.ts +0 -1
- package/scripts/transform_code.js +0 -111
- package/scripts/transform_schema.d.ts +0 -1
- /package/scripts/{move_generated.d.ts → migrate_v0.1.d.ts} +0 -0
- /package/scripts/{transform_actions.d.ts → move_types.d.ts} +0 -0
package/core/ent.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -22,25 +26,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
27
|
};
|
|
24
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.
|
|
29
|
+
exports.applyPrivacyPolicyForRow = exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.getEdgeClauseAndFields = exports.loadEdges = exports.getDefaultLimit = exports.setDefaultLimit = exports.loadEdgeDatas = exports.loadEdgeData = exports.assocEdgeLoader = exports.AssocEdgeData = exports.getCursor = exports.AssocEdge = exports.deleteRowsSync = exports.deleteRows = exports.editRowSync = exports.editRow = exports.buildUpdateQuery = exports.createRowSync = exports.createRow = exports.buildInsertQuery = exports.buildGroupQuery = exports.buildQuery = exports.loadRows = exports.performRawQuery = exports.___setLogQueryErrorWithError = exports.loadRow = exports.loadRowX = exports.logQuery = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadCustomCount = exports.loadCustomData = exports.loadCustomEnts = exports.loadEntsFromClause = exports.loadEntsList = exports.loadEnts = exports.loadEntXFromClause = exports.loadEntFromClause = exports.loadEntXViaKey = exports.loadEntX = exports.loadEntViaKey = exports.loadEnt = exports.getEntKey = exports.getEntLoader = exports.rowIsError = void 0;
|
|
30
|
+
exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = void 0;
|
|
26
31
|
const db_1 = __importStar(require("./db"));
|
|
27
32
|
const privacy_1 = require("./privacy");
|
|
28
33
|
const clause = __importStar(require("./clause"));
|
|
29
|
-
const action_1 = require("../action");
|
|
30
34
|
const logger_1 = require("./logger");
|
|
31
35
|
const dataloader_1 = __importDefault(require("dataloader"));
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
const global_schema_1 = require("./global_schema");
|
|
37
|
+
const query_impl_1 = require("./query_impl");
|
|
38
|
+
const loader_1 = require("./loaders/loader");
|
|
39
|
+
class entCacheMap {
|
|
40
|
+
constructor(viewer, options) {
|
|
41
|
+
this.viewer = viewer;
|
|
35
42
|
this.options = options;
|
|
36
43
|
this.m = new Map();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
this.logEnabled = false;
|
|
45
|
+
this.logEnabled = (0, logger_1.logEnabled)("cache");
|
|
46
|
+
}
|
|
47
|
+
get(id) {
|
|
48
|
+
const ret = this.m.get(id);
|
|
49
|
+
if (this.logEnabled && ret) {
|
|
50
|
+
const key = getEntKey(this.viewer, id, this.options);
|
|
51
|
+
(0, logger_1.log)("cache", {
|
|
52
|
+
"ent-cache-hit": key,
|
|
44
53
|
});
|
|
45
54
|
}
|
|
46
55
|
return ret;
|
|
@@ -55,20 +64,23 @@ class cacheMap {
|
|
|
55
64
|
return this.m.clear();
|
|
56
65
|
}
|
|
57
66
|
}
|
|
58
|
-
function
|
|
67
|
+
function createAssocEdgeConfigLoader(options) {
|
|
59
68
|
const loaderOptions = {};
|
|
60
69
|
// if query logging is enabled, we should log what's happening with loader
|
|
61
70
|
if ((0, logger_1.logEnabled)("query")) {
|
|
62
|
-
loaderOptions.cacheMap = new
|
|
71
|
+
loaderOptions.cacheMap = new loader_1.CacheMap(options);
|
|
63
72
|
}
|
|
73
|
+
// something here brokwn with strict:true
|
|
64
74
|
return new dataloader_1.default(async (ids) => {
|
|
65
75
|
if (!ids.length) {
|
|
66
76
|
return [];
|
|
67
77
|
}
|
|
68
78
|
let col = options.key;
|
|
79
|
+
// defaults to uuid
|
|
80
|
+
let typ = options.keyType || "uuid";
|
|
69
81
|
const rowOptions = {
|
|
70
82
|
...options,
|
|
71
|
-
clause: clause.
|
|
83
|
+
clause: clause.DBTypeIn(col, ids, typ),
|
|
72
84
|
};
|
|
73
85
|
// TODO is there a better way of doing this?
|
|
74
86
|
// context not needed because we're creating a loader which has its own cache which is being used here
|
|
@@ -84,28 +96,165 @@ function createDataLoader(options) {
|
|
|
84
96
|
return result;
|
|
85
97
|
}, loaderOptions);
|
|
86
98
|
}
|
|
87
|
-
//
|
|
99
|
+
// used to wrap errors that would eventually be thrown in ents
|
|
100
|
+
// not an Error because DataLoader automatically rejects that
|
|
101
|
+
class ErrorWrapper {
|
|
102
|
+
constructor(error) {
|
|
103
|
+
this.error = error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// note if storing the result of this in something that checks instanceof Error e.g. DataLoader, we need to check instanceof at that callsite
|
|
107
|
+
function rowIsError(row) {
|
|
108
|
+
// jest does things that break instanceof checks
|
|
109
|
+
// so we need to check the name as well for native error SqliteError
|
|
110
|
+
return row instanceof Error || row?.constructor?.name === "SqliteError";
|
|
111
|
+
}
|
|
112
|
+
exports.rowIsError = rowIsError;
|
|
113
|
+
function createEntLoader(viewer, options, map) {
|
|
114
|
+
// share the cache across loaders even if we create a new instance
|
|
115
|
+
const loaderOptions = {};
|
|
116
|
+
loaderOptions.cacheMap = map;
|
|
117
|
+
return new dataloader_1.default(async (ids) => {
|
|
118
|
+
if (!ids.length) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
let result = [];
|
|
122
|
+
const tableName = options.loaderFactory.options?.tableName;
|
|
123
|
+
const loader = options.loaderFactory.createLoader(viewer.context);
|
|
124
|
+
const rows = await loader.loadMany(ids);
|
|
125
|
+
// this is a loader which should return the same order based on passed-in ids
|
|
126
|
+
// so let's depend on that...
|
|
127
|
+
for (let idx = 0; idx < rows.length; idx++) {
|
|
128
|
+
const row = rows[idx];
|
|
129
|
+
// db error
|
|
130
|
+
if (rowIsError(row)) {
|
|
131
|
+
if (row instanceof Error) {
|
|
132
|
+
result[idx] = row;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// @ts-ignore SqliteError
|
|
136
|
+
result[idx] = new Error(row.message);
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
else if (!row) {
|
|
141
|
+
if (tableName) {
|
|
142
|
+
result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]} in table ${tableName}`));
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]}`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
|
|
150
|
+
if (rowIsError(r)) {
|
|
151
|
+
result[idx] = new ErrorWrapper(r);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
result[idx] = r;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}, loaderOptions);
|
|
160
|
+
}
|
|
161
|
+
class EntLoader {
|
|
162
|
+
constructor(viewer, options) {
|
|
163
|
+
this.viewer = viewer;
|
|
164
|
+
this.options = options;
|
|
165
|
+
this.map = new entCacheMap(viewer, options);
|
|
166
|
+
this.loader = createEntLoader(this.viewer, this.options, this.map);
|
|
167
|
+
}
|
|
168
|
+
getMap() {
|
|
169
|
+
return this.map;
|
|
170
|
+
}
|
|
171
|
+
async load(id) {
|
|
172
|
+
return this.loader.load(id);
|
|
173
|
+
}
|
|
174
|
+
async loadMany(ids) {
|
|
175
|
+
return this.loader.loadMany(ids);
|
|
176
|
+
}
|
|
177
|
+
prime(id, ent) {
|
|
178
|
+
this.loader.prime(id, ent);
|
|
179
|
+
}
|
|
180
|
+
clear(id) {
|
|
181
|
+
this.loader.clear(id);
|
|
182
|
+
}
|
|
183
|
+
clearAll() {
|
|
184
|
+
this.loader.clearAll();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function getEntLoader(viewer, options) {
|
|
188
|
+
if (!viewer.context?.cache) {
|
|
189
|
+
return new EntLoader(viewer, options);
|
|
190
|
+
}
|
|
191
|
+
const name = `ent-loader:${viewer.instanceKey()}:${options.loaderFactory.name}`;
|
|
192
|
+
return viewer.context.cache.getLoaderWithLoadMany(name, () => new EntLoader(viewer, options));
|
|
193
|
+
}
|
|
194
|
+
exports.getEntLoader = getEntLoader;
|
|
195
|
+
function getEntKey(viewer, id, options) {
|
|
196
|
+
return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
|
|
197
|
+
}
|
|
198
|
+
exports.getEntKey = getEntKey;
|
|
88
199
|
async function loadEnt(viewer, id, options) {
|
|
89
|
-
|
|
90
|
-
|
|
200
|
+
if (typeof id !== "string" &&
|
|
201
|
+
typeof id !== "number" &&
|
|
202
|
+
typeof id !== "bigint") {
|
|
203
|
+
throw new Error(`invalid id ${id} passed to loadEnt`);
|
|
204
|
+
}
|
|
205
|
+
const r = await getEntLoader(viewer, options).load(id);
|
|
206
|
+
return r instanceof ErrorWrapper ? null : r;
|
|
91
207
|
}
|
|
92
208
|
exports.loadEnt = loadEnt;
|
|
209
|
+
async function applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options,
|
|
210
|
+
// can pass in loader when calling this for multi-id cases...
|
|
211
|
+
loader) {
|
|
212
|
+
if (!loader) {
|
|
213
|
+
loader = getEntLoader(viewer, options);
|
|
214
|
+
}
|
|
215
|
+
// TODO every row.id needs to be audited...
|
|
216
|
+
// https://github.com/lolopinto/ent/issues/1064
|
|
217
|
+
const id = row.id;
|
|
218
|
+
// we should check the ent loader cache to see if this is already there
|
|
219
|
+
// TODO hmm... we eventually need a custom data-loader for this too so that it's all done correctly if there's a complicated fetch deep down in graphql
|
|
220
|
+
const result = loader.getMap().get(id);
|
|
221
|
+
if (result !== undefined) {
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
|
|
225
|
+
if (rowIsError(r)) {
|
|
226
|
+
loader.prime(id, new ErrorWrapper(r));
|
|
227
|
+
return new ErrorWrapper(r);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
loader.prime(id, r);
|
|
231
|
+
return r;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
93
234
|
// this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
|
|
94
235
|
// used for load via email address etc
|
|
95
236
|
async function loadEntViaKey(viewer, key, options) {
|
|
96
237
|
const row = await options.loaderFactory
|
|
97
238
|
.createLoader(viewer.context)
|
|
98
239
|
.load(key);
|
|
99
|
-
|
|
240
|
+
if (!row) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
|
|
244
|
+
return r instanceof ErrorWrapper ? null : r;
|
|
100
245
|
}
|
|
101
246
|
exports.loadEntViaKey = loadEntViaKey;
|
|
102
247
|
async function loadEntX(viewer, id, options) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
throw new Error(
|
|
248
|
+
if (typeof id !== "string" &&
|
|
249
|
+
typeof id !== "number" &&
|
|
250
|
+
typeof id !== "bigint") {
|
|
251
|
+
throw new Error(`invalid id ${id} passed to loadEntX`);
|
|
107
252
|
}
|
|
108
|
-
|
|
253
|
+
const r = await getEntLoader(viewer, options).load(id);
|
|
254
|
+
if (r instanceof ErrorWrapper) {
|
|
255
|
+
throw r.error;
|
|
256
|
+
}
|
|
257
|
+
return r;
|
|
109
258
|
}
|
|
110
259
|
exports.loadEntX = loadEntX;
|
|
111
260
|
async function loadEntXViaKey(viewer, key, options) {
|
|
@@ -116,9 +265,16 @@ async function loadEntXViaKey(viewer, key, options) {
|
|
|
116
265
|
// todo make this better
|
|
117
266
|
throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
|
|
118
267
|
}
|
|
119
|
-
|
|
268
|
+
const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
|
|
269
|
+
if (r instanceof ErrorWrapper) {
|
|
270
|
+
throw r.error;
|
|
271
|
+
}
|
|
272
|
+
return r;
|
|
120
273
|
}
|
|
121
274
|
exports.loadEntXViaKey = loadEntXViaKey;
|
|
275
|
+
/**
|
|
276
|
+
* @deprecated use loadCustomEnts
|
|
277
|
+
*/
|
|
122
278
|
async function loadEntFromClause(viewer, options, clause) {
|
|
123
279
|
const rowOptions = {
|
|
124
280
|
...options,
|
|
@@ -126,12 +282,18 @@ async function loadEntFromClause(viewer, options, clause) {
|
|
|
126
282
|
context: viewer.context,
|
|
127
283
|
};
|
|
128
284
|
const row = await loadRow(rowOptions);
|
|
129
|
-
|
|
285
|
+
if (row === null) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return applyPrivacyPolicyForRow(viewer, options, row);
|
|
130
289
|
}
|
|
131
290
|
exports.loadEntFromClause = loadEntFromClause;
|
|
132
291
|
// same as loadEntFromClause
|
|
133
292
|
// only works for ents where primary key is "id"
|
|
134
293
|
// use loadEnt with a loaderFactory if different
|
|
294
|
+
/**
|
|
295
|
+
* @deprecated use loadCustomEnts
|
|
296
|
+
*/
|
|
135
297
|
async function loadEntXFromClause(viewer, options, clause) {
|
|
136
298
|
const rowOptions = {
|
|
137
299
|
...options,
|
|
@@ -146,37 +308,19 @@ async function loadEnts(viewer, options, ...ids) {
|
|
|
146
308
|
if (!ids.length) {
|
|
147
309
|
return new Map();
|
|
148
310
|
}
|
|
149
|
-
|
|
150
|
-
let rows = [];
|
|
151
|
-
// TODO loadMany everywhere
|
|
152
|
-
const l = options.loaderFactory.createLoader(viewer.context);
|
|
153
|
-
if (l.loadMany) {
|
|
154
|
-
loaded = true;
|
|
155
|
-
rows = await l.loadMany(ids);
|
|
156
|
-
}
|
|
157
|
-
// TODO rewrite all of this
|
|
311
|
+
// result
|
|
158
312
|
let m = new Map();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (row instanceof Error) {
|
|
166
|
-
throw row;
|
|
167
|
-
}
|
|
168
|
-
rows2.push(row);
|
|
313
|
+
const ret = await getEntLoader(viewer, options).loadMany(ids);
|
|
314
|
+
for (const r of ret) {
|
|
315
|
+
if (rowIsError(r)) {
|
|
316
|
+
throw r;
|
|
169
317
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
m
|
|
174
|
-
// this is always "id" if not using an ObjectLoaderFactory
|
|
175
|
-
clause.In("id", ...ids), options);
|
|
318
|
+
if (r instanceof ErrorWrapper) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
m.set(r.id, r);
|
|
176
322
|
}
|
|
177
323
|
return m;
|
|
178
|
-
// TODO do we want to change this to be a map not a list so that it's easy to check for existence?
|
|
179
|
-
// TODO eventually this should be doing a cache then db queyr and maybe depend on dataloader to get all the results at once
|
|
180
324
|
}
|
|
181
325
|
exports.loadEnts = loadEnts;
|
|
182
326
|
// calls loadEnts and returns the results sorted in the order they were passed in
|
|
@@ -195,6 +339,9 @@ async function loadEntsList(viewer, options, ...ids) {
|
|
|
195
339
|
exports.loadEntsList = loadEntsList;
|
|
196
340
|
// we return a map here so that any sorting for queries that exist
|
|
197
341
|
// can be done in O(N) time
|
|
342
|
+
/**
|
|
343
|
+
* @deperecated use loadCustomEnts
|
|
344
|
+
*/
|
|
198
345
|
async function loadEntsFromClause(viewer, clause, options) {
|
|
199
346
|
const rowOptions = {
|
|
200
347
|
...options,
|
|
@@ -202,65 +349,129 @@ async function loadEntsFromClause(viewer, clause, options) {
|
|
|
202
349
|
context: viewer.context,
|
|
203
350
|
};
|
|
204
351
|
const rows = await loadRows(rowOptions);
|
|
205
|
-
return
|
|
352
|
+
return applyPrivacyPolicyForRowsDeprecated(viewer, rows, options);
|
|
206
353
|
}
|
|
207
354
|
exports.loadEntsFromClause = loadEntsFromClause;
|
|
208
355
|
async function loadCustomEnts(viewer, options, query) {
|
|
209
356
|
const rows = await loadCustomData(options, query, viewer.context);
|
|
210
|
-
|
|
211
|
-
await Promise.all(rows.map(async (row, idx) => {
|
|
212
|
-
const ent = new options.ent(viewer, row);
|
|
213
|
-
let privacyEnt = await applyPrivacyPolicyForEnt(viewer, ent, row, options);
|
|
214
|
-
if (privacyEnt) {
|
|
215
|
-
result[idx] = privacyEnt;
|
|
216
|
-
}
|
|
217
|
-
}));
|
|
218
|
-
// filter ents that aren't visible because of privacy
|
|
219
|
-
return result.filter((r) => r !== undefined);
|
|
357
|
+
return applyPrivacyPolicyForRows(viewer, rows, options);
|
|
220
358
|
}
|
|
221
359
|
exports.loadCustomEnts = loadCustomEnts;
|
|
222
360
|
function isClause(opts) {
|
|
223
361
|
const cls = opts;
|
|
224
362
|
return cls.clause !== undefined && cls.values !== undefined;
|
|
225
363
|
}
|
|
226
|
-
function
|
|
364
|
+
function isParameterizedQuery(opts) {
|
|
227
365
|
return opts.query !== undefined;
|
|
228
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Note that if there's default read transformations (e.g. soft delete) and a clause is passed in
|
|
369
|
+
* either as Clause or QueryDataOptions without {disableTransformations: true}, the default transformation
|
|
370
|
+
* (e.g. soft delete) is applied.
|
|
371
|
+
*
|
|
372
|
+
* Passing a full SQL string or Paramterized SQL string doesn't apply it and the given string is sent to the
|
|
373
|
+
* database as written.
|
|
374
|
+
*
|
|
375
|
+
* e.g.
|
|
376
|
+
* Foo.loadCustom(opts, 'SELECT * FROM foo') // doesn't change the query
|
|
377
|
+
* Foo.loadCustom(opts, { query: 'SELECT * FROM foo WHERE id = ?', values: [1]}) // doesn't change the query
|
|
378
|
+
* Foo.loadCustom(opts, query.Eq('time', Date.now())) // changes the query
|
|
379
|
+
* Foo.loadCustom(opts, {
|
|
380
|
+
* clause: query.LessEq('time', Date.now()),
|
|
381
|
+
* limit: 100,
|
|
382
|
+
* orderby: 'time',
|
|
383
|
+
* }) // changes the query
|
|
384
|
+
* Foo.loadCustom(opts, {
|
|
385
|
+
* clause: query.LessEq('time', Date.now()),
|
|
386
|
+
* limit: 100,
|
|
387
|
+
* orderby: 'time',
|
|
388
|
+
* disableTransformations: false
|
|
389
|
+
* }) // doesn't change the query
|
|
390
|
+
*
|
|
391
|
+
* For queries that pass in a clause, we batch them with an underlying dataloader so that multiple queries with the same clause
|
|
392
|
+
* or parallel queries with the same clause are batched together.
|
|
393
|
+
*
|
|
394
|
+
* If a raw or parameterized query is passed in, we don't attempt to batch them together and they're executed as is.
|
|
395
|
+
* If you end up with a scenario where you may need to coalesce or batch (non-clause) queries here, you should use some kind of memoization here.
|
|
396
|
+
*/
|
|
229
397
|
async function loadCustomData(options, query, context) {
|
|
398
|
+
const rows = await loadCustomDataImpl(options, query, context);
|
|
399
|
+
// prime the data so that subsequent fetches of the row with this id are a cache hit.
|
|
400
|
+
if (options.prime) {
|
|
401
|
+
const loader = options.loaderFactory.createLoader(context);
|
|
402
|
+
if (isPrimableLoader(loader) && loader.primeAll !== undefined) {
|
|
403
|
+
for (const row of rows) {
|
|
404
|
+
loader.primeAll(row);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return rows;
|
|
409
|
+
}
|
|
410
|
+
exports.loadCustomData = loadCustomData;
|
|
411
|
+
// NOTE: if you use a raw query or paramterized query with this,
|
|
412
|
+
// you should use `SELECT count(*) as count...`
|
|
413
|
+
async function loadCustomCount(options, query, context) {
|
|
414
|
+
// if clause, we'll use the loader and strong typing/coalescing it provides
|
|
415
|
+
if (typeof query !== "string" && isClause(query)) {
|
|
416
|
+
return options.loaderFactory.createCountLoader(context).load(query);
|
|
417
|
+
}
|
|
418
|
+
const rows = await loadCustomDataImpl({
|
|
419
|
+
...options,
|
|
420
|
+
fields: ["count(1) as count"],
|
|
421
|
+
}, query, context);
|
|
422
|
+
if (rows.length) {
|
|
423
|
+
return parseInt(rows[0].count);
|
|
424
|
+
}
|
|
425
|
+
return 0;
|
|
426
|
+
}
|
|
427
|
+
exports.loadCustomCount = loadCustomCount;
|
|
428
|
+
function isPrimableLoader(loader) {
|
|
429
|
+
return loader != undefined;
|
|
430
|
+
}
|
|
431
|
+
async function loadCustomDataImpl(options, query, context) {
|
|
230
432
|
if (typeof query === "string") {
|
|
231
433
|
// no caching, perform raw query
|
|
232
|
-
return
|
|
434
|
+
return performRawQuery(query, [], []);
|
|
233
435
|
}
|
|
234
436
|
else if (isClause(query)) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
context: context,
|
|
240
|
-
});
|
|
437
|
+
const r = await options.loaderFactory
|
|
438
|
+
.createTypedLoader(context)
|
|
439
|
+
.load(query);
|
|
440
|
+
return r;
|
|
241
441
|
}
|
|
242
|
-
else if (
|
|
442
|
+
else if (isParameterizedQuery(query)) {
|
|
243
443
|
// no caching, perform raw query
|
|
244
|
-
return
|
|
444
|
+
return performRawQuery(query.query, query.values || [], query.logValues);
|
|
245
445
|
}
|
|
246
446
|
else {
|
|
247
447
|
// this will have rudimentary caching but nothing crazy
|
|
248
|
-
|
|
448
|
+
let cls = query.clause;
|
|
449
|
+
if (!query.disableTransformations) {
|
|
450
|
+
cls = clause.getCombinedClause(options.loaderFactory.options, query.clause);
|
|
451
|
+
}
|
|
452
|
+
return loadRows({
|
|
249
453
|
...query,
|
|
250
454
|
...options,
|
|
251
455
|
context: context,
|
|
456
|
+
// @ts-expect-error
|
|
457
|
+
clause: cls,
|
|
252
458
|
});
|
|
253
459
|
}
|
|
254
460
|
}
|
|
255
|
-
exports.loadCustomData = loadCustomData;
|
|
256
461
|
// Derived ents
|
|
462
|
+
// no ent caching
|
|
257
463
|
async function loadDerivedEnt(viewer, data, loader) {
|
|
258
464
|
const ent = new loader(viewer, data);
|
|
259
|
-
|
|
465
|
+
const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
|
|
260
466
|
ent: loader,
|
|
261
467
|
});
|
|
468
|
+
if (rowIsError(r)) {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
return r;
|
|
262
472
|
}
|
|
263
473
|
exports.loadDerivedEnt = loadDerivedEnt;
|
|
474
|
+
// won't have caching yet either
|
|
264
475
|
async function loadDerivedEntX(viewer, data, loader) {
|
|
265
476
|
const ent = new loader(viewer, data);
|
|
266
477
|
return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
|
|
@@ -269,19 +480,21 @@ exports.loadDerivedEntX = loadDerivedEntX;
|
|
|
269
480
|
// everything calls into this two so should be fine
|
|
270
481
|
// TODO is there a smarter way to not instantiate two objects here?
|
|
271
482
|
async function applyPrivacyPolicyForEnt(viewer, ent, data, fieldPrivacyOptions) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!visible) {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
483
|
+
const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
|
|
484
|
+
if (error === null) {
|
|
277
485
|
return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
|
|
278
486
|
}
|
|
279
|
-
return
|
|
487
|
+
return error;
|
|
280
488
|
}
|
|
281
489
|
async function applyPrivacyPolicyForEntX(viewer, ent, data, options) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
490
|
+
const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
|
|
491
|
+
if (rowIsError(r)) {
|
|
492
|
+
throw r;
|
|
493
|
+
}
|
|
494
|
+
if (r === null) {
|
|
495
|
+
throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
|
|
496
|
+
}
|
|
497
|
+
return r;
|
|
285
498
|
}
|
|
286
499
|
async function doFieldPrivacy(viewer, ent, data, options) {
|
|
287
500
|
if (!options.fieldPrivacy) {
|
|
@@ -289,16 +502,20 @@ async function doFieldPrivacy(viewer, ent, data, options) {
|
|
|
289
502
|
}
|
|
290
503
|
const promises = [];
|
|
291
504
|
let somethingChanged = false;
|
|
505
|
+
const clone = { ...data };
|
|
506
|
+
const origData = {
|
|
507
|
+
...data,
|
|
508
|
+
};
|
|
292
509
|
for (const [k, policy] of options.fieldPrivacy) {
|
|
510
|
+
const curr = clone[k];
|
|
511
|
+
if (curr === null || curr === undefined) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
293
514
|
promises.push((async () => {
|
|
294
515
|
// don't do anything if key is null or for some reason missing
|
|
295
|
-
const curr = data[k];
|
|
296
|
-
if (curr === null || curr === undefined) {
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
516
|
const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
|
|
300
517
|
if (!r) {
|
|
301
|
-
|
|
518
|
+
clone[k] = null;
|
|
302
519
|
somethingChanged = true;
|
|
303
520
|
}
|
|
304
521
|
})());
|
|
@@ -306,8 +523,11 @@ async function doFieldPrivacy(viewer, ent, data, options) {
|
|
|
306
523
|
await Promise.all(promises);
|
|
307
524
|
if (somethingChanged) {
|
|
308
525
|
// have to create new instance
|
|
309
|
-
|
|
526
|
+
const ent = new options.ent(viewer, clone);
|
|
527
|
+
ent.__setRawDBData(origData);
|
|
528
|
+
return ent;
|
|
310
529
|
}
|
|
530
|
+
ent.__setRawDBData(origData);
|
|
311
531
|
return ent;
|
|
312
532
|
}
|
|
313
533
|
function logQuery(query, logValues) {
|
|
@@ -317,6 +537,7 @@ function logQuery(query, logValues) {
|
|
|
317
537
|
});
|
|
318
538
|
(0, logger_1.logTrace)();
|
|
319
539
|
}
|
|
540
|
+
exports.logQuery = logQuery;
|
|
320
541
|
// TODO long term figure out if this API should be exposed
|
|
321
542
|
async function loadRowX(options) {
|
|
322
543
|
const result = await loadRow(options);
|
|
@@ -339,29 +560,26 @@ async function loadRow(options) {
|
|
|
339
560
|
}
|
|
340
561
|
const query = buildQuery(options);
|
|
341
562
|
logQuery(query, options.clause.logValues());
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (res.rowCount
|
|
346
|
-
|
|
347
|
-
(0, logger_1.log)("error", "got more than one row for query " + query);
|
|
348
|
-
}
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
// put the row in the cache...
|
|
352
|
-
if (cache) {
|
|
353
|
-
cache.primeCache(options, res.rows[0]);
|
|
563
|
+
const pool = db_1.default.getInstance().getPool();
|
|
564
|
+
const res = await pool.query(query, options.clause.values());
|
|
565
|
+
if (res.rowCount != 1) {
|
|
566
|
+
if (res.rowCount > 1) {
|
|
567
|
+
(0, logger_1.log)("error", "got more than one row for query " + query);
|
|
354
568
|
}
|
|
355
|
-
return res.rows[0];
|
|
356
|
-
}
|
|
357
|
-
catch (e) {
|
|
358
|
-
// an example of an error being suppressed
|
|
359
|
-
// another one. TODO https://github.com/lolopinto/ent/issues/862
|
|
360
|
-
(0, logger_1.log)("error", e);
|
|
361
569
|
return null;
|
|
362
570
|
}
|
|
571
|
+
// put the row in the cache...
|
|
572
|
+
if (cache) {
|
|
573
|
+
cache.primeCache(options, res.rows[0]);
|
|
574
|
+
}
|
|
575
|
+
return res.rows[0];
|
|
363
576
|
}
|
|
364
577
|
exports.loadRow = loadRow;
|
|
578
|
+
var _logQueryWithError = false;
|
|
579
|
+
function ___setLogQueryErrorWithError(val) {
|
|
580
|
+
_logQueryWithError = val || false;
|
|
581
|
+
}
|
|
582
|
+
exports.___setLogQueryErrorWithError = ___setLogQueryErrorWithError;
|
|
365
583
|
// this always goes to the db, no cache, nothing
|
|
366
584
|
async function performRawQuery(query, values, logValues) {
|
|
367
585
|
const pool = db_1.default.getInstance().getPool();
|
|
@@ -371,9 +589,11 @@ async function performRawQuery(query, values, logValues) {
|
|
|
371
589
|
return res.rows;
|
|
372
590
|
}
|
|
373
591
|
catch (e) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
592
|
+
if (_logQueryWithError) {
|
|
593
|
+
const msg = e.message;
|
|
594
|
+
throw new Error(`error \`${msg}\` running query: \`${query}\` with values: \`${logValues}\``);
|
|
595
|
+
}
|
|
596
|
+
throw e;
|
|
377
597
|
}
|
|
378
598
|
}
|
|
379
599
|
exports.performRawQuery = performRawQuery;
|
|
@@ -400,17 +620,18 @@ function buildQuery(options) {
|
|
|
400
620
|
const fields = options.fields.join(", ");
|
|
401
621
|
// always start at 1
|
|
402
622
|
const whereClause = options.clause.clause(1);
|
|
403
|
-
|
|
623
|
+
const parts = [];
|
|
624
|
+
parts.push(`SELECT ${fields} FROM ${options.tableName} WHERE ${whereClause}`);
|
|
404
625
|
if (options.groupby) {
|
|
405
|
-
|
|
626
|
+
parts.push(`GROUP BY ${options.groupby}`);
|
|
406
627
|
}
|
|
407
628
|
if (options.orderby) {
|
|
408
|
-
|
|
629
|
+
parts.push(`ORDER BY ${(0, query_impl_1.getOrderByPhrase)(options.orderby)}`);
|
|
409
630
|
}
|
|
410
631
|
if (options.limit) {
|
|
411
|
-
|
|
632
|
+
parts.push(`LIMIT ${options.limit}`);
|
|
412
633
|
}
|
|
413
|
-
return
|
|
634
|
+
return parts.join(" ");
|
|
414
635
|
}
|
|
415
636
|
exports.buildQuery = buildQuery;
|
|
416
637
|
// this is used for queries when we select multiple ids at once
|
|
@@ -422,7 +643,7 @@ function buildGroupQuery(options) {
|
|
|
422
643
|
}
|
|
423
644
|
let orderby = "";
|
|
424
645
|
if (options.orderby) {
|
|
425
|
-
orderby = `ORDER BY ${options.orderby}`;
|
|
646
|
+
orderby = `ORDER BY ${(0, query_impl_1.getOrderByPhrase)(options.orderby)}`;
|
|
426
647
|
}
|
|
427
648
|
// window functions work in sqlite!
|
|
428
649
|
// https://www.sqlite.org/windowfunctions.html
|
|
@@ -432,400 +653,32 @@ function buildGroupQuery(options) {
|
|
|
432
653
|
];
|
|
433
654
|
}
|
|
434
655
|
exports.buildGroupQuery = buildGroupQuery;
|
|
435
|
-
class EditNodeOperation {
|
|
436
|
-
constructor(options, existingEnt = null) {
|
|
437
|
-
this.options = options;
|
|
438
|
-
this.existingEnt = existingEnt;
|
|
439
|
-
this.placeholderID = options.placeholderID;
|
|
440
|
-
}
|
|
441
|
-
resolve(executor) {
|
|
442
|
-
if (!this.options.fieldsToResolve.length) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
let fields = this.options.fields;
|
|
446
|
-
this.options.fieldsToResolve.forEach((fieldName) => {
|
|
447
|
-
let value = fields[fieldName];
|
|
448
|
-
if (!value) {
|
|
449
|
-
throw new Error(`trying to resolve field ${fieldName} but not a valid field`);
|
|
450
|
-
}
|
|
451
|
-
let ent = executor.resolveValue(value.placeholderID);
|
|
452
|
-
if (!ent) {
|
|
453
|
-
throw new Error(`couldn't resolve field \`${fieldName}\` with value ${value.placeholderID}`);
|
|
454
|
-
}
|
|
455
|
-
fields[fieldName] = ent.id;
|
|
456
|
-
});
|
|
457
|
-
this.options.fields = fields;
|
|
458
|
-
}
|
|
459
|
-
hasData(data) {
|
|
460
|
-
for (const _k in data) {
|
|
461
|
-
return true;
|
|
462
|
-
}
|
|
463
|
-
return false;
|
|
464
|
-
}
|
|
465
|
-
async performWrite(queryer, context) {
|
|
466
|
-
let options = {
|
|
467
|
-
...this.options,
|
|
468
|
-
context,
|
|
469
|
-
};
|
|
470
|
-
if (this.existingEnt) {
|
|
471
|
-
if (this.hasData(options.fields)) {
|
|
472
|
-
// even this with returning * may not always work if transformed...
|
|
473
|
-
// we can have a transformed flag to see if it should be returned?
|
|
474
|
-
this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
this.row = this.existingEnt["data"];
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
else {
|
|
481
|
-
this.row = await createRow(queryer, options, "RETURNING *");
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
reloadRow(queryer, id, options) {
|
|
485
|
-
// TODO this isn't always an ObjectLoader. should throw or figure out a way to get query
|
|
486
|
-
// and run this on its own...
|
|
487
|
-
const loader = this.options.loadEntOptions.loaderFactory.createLoader(options.context);
|
|
488
|
-
const opts = loader.getOptions();
|
|
489
|
-
let cls = clause.Eq(options.key, id);
|
|
490
|
-
if (opts.clause) {
|
|
491
|
-
let optionClause;
|
|
492
|
-
if (typeof opts.clause === "function") {
|
|
493
|
-
optionClause = opts.clause();
|
|
494
|
-
}
|
|
495
|
-
else {
|
|
496
|
-
optionClause = opts.clause;
|
|
497
|
-
}
|
|
498
|
-
if (optionClause) {
|
|
499
|
-
cls = clause.And(optionClause, cls);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
const query = buildQuery({
|
|
503
|
-
fields: opts.fields.length ? opts.fields : ["*"],
|
|
504
|
-
tableName: options.tableName,
|
|
505
|
-
clause: cls,
|
|
506
|
-
});
|
|
507
|
-
// special case log here because we're not going through any of the normal
|
|
508
|
-
// methods here because those are async and this is sync
|
|
509
|
-
// this is the only place we're doing this so only handling here
|
|
510
|
-
logQuery(query, [id]);
|
|
511
|
-
const r = queryer.querySync(query, [id]);
|
|
512
|
-
if (r.rows.length === 1) {
|
|
513
|
-
this.row = r.rows[0];
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
performWriteSync(queryer, context) {
|
|
517
|
-
let options = {
|
|
518
|
-
...this.options,
|
|
519
|
-
context,
|
|
520
|
-
};
|
|
521
|
-
if (this.existingEnt) {
|
|
522
|
-
if (this.hasData(this.options.fields)) {
|
|
523
|
-
editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
|
|
524
|
-
this.reloadRow(queryer, this.existingEnt.id, options);
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
this.row = this.existingEnt["data"];
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
else {
|
|
531
|
-
createRowSync(queryer, options, "RETURNING *");
|
|
532
|
-
const id = options.fields[options.key];
|
|
533
|
-
this.reloadRow(queryer, id, options);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
returnedRow() {
|
|
537
|
-
return this.row;
|
|
538
|
-
}
|
|
539
|
-
createdEnt(viewer) {
|
|
540
|
-
if (!this.row) {
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
return new this.options.loadEntOptions.ent(viewer, this.row);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
exports.EditNodeOperation = EditNodeOperation;
|
|
547
|
-
class EdgeOperation {
|
|
548
|
-
constructor(edgeInput, options) {
|
|
549
|
-
this.edgeInput = edgeInput;
|
|
550
|
-
this.options = options;
|
|
551
|
-
}
|
|
552
|
-
async preFetch(queryer, context) {
|
|
553
|
-
let edgeData = await loadEdgeData(this.edgeInput.edgeType);
|
|
554
|
-
if (!edgeData) {
|
|
555
|
-
throw new Error(`error loading edge data for ${this.edgeInput.edgeType}`);
|
|
556
|
-
}
|
|
557
|
-
this.edgeData = edgeData;
|
|
558
|
-
}
|
|
559
|
-
async performWrite(queryer, context) {
|
|
560
|
-
if (!this.edgeData) {
|
|
561
|
-
throw new Error(`error fetching edgeData for type ${this.edgeInput.edgeType}`);
|
|
562
|
-
}
|
|
563
|
-
switch (this.options.operation) {
|
|
564
|
-
case action_1.WriteOperation.Delete:
|
|
565
|
-
return this.performDeleteWrite(queryer, this.edgeData, this.edgeInput, context);
|
|
566
|
-
case action_1.WriteOperation.Insert:
|
|
567
|
-
case action_1.WriteOperation.Edit:
|
|
568
|
-
return this.performInsertWrite(queryer, this.edgeData, this.edgeInput, context);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
performWriteSync(queryer, context) {
|
|
572
|
-
if (!this.edgeData) {
|
|
573
|
-
throw new Error(`error fetching edgeData for type ${this.edgeInput.edgeType}`);
|
|
574
|
-
}
|
|
575
|
-
switch (this.options.operation) {
|
|
576
|
-
case action_1.WriteOperation.Delete:
|
|
577
|
-
return this.performDeleteWriteSync(queryer, this.edgeData, this.edgeInput, context);
|
|
578
|
-
case action_1.WriteOperation.Insert:
|
|
579
|
-
case action_1.WriteOperation.Edit:
|
|
580
|
-
return this.performInsertWriteSync(queryer, this.edgeData, this.edgeInput, context);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
getDeleteRowParams(edgeData, edge, context) {
|
|
584
|
-
return {
|
|
585
|
-
options: {
|
|
586
|
-
tableName: edgeData.edgeTable,
|
|
587
|
-
context,
|
|
588
|
-
},
|
|
589
|
-
clause: clause.And(clause.Eq("id1", edge.id1), clause.Eq("id2", edge.id2), clause.Eq("edge_type", edge.edgeType)),
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
async performDeleteWrite(q, edgeData, edge, context) {
|
|
593
|
-
const params = this.getDeleteRowParams(edgeData, edge, context);
|
|
594
|
-
return deleteRows(q, params.options, params.clause);
|
|
595
|
-
}
|
|
596
|
-
performDeleteWriteSync(q, edgeData, edge, context) {
|
|
597
|
-
const params = this.getDeleteRowParams(edgeData, edge, context);
|
|
598
|
-
return deleteRowsSync(q, params.options, params.clause);
|
|
599
|
-
}
|
|
600
|
-
getInsertRowParams(edgeData, edge, context) {
|
|
601
|
-
const fields = {
|
|
602
|
-
id1: edge.id1,
|
|
603
|
-
id2: edge.id2,
|
|
604
|
-
id1_type: edge.id1Type,
|
|
605
|
-
id2_type: edge.id2Type,
|
|
606
|
-
edge_type: edge.edgeType,
|
|
607
|
-
data: edge.data || null,
|
|
608
|
-
};
|
|
609
|
-
if (edge.time) {
|
|
610
|
-
fields["time"] = edge.time.toISOString();
|
|
611
|
-
}
|
|
612
|
-
else {
|
|
613
|
-
// todo make this a schema field like what we do in generated base files...
|
|
614
|
-
// maybe when actions exist?
|
|
615
|
-
fields["time"] = new Date().toISOString();
|
|
616
|
-
}
|
|
617
|
-
return [
|
|
618
|
-
{
|
|
619
|
-
tableName: edgeData.edgeTable,
|
|
620
|
-
fields: fields,
|
|
621
|
-
fieldsToLog: fields,
|
|
622
|
-
context,
|
|
623
|
-
},
|
|
624
|
-
"ON CONFLICT(id1, edge_type, id2) DO UPDATE SET data = EXCLUDED.data",
|
|
625
|
-
];
|
|
626
|
-
}
|
|
627
|
-
async performInsertWrite(q, edgeData, edge, context) {
|
|
628
|
-
const [options, suffix] = this.getInsertRowParams(edgeData, edge, context);
|
|
629
|
-
await createRow(q, options, suffix);
|
|
630
|
-
}
|
|
631
|
-
performInsertWriteSync(q, edgeData, edge, context) {
|
|
632
|
-
const [options, suffix] = this.getInsertRowParams(edgeData, edge, context);
|
|
633
|
-
createRowSync(q, options, suffix);
|
|
634
|
-
}
|
|
635
|
-
resolveImpl(executor, placeholder, desc) {
|
|
636
|
-
let ent = executor.resolveValue(placeholder);
|
|
637
|
-
if (!ent) {
|
|
638
|
-
throw new Error(`could not resolve placeholder value ${placeholder} for ${desc} for edge ${this.edgeInput.edgeType}`);
|
|
639
|
-
}
|
|
640
|
-
if (ent.id === undefined) {
|
|
641
|
-
throw new Error(`id of resolved ent is not defined`);
|
|
642
|
-
}
|
|
643
|
-
return [ent.id, ent.nodeType];
|
|
644
|
-
}
|
|
645
|
-
resolve(executor) {
|
|
646
|
-
if (this.options.id1Placeholder) {
|
|
647
|
-
[this.edgeInput.id1, this.edgeInput.id1Type] = this.resolveImpl(executor, this.edgeInput.id1, "id1");
|
|
648
|
-
}
|
|
649
|
-
if (this.options.id2Placeholder) {
|
|
650
|
-
[this.edgeInput.id2, this.edgeInput.id2Type] = this.resolveImpl(executor, this.edgeInput.id2, "id2");
|
|
651
|
-
}
|
|
652
|
-
if (this.options.dataPlaceholder) {
|
|
653
|
-
if (!this.edgeInput.data) {
|
|
654
|
-
throw new Error(`data placeholder set but edgeInput data undefined`);
|
|
655
|
-
}
|
|
656
|
-
let [data, _] = this.resolveImpl(executor, this.edgeInput.data.toString(), "data");
|
|
657
|
-
this.edgeInput.data = data.toString();
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
symmetricEdge() {
|
|
661
|
-
return new EdgeOperation({
|
|
662
|
-
id1: this.edgeInput.id2,
|
|
663
|
-
id1Type: this.edgeInput.id2Type,
|
|
664
|
-
id2: this.edgeInput.id1,
|
|
665
|
-
id2Type: this.edgeInput.id1Type,
|
|
666
|
-
edgeType: this.edgeInput.edgeType,
|
|
667
|
-
time: this.edgeInput.time,
|
|
668
|
-
data: this.edgeInput.data,
|
|
669
|
-
}, {
|
|
670
|
-
operation: this.options.operation,
|
|
671
|
-
id1Placeholder: this.options.id2Placeholder,
|
|
672
|
-
id2Placeholder: this.options.id1Placeholder,
|
|
673
|
-
dataPlaceholder: this.options.dataPlaceholder,
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
inverseEdge(edgeData) {
|
|
677
|
-
return new EdgeOperation({
|
|
678
|
-
id1: this.edgeInput.id2,
|
|
679
|
-
id1Type: this.edgeInput.id2Type,
|
|
680
|
-
id2: this.edgeInput.id1,
|
|
681
|
-
id2Type: this.edgeInput.id1Type,
|
|
682
|
-
edgeType: edgeData.inverseEdgeType,
|
|
683
|
-
time: this.edgeInput.time,
|
|
684
|
-
data: this.edgeInput.data,
|
|
685
|
-
}, {
|
|
686
|
-
operation: this.options.operation,
|
|
687
|
-
id1Placeholder: this.options.id2Placeholder,
|
|
688
|
-
id2Placeholder: this.options.id1Placeholder,
|
|
689
|
-
dataPlaceholder: this.options.dataPlaceholder,
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
static resolveIDs(srcBuilder, // id1
|
|
693
|
-
destID) {
|
|
694
|
-
let destIDVal;
|
|
695
|
-
let destPlaceholder = false;
|
|
696
|
-
if (this.isBuilder(destID)) {
|
|
697
|
-
destIDVal = destID.placeholderID;
|
|
698
|
-
destPlaceholder = true;
|
|
699
|
-
}
|
|
700
|
-
else {
|
|
701
|
-
destIDVal = destID;
|
|
702
|
-
}
|
|
703
|
-
let srcIDVal;
|
|
704
|
-
let srcType;
|
|
705
|
-
let srcPlaceholder = false;
|
|
706
|
-
if (srcBuilder.existingEnt) {
|
|
707
|
-
srcIDVal = srcBuilder.existingEnt.id;
|
|
708
|
-
srcType = srcBuilder.existingEnt.nodeType;
|
|
709
|
-
}
|
|
710
|
-
else {
|
|
711
|
-
srcPlaceholder = true;
|
|
712
|
-
// get placeholder.
|
|
713
|
-
srcIDVal = srcBuilder.placeholderID;
|
|
714
|
-
// expected to be filled later
|
|
715
|
-
srcType = "";
|
|
716
|
-
}
|
|
717
|
-
return [srcIDVal, srcType, srcPlaceholder, destIDVal, destPlaceholder];
|
|
718
|
-
}
|
|
719
|
-
static isBuilder(val) {
|
|
720
|
-
return val.placeholderID !== undefined;
|
|
721
|
-
}
|
|
722
|
-
static resolveData(data) {
|
|
723
|
-
if (!data) {
|
|
724
|
-
return [undefined, false];
|
|
725
|
-
}
|
|
726
|
-
if (this.isBuilder(data)) {
|
|
727
|
-
return [data.placeholderID.toString(), true];
|
|
728
|
-
}
|
|
729
|
-
return [data, false];
|
|
730
|
-
}
|
|
731
|
-
static inboundEdge(builder, edgeType, id1, nodeType, options) {
|
|
732
|
-
let [id2Val, id2Type, id2Placeholder, id1Val, id1Placeholder] = EdgeOperation.resolveIDs(builder, id1);
|
|
733
|
-
let [data, dataPlaceholder] = EdgeOperation.resolveData(options?.data);
|
|
734
|
-
const edge = {
|
|
735
|
-
id1: id1Val,
|
|
736
|
-
edgeType: edgeType,
|
|
737
|
-
id2: id2Val,
|
|
738
|
-
id2Type: id2Type,
|
|
739
|
-
id1Type: nodeType,
|
|
740
|
-
...options,
|
|
741
|
-
};
|
|
742
|
-
if (data) {
|
|
743
|
-
edge.data = data;
|
|
744
|
-
}
|
|
745
|
-
return new EdgeOperation(edge, {
|
|
746
|
-
operation: action_1.WriteOperation.Insert,
|
|
747
|
-
id2Placeholder,
|
|
748
|
-
id1Placeholder,
|
|
749
|
-
dataPlaceholder,
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
static outboundEdge(builder, edgeType, id2, nodeType, options) {
|
|
753
|
-
let [id1Val, id1Type, id1Placeholder, id2Val, id2Placeholder] = EdgeOperation.resolveIDs(builder, id2);
|
|
754
|
-
let [data, dataPlaceholder] = EdgeOperation.resolveData(options?.data);
|
|
755
|
-
const edge = {
|
|
756
|
-
id1: id1Val,
|
|
757
|
-
edgeType: edgeType,
|
|
758
|
-
id2: id2Val,
|
|
759
|
-
id2Type: nodeType,
|
|
760
|
-
id1Type: id1Type,
|
|
761
|
-
...options,
|
|
762
|
-
};
|
|
763
|
-
if (data) {
|
|
764
|
-
edge.data = data;
|
|
765
|
-
}
|
|
766
|
-
return new EdgeOperation(edge, {
|
|
767
|
-
operation: action_1.WriteOperation.Insert,
|
|
768
|
-
id1Placeholder,
|
|
769
|
-
id2Placeholder,
|
|
770
|
-
dataPlaceholder,
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
static removeInboundEdge(builder, edgeType, id1) {
|
|
774
|
-
if (!builder.existingEnt) {
|
|
775
|
-
throw new Error("cannot remove an edge from a non-existing ent");
|
|
776
|
-
}
|
|
777
|
-
const edge = {
|
|
778
|
-
id1: id1,
|
|
779
|
-
edgeType: edgeType,
|
|
780
|
-
id2: builder.existingEnt.id,
|
|
781
|
-
id2Type: "",
|
|
782
|
-
id1Type: "",
|
|
783
|
-
};
|
|
784
|
-
return new EdgeOperation(edge, {
|
|
785
|
-
operation: action_1.WriteOperation.Delete,
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
static removeOutboundEdge(builder, edgeType, id2) {
|
|
789
|
-
if (!builder.existingEnt) {
|
|
790
|
-
throw new Error("cannot remove an edge from a non-existing ent");
|
|
791
|
-
}
|
|
792
|
-
const edge = {
|
|
793
|
-
id2: id2,
|
|
794
|
-
edgeType: edgeType,
|
|
795
|
-
id1: builder.existingEnt.id,
|
|
796
|
-
id2Type: "",
|
|
797
|
-
id1Type: "",
|
|
798
|
-
};
|
|
799
|
-
return new EdgeOperation(edge, {
|
|
800
|
-
operation: action_1.WriteOperation.Delete,
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
exports.EdgeOperation = EdgeOperation;
|
|
805
656
|
function isSyncQueryer(queryer) {
|
|
806
657
|
return queryer.execSync !== undefined;
|
|
807
658
|
}
|
|
808
659
|
async function mutateRow(queryer, query, values, logValues, options) {
|
|
809
660
|
logQuery(query, logValues);
|
|
810
661
|
let cache = options.context?.cache;
|
|
662
|
+
let res;
|
|
811
663
|
try {
|
|
812
|
-
let res;
|
|
813
664
|
if (isSyncQueryer(queryer)) {
|
|
814
665
|
res = queryer.execSync(query, values);
|
|
815
666
|
}
|
|
816
667
|
else {
|
|
817
668
|
res = await queryer.exec(query, values);
|
|
818
669
|
}
|
|
819
|
-
|
|
820
|
-
|
|
670
|
+
}
|
|
671
|
+
catch (e) {
|
|
672
|
+
if (_logQueryWithError) {
|
|
673
|
+
const msg = e.message;
|
|
674
|
+
throw new Error(`error \`${msg}\` running query: \`${query}\``);
|
|
821
675
|
}
|
|
822
|
-
|
|
676
|
+
throw e;
|
|
823
677
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
(0, logger_1.log)("error", err);
|
|
827
|
-
throw err;
|
|
678
|
+
if (cache) {
|
|
679
|
+
cache.clearCache();
|
|
828
680
|
}
|
|
681
|
+
return res;
|
|
829
682
|
}
|
|
830
683
|
function mutateRowSync(queryer, query, values, logValues, options) {
|
|
831
684
|
logQuery(query, logValues);
|
|
@@ -837,10 +690,12 @@ function mutateRowSync(queryer, query, values, logValues, options) {
|
|
|
837
690
|
}
|
|
838
691
|
return res;
|
|
839
692
|
}
|
|
840
|
-
catch (
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
693
|
+
catch (e) {
|
|
694
|
+
if (_logQueryWithError) {
|
|
695
|
+
const msg = e.message;
|
|
696
|
+
throw new Error(`error \`${msg}\` running query: \`${query}\``);
|
|
697
|
+
}
|
|
698
|
+
throw e;
|
|
844
699
|
}
|
|
845
700
|
}
|
|
846
701
|
function buildInsertQuery(options, suffix) {
|
|
@@ -867,8 +722,26 @@ function buildInsertQuery(options, suffix) {
|
|
|
867
722
|
const cols = fields.join(", ");
|
|
868
723
|
const vals = valsString.join(", ");
|
|
869
724
|
let query = `INSERT INTO ${options.tableName} (${cols}) VALUES (${vals})`;
|
|
725
|
+
if (options.onConflict) {
|
|
726
|
+
let onConflict = "";
|
|
727
|
+
if (options.onConflict.onConflictConstraint) {
|
|
728
|
+
onConflict = `ON CONFLICT ON CONSTRAINT ${options.onConflict.onConflictConstraint}`;
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
onConflict = `ON CONFLICT(${options.onConflict.onConflictCols.join(", ")})`;
|
|
732
|
+
}
|
|
733
|
+
if (options.onConflict.updateCols?.length) {
|
|
734
|
+
onConflict += ` DO UPDATE SET ${options.onConflict.updateCols
|
|
735
|
+
.map((f) => `${f} = EXCLUDED.${f}`)
|
|
736
|
+
.join(", ")}`;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
onConflict += ` DO NOTHING`;
|
|
740
|
+
}
|
|
741
|
+
query = query + " " + onConflict;
|
|
742
|
+
}
|
|
870
743
|
if (suffix) {
|
|
871
|
-
query
|
|
744
|
+
query += " " + suffix;
|
|
872
745
|
}
|
|
873
746
|
return [query, values, logValues];
|
|
874
747
|
}
|
|
@@ -893,32 +766,47 @@ function createRowSync(queryer, options, suffix) {
|
|
|
893
766
|
return null;
|
|
894
767
|
}
|
|
895
768
|
exports.createRowSync = createRowSync;
|
|
896
|
-
function buildUpdateQuery(options,
|
|
769
|
+
function buildUpdateQuery(options, suffix) {
|
|
897
770
|
let valsString = [];
|
|
898
771
|
let values = [];
|
|
899
772
|
let logValues = [];
|
|
900
773
|
const dialect = db_1.default.getDialect();
|
|
901
774
|
let idx = 1;
|
|
902
775
|
for (const key in options.fields) {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
776
|
+
if (options.expressions && options.expressions.has(key)) {
|
|
777
|
+
const cls = options.expressions.get(key);
|
|
778
|
+
valsString.push(`${key} = ${cls.clause(idx)}`);
|
|
779
|
+
// TODO need to test a clause with more than one value...
|
|
780
|
+
const newVals = cls.values();
|
|
781
|
+
idx += newVals.length;
|
|
782
|
+
values.push(...newVals);
|
|
783
|
+
logValues.push(...cls.logValues());
|
|
910
784
|
}
|
|
911
785
|
else {
|
|
912
|
-
|
|
786
|
+
const val = options.fields[key];
|
|
787
|
+
values.push(val);
|
|
788
|
+
if (options.fieldsToLog) {
|
|
789
|
+
logValues.push(options.fieldsToLog[key]);
|
|
790
|
+
}
|
|
791
|
+
// TODO would be nice to use clause here. need update version of the queries so that
|
|
792
|
+
// we don't have to handle dialect specifics here
|
|
793
|
+
// can't use clause because of IS NULL
|
|
794
|
+
// valsString.push(clause.Eq(key, val).clause(idx));
|
|
795
|
+
if (dialect === db_1.Dialect.Postgres) {
|
|
796
|
+
valsString.push(`${key} = $${idx}`);
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
valsString.push(`${key} = ?`);
|
|
800
|
+
}
|
|
801
|
+
idx++;
|
|
913
802
|
}
|
|
914
803
|
}
|
|
915
804
|
const vals = valsString.join(", ");
|
|
916
805
|
let query = `UPDATE ${options.tableName} SET ${vals} WHERE `;
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
query = query + `${options.key} = ?`;
|
|
806
|
+
query = query + options.whereClause.clause(idx);
|
|
807
|
+
values.push(...options.whereClause.values());
|
|
808
|
+
if (options.fieldsToLog) {
|
|
809
|
+
logValues.push(...options.whereClause.logValues());
|
|
922
810
|
}
|
|
923
811
|
if (suffix) {
|
|
924
812
|
query = query + " " + suffix;
|
|
@@ -926,10 +814,8 @@ function buildUpdateQuery(options, id, suffix) {
|
|
|
926
814
|
return [query, values, logValues];
|
|
927
815
|
}
|
|
928
816
|
exports.buildUpdateQuery = buildUpdateQuery;
|
|
929
|
-
async function editRow(queryer, options,
|
|
930
|
-
const [query, values, logValues] = buildUpdateQuery(options,
|
|
931
|
-
// add id as value to prepared query
|
|
932
|
-
values.push(id);
|
|
817
|
+
async function editRow(queryer, options, suffix) {
|
|
818
|
+
const [query, values, logValues] = buildUpdateQuery(options, suffix);
|
|
933
819
|
const res = await mutateRow(queryer, query, values, logValues, options);
|
|
934
820
|
if (res?.rowCount == 1) {
|
|
935
821
|
// for now assume id primary key
|
|
@@ -940,10 +826,8 @@ async function editRow(queryer, options, id, suffix) {
|
|
|
940
826
|
return null;
|
|
941
827
|
}
|
|
942
828
|
exports.editRow = editRow;
|
|
943
|
-
function editRowSync(queryer, options,
|
|
944
|
-
const [query, values, logValues] = buildUpdateQuery(options,
|
|
945
|
-
// add id as value to prepared query
|
|
946
|
-
values.push(id);
|
|
829
|
+
function editRowSync(queryer, options, suffix) {
|
|
830
|
+
const [query, values, logValues] = buildUpdateQuery(options, suffix);
|
|
947
831
|
const res = mutateRowSync(queryer, query, values, logValues, options);
|
|
948
832
|
if (res?.rowCount == 1) {
|
|
949
833
|
// for now assume id primary key
|
|
@@ -964,27 +848,6 @@ function deleteRowsSync(queryer, options, cls) {
|
|
|
964
848
|
mutateRowSync(queryer, query, cls.values(), cls.logValues(), options);
|
|
965
849
|
}
|
|
966
850
|
exports.deleteRowsSync = deleteRowsSync;
|
|
967
|
-
class DeleteNodeOperation {
|
|
968
|
-
constructor(id, options) {
|
|
969
|
-
this.id = id;
|
|
970
|
-
this.options = options;
|
|
971
|
-
}
|
|
972
|
-
async performWrite(queryer, context) {
|
|
973
|
-
let options = {
|
|
974
|
-
...this.options,
|
|
975
|
-
context,
|
|
976
|
-
};
|
|
977
|
-
return deleteRows(queryer, options, clause.Eq("id", this.id));
|
|
978
|
-
}
|
|
979
|
-
performWriteSync(queryer, context) {
|
|
980
|
-
let options = {
|
|
981
|
-
...this.options,
|
|
982
|
-
context,
|
|
983
|
-
};
|
|
984
|
-
return deleteRowsSync(queryer, options, clause.Eq("id", this.id));
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
exports.DeleteNodeOperation = DeleteNodeOperation;
|
|
988
851
|
class AssocEdge {
|
|
989
852
|
constructor(data) {
|
|
990
853
|
this.id1 = data.id1;
|
|
@@ -994,21 +857,22 @@ class AssocEdge {
|
|
|
994
857
|
this.edgeType = data.edge_type;
|
|
995
858
|
this.time = data.time;
|
|
996
859
|
this.data = data.data;
|
|
860
|
+
this.rawData = data;
|
|
861
|
+
}
|
|
862
|
+
__getRawData() {
|
|
863
|
+
// incase there's extra db fields. useful for tests
|
|
864
|
+
// in production, a subclass of this should be in use so we won't need this...
|
|
865
|
+
return this.rawData;
|
|
997
866
|
}
|
|
998
867
|
getCursor() {
|
|
999
868
|
return getCursor({
|
|
1000
869
|
row: this,
|
|
1001
|
-
col: "
|
|
1002
|
-
conv: (t) => {
|
|
1003
|
-
if (typeof t === "string") {
|
|
1004
|
-
return Date.parse(t);
|
|
1005
|
-
}
|
|
1006
|
-
return t.getTime();
|
|
1007
|
-
},
|
|
870
|
+
col: "id2",
|
|
1008
871
|
});
|
|
1009
872
|
}
|
|
1010
873
|
}
|
|
1011
874
|
exports.AssocEdge = AssocEdge;
|
|
875
|
+
// TODO eventually update this for sortCol time unique keys
|
|
1012
876
|
function getCursor(opts) {
|
|
1013
877
|
const { row, col, conv } = opts;
|
|
1014
878
|
// row: Data, col: string, conv?: (any) => any) {
|
|
@@ -1044,10 +908,11 @@ const assocEdgeFields = [
|
|
|
1044
908
|
"inverse_edge_type",
|
|
1045
909
|
"edge_table",
|
|
1046
910
|
];
|
|
1047
|
-
exports.assocEdgeLoader =
|
|
911
|
+
exports.assocEdgeLoader = createAssocEdgeConfigLoader({
|
|
1048
912
|
tableName: "assoc_edge_config",
|
|
1049
913
|
fields: assocEdgeFields,
|
|
1050
914
|
key: "edge_type",
|
|
915
|
+
keyType: "uuid",
|
|
1051
916
|
});
|
|
1052
917
|
// we don't expect assoc_edge_config information to change
|
|
1053
918
|
// so not using ContextCache but just caching it as needed once per server
|
|
@@ -1069,7 +934,7 @@ async function loadEdgeDatas(...edgeTypes) {
|
|
|
1069
934
|
if (!row) {
|
|
1070
935
|
return;
|
|
1071
936
|
}
|
|
1072
|
-
if (row
|
|
937
|
+
if (rowIsError(row)) {
|
|
1073
938
|
throw row;
|
|
1074
939
|
}
|
|
1075
940
|
m.set(row["edge_type"], new AssocEdgeData(row));
|
|
@@ -1086,54 +951,94 @@ const edgeFields = [
|
|
|
1086
951
|
"time",
|
|
1087
952
|
"data",
|
|
1088
953
|
];
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
954
|
+
let defaultLimit = 1000;
|
|
955
|
+
function setDefaultLimit(limit) {
|
|
956
|
+
defaultLimit = limit;
|
|
957
|
+
}
|
|
958
|
+
exports.setDefaultLimit = setDefaultLimit;
|
|
959
|
+
function getDefaultLimit() {
|
|
960
|
+
return defaultLimit;
|
|
961
|
+
}
|
|
962
|
+
exports.getDefaultLimit = getDefaultLimit;
|
|
963
|
+
function defaultEdgeQueryOptions(id1, edgeType, id2) {
|
|
964
|
+
let cls = clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType));
|
|
965
|
+
if (id2) {
|
|
966
|
+
cls = clause.And(cls, clause.Eq("id2", id2));
|
|
967
|
+
}
|
|
1092
968
|
return {
|
|
1093
|
-
clause:
|
|
1094
|
-
orderby:
|
|
1095
|
-
|
|
969
|
+
clause: cls,
|
|
970
|
+
orderby: [
|
|
971
|
+
{
|
|
972
|
+
column: "time",
|
|
973
|
+
direction: "DESC",
|
|
974
|
+
},
|
|
975
|
+
],
|
|
976
|
+
limit: defaultLimit,
|
|
1096
977
|
};
|
|
1097
978
|
}
|
|
1098
|
-
exports.defaultEdgeQueryOptions = defaultEdgeQueryOptions;
|
|
1099
979
|
async function loadEdges(options) {
|
|
1100
980
|
return loadCustomEdges({ ...options, ctr: AssocEdge });
|
|
1101
981
|
}
|
|
1102
982
|
exports.loadEdges = loadEdges;
|
|
983
|
+
function getEdgeClauseAndFields(cls, options) {
|
|
984
|
+
let fields = edgeFields;
|
|
985
|
+
const transformEdgeRead = (0, global_schema_1.__getGlobalSchema)()?.transformEdgeRead;
|
|
986
|
+
if (transformEdgeRead) {
|
|
987
|
+
const transformClause = transformEdgeRead();
|
|
988
|
+
if (!options.queryOptions?.disableTransformations) {
|
|
989
|
+
cls = clause.And(cls, transformClause);
|
|
990
|
+
}
|
|
991
|
+
fields = edgeFields.concat(transformClause.columns());
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
cls,
|
|
995
|
+
fields,
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
exports.getEdgeClauseAndFields = getEdgeClauseAndFields;
|
|
1103
999
|
async function loadCustomEdges(options) {
|
|
1104
|
-
const {
|
|
1000
|
+
const { cls: actualClause, fields, defaultOptions, tableName, } = await loadEgesInfo(options);
|
|
1001
|
+
const rows = await loadRows({
|
|
1002
|
+
tableName,
|
|
1003
|
+
fields: fields,
|
|
1004
|
+
clause: actualClause,
|
|
1005
|
+
orderby: options.queryOptions?.orderby || defaultOptions.orderby,
|
|
1006
|
+
limit: options.queryOptions?.limit || defaultOptions.limit,
|
|
1007
|
+
context: options.context,
|
|
1008
|
+
});
|
|
1009
|
+
return rows.map((row) => {
|
|
1010
|
+
return new options.ctr(row);
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
exports.loadCustomEdges = loadCustomEdges;
|
|
1014
|
+
async function loadEgesInfo(options, id2) {
|
|
1015
|
+
const { id1, edgeType } = options;
|
|
1105
1016
|
const edgeData = await loadEdgeData(edgeType);
|
|
1106
1017
|
if (!edgeData) {
|
|
1107
1018
|
throw new Error(`error loading edge data for ${edgeType}`);
|
|
1108
1019
|
}
|
|
1109
|
-
const defaultOptions = defaultEdgeQueryOptions(id1, edgeType);
|
|
1020
|
+
const defaultOptions = defaultEdgeQueryOptions(id1, edgeType, id2);
|
|
1110
1021
|
let cls = defaultOptions.clause;
|
|
1111
1022
|
if (options.queryOptions?.clause) {
|
|
1112
1023
|
cls = clause.And(cls, options.queryOptions.clause);
|
|
1113
1024
|
}
|
|
1114
|
-
|
|
1025
|
+
return {
|
|
1026
|
+
...getEdgeClauseAndFields(cls, options),
|
|
1027
|
+
defaultOptions,
|
|
1115
1028
|
tableName: edgeData.edgeTable,
|
|
1116
|
-
|
|
1117
|
-
clause: cls,
|
|
1118
|
-
orderby: options.queryOptions?.orderby || defaultOptions.orderby,
|
|
1119
|
-
limit: options.queryOptions?.limit || defaultOptions.limit,
|
|
1120
|
-
context,
|
|
1121
|
-
});
|
|
1122
|
-
return rows.map((row) => {
|
|
1123
|
-
return new options.ctr(row);
|
|
1124
|
-
});
|
|
1029
|
+
};
|
|
1125
1030
|
}
|
|
1126
|
-
exports.loadCustomEdges = loadCustomEdges;
|
|
1127
1031
|
async function loadUniqueEdge(options) {
|
|
1128
1032
|
const { id1, edgeType, context } = options;
|
|
1129
1033
|
const edgeData = await loadEdgeData(edgeType);
|
|
1130
1034
|
if (!edgeData) {
|
|
1131
1035
|
throw new Error(`error loading edge data for ${edgeType}`);
|
|
1132
1036
|
}
|
|
1037
|
+
const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
|
|
1133
1038
|
const row = await loadRow({
|
|
1134
1039
|
tableName: edgeData.edgeTable,
|
|
1135
|
-
fields:
|
|
1136
|
-
clause:
|
|
1040
|
+
fields: fields,
|
|
1041
|
+
clause: cls,
|
|
1137
1042
|
context,
|
|
1138
1043
|
});
|
|
1139
1044
|
if (!row) {
|
|
@@ -1160,21 +1065,28 @@ async function loadRawEdgeCountX(options) {
|
|
|
1160
1065
|
if (!edgeData) {
|
|
1161
1066
|
throw new Error(`error loading edge data for ${edgeType}`);
|
|
1162
1067
|
}
|
|
1068
|
+
const { cls } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
|
|
1163
1069
|
const row = await loadRowX({
|
|
1164
1070
|
tableName: edgeData.edgeTable,
|
|
1165
1071
|
// sqlite needs as count otherwise it returns count(1)
|
|
1166
1072
|
fields: ["count(1) as count"],
|
|
1167
|
-
clause:
|
|
1073
|
+
clause: cls,
|
|
1168
1074
|
context,
|
|
1169
1075
|
});
|
|
1170
1076
|
return parseInt(row["count"], 10) || 0;
|
|
1171
1077
|
}
|
|
1172
1078
|
exports.loadRawEdgeCountX = loadRawEdgeCountX;
|
|
1173
1079
|
async function loadEdgeForID2(options) {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1080
|
+
const { cls: actualClause, fields, tableName, } = await loadEgesInfo(options, options.id2);
|
|
1081
|
+
const row = await loadRow({
|
|
1082
|
+
tableName,
|
|
1083
|
+
fields: fields,
|
|
1084
|
+
clause: actualClause,
|
|
1085
|
+
context: options.context,
|
|
1086
|
+
});
|
|
1087
|
+
if (row) {
|
|
1088
|
+
return new options.ctr(row);
|
|
1089
|
+
}
|
|
1178
1090
|
}
|
|
1179
1091
|
exports.loadEdgeForID2 = loadEdgeForID2;
|
|
1180
1092
|
async function loadNodesByEdge(viewer, id1, edgeType, options) {
|
|
@@ -1190,19 +1102,20 @@ async function loadNodesByEdge(viewer, id1, edgeType, options) {
|
|
|
1190
1102
|
}
|
|
1191
1103
|
exports.loadNodesByEdge = loadNodesByEdge;
|
|
1192
1104
|
async function applyPrivacyPolicyForRow(viewer, options, row) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
}
|
|
1196
|
-
const ent = new options.ent(viewer, row);
|
|
1197
|
-
return await applyPrivacyPolicyForEnt(viewer, ent, row, options);
|
|
1105
|
+
const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
|
|
1106
|
+
return rowIsError(r) ? null : r;
|
|
1198
1107
|
}
|
|
1199
1108
|
exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
|
|
1109
|
+
async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
|
|
1110
|
+
const ent = new options.ent(viewer, row);
|
|
1111
|
+
return applyPrivacyPolicyForEnt(viewer, ent, row, options);
|
|
1112
|
+
}
|
|
1200
1113
|
async function applyPrivacyPolicyForRowX(viewer, options, row) {
|
|
1201
1114
|
const ent = new options.ent(viewer, row);
|
|
1202
1115
|
return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
|
|
1203
1116
|
}
|
|
1204
|
-
|
|
1205
|
-
async function
|
|
1117
|
+
// deprecated. doesn't use entcache
|
|
1118
|
+
async function applyPrivacyPolicyForRowsDeprecated(viewer, rows, options) {
|
|
1206
1119
|
let m = new Map();
|
|
1207
1120
|
// apply privacy logic
|
|
1208
1121
|
await Promise.all(rows.map(async (row) => {
|
|
@@ -1213,27 +1126,62 @@ async function applyPrivacyPolicyForRows(viewer, rows, options) {
|
|
|
1213
1126
|
}));
|
|
1214
1127
|
return m;
|
|
1215
1128
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1129
|
+
async function applyPrivacyPolicyForRows(viewer, rows, options) {
|
|
1130
|
+
const result = new Array(rows.length);
|
|
1131
|
+
if (!rows.length) {
|
|
1132
|
+
return [];
|
|
1133
|
+
}
|
|
1134
|
+
const entLoader = getEntLoader(viewer, options);
|
|
1135
|
+
await Promise.all(rows.map(async (row, idx) => {
|
|
1136
|
+
const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options, entLoader);
|
|
1137
|
+
if (r instanceof ErrorWrapper) {
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
result[idx] = r;
|
|
1141
|
+
}));
|
|
1142
|
+
// filter ents that aren't visible because of privacy
|
|
1143
|
+
return result.filter((r) => r !== undefined);
|
|
1226
1144
|
}
|
|
1145
|
+
exports.applyPrivacyPolicyForRows = applyPrivacyPolicyForRows;
|
|
1227
1146
|
// given a viewer, an id pair, and a map of edgeEnum to EdgeType
|
|
1228
1147
|
// return the edgeEnum that's set in the group
|
|
1229
1148
|
async function getEdgeTypeInGroup(viewer, id1, id2, m) {
|
|
1230
1149
|
let promises = [];
|
|
1231
|
-
|
|
1232
|
-
|
|
1150
|
+
const edgeDatas = await loadEdgeDatas(...Array.from(m.values()));
|
|
1151
|
+
let tableToEdgeEnumMap = new Map();
|
|
1152
|
+
for (const [edgeEnum, edgeType] of m) {
|
|
1153
|
+
const edgeData = edgeDatas.get(edgeType);
|
|
1154
|
+
if (!edgeData) {
|
|
1155
|
+
throw new Error(`could not load edge data for '${edgeType}'`);
|
|
1156
|
+
}
|
|
1157
|
+
const l = tableToEdgeEnumMap.get(edgeData.edgeTable) ?? [];
|
|
1158
|
+
l.push(edgeEnum);
|
|
1159
|
+
tableToEdgeEnumMap.set(edgeData.edgeTable, l);
|
|
1233
1160
|
}
|
|
1161
|
+
tableToEdgeEnumMap.forEach((edgeEnums, tableName) => {
|
|
1162
|
+
promises.push((async () => {
|
|
1163
|
+
const edgeTypes = edgeEnums.map((edgeEnum) => m.get(edgeEnum));
|
|
1164
|
+
const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.UuidIn("edge_type", edgeTypes), clause.Eq("id2", id2)), {});
|
|
1165
|
+
const rows = await loadRows({
|
|
1166
|
+
tableName,
|
|
1167
|
+
fields,
|
|
1168
|
+
clause: cls,
|
|
1169
|
+
context: viewer.context,
|
|
1170
|
+
});
|
|
1171
|
+
const row = rows[0];
|
|
1172
|
+
if (row) {
|
|
1173
|
+
const edgeType = row.edge_type;
|
|
1174
|
+
for (const [k, v] of m) {
|
|
1175
|
+
if (v === edgeType) {
|
|
1176
|
+
return [k, new AssocEdge(row)];
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
})());
|
|
1181
|
+
});
|
|
1234
1182
|
const results = await Promise.all(promises);
|
|
1235
1183
|
for (const res of results) {
|
|
1236
|
-
if (res[1]) {
|
|
1184
|
+
if (res && res[1]) {
|
|
1237
1185
|
return [res[0], res[1]];
|
|
1238
1186
|
}
|
|
1239
1187
|
}
|