terminusdb 12.0.2
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/Contributing.md +36 -0
- package/LICENSE +201 -0
- package/README.md +175 -0
- package/RELEASE_NOTES.md +462 -0
- package/dist/index.html +22 -0
- package/dist/terminusdb-client.min.js +3 -0
- package/dist/terminusdb-client.min.js.LICENSE.txt +188 -0
- package/dist/terminusdb-client.min.js.map +1 -0
- package/dist/typescript/index.d.ts +14 -0
- package/dist/typescript/lib/accessControl.d.ts +554 -0
- package/dist/typescript/lib/axiosInstance.d.ts +2 -0
- package/dist/typescript/lib/connectionConfig.d.ts +381 -0
- package/dist/typescript/lib/const.d.ts +54 -0
- package/dist/typescript/lib/dispatchRequest.d.ts +17 -0
- package/dist/typescript/lib/errorMessage.d.ts +25 -0
- package/dist/typescript/lib/query/woqlBuilder.d.ts +75 -0
- package/dist/typescript/lib/query/woqlCore.d.ts +341 -0
- package/dist/typescript/lib/query/woqlDoc.d.ts +63 -0
- package/dist/typescript/lib/query/woqlLibrary.d.ts +718 -0
- package/dist/typescript/lib/query/woqlPrinter.d.ts +71 -0
- package/dist/typescript/lib/query/woqlQuery.d.ts +833 -0
- package/dist/typescript/lib/typedef.d.ts +624 -0
- package/dist/typescript/lib/utils.d.ts +199 -0
- package/dist/typescript/lib/valueHash.d.ts +146 -0
- package/dist/typescript/lib/viewer/chartConfig.d.ts +62 -0
- package/dist/typescript/lib/viewer/chooserConfig.d.ts +38 -0
- package/dist/typescript/lib/viewer/documentFrame.d.ts +44 -0
- package/dist/typescript/lib/viewer/frameConfig.d.ts +74 -0
- package/dist/typescript/lib/viewer/frameRule.d.ts +145 -0
- package/dist/typescript/lib/viewer/graphConfig.d.ts +73 -0
- package/dist/typescript/lib/viewer/objectFrame.d.ts +212 -0
- package/dist/typescript/lib/viewer/streamConfig.d.ts +23 -0
- package/dist/typescript/lib/viewer/tableConfig.d.ts +66 -0
- package/dist/typescript/lib/viewer/terminusRule.d.ts +75 -0
- package/dist/typescript/lib/viewer/viewConfig.d.ts +47 -0
- package/dist/typescript/lib/viewer/woqlChart.d.ts +1 -0
- package/dist/typescript/lib/viewer/woqlChooser.d.ts +56 -0
- package/dist/typescript/lib/viewer/woqlGraph.d.ts +26 -0
- package/dist/typescript/lib/viewer/woqlPaging.d.ts +1 -0
- package/dist/typescript/lib/viewer/woqlResult.d.ts +128 -0
- package/dist/typescript/lib/viewer/woqlRule.d.ts +96 -0
- package/dist/typescript/lib/viewer/woqlStream.d.ts +31 -0
- package/dist/typescript/lib/viewer/woqlTable.d.ts +102 -0
- package/dist/typescript/lib/viewer/woqlView.d.ts +49 -0
- package/dist/typescript/lib/woql.d.ts +1267 -0
- package/dist/typescript/lib/woqlClient.d.ts +1216 -0
- package/index.js +28 -0
- package/lib/.eslintrc +1 -0
- package/lib/accessControl.js +988 -0
- package/lib/axiosInstance.js +5 -0
- package/lib/connectionConfig.js +765 -0
- package/lib/const.js +59 -0
- package/lib/dispatchRequest.js +236 -0
- package/lib/errorMessage.js +110 -0
- package/lib/query/woqlBuilder.js +234 -0
- package/lib/query/woqlCore.js +934 -0
- package/lib/query/woqlDoc.js +177 -0
- package/lib/query/woqlLibrary.js +1015 -0
- package/lib/query/woqlPrinter.js +476 -0
- package/lib/query/woqlQuery.js +1865 -0
- package/lib/typedef.js +248 -0
- package/lib/utils.js +817 -0
- package/lib/valueHash.js_old +581 -0
- package/lib/viewer/chartConfig.js +411 -0
- package/lib/viewer/chooserConfig.js +234 -0
- package/lib/viewer/documentFrame.js +206 -0
- package/lib/viewer/frameConfig.js +469 -0
- package/lib/viewer/frameRule.js +519 -0
- package/lib/viewer/graphConfig.js +345 -0
- package/lib/viewer/objectFrame.js +1550 -0
- package/lib/viewer/streamConfig.js +82 -0
- package/lib/viewer/tableConfig.js +310 -0
- package/lib/viewer/terminusRule.js +196 -0
- package/lib/viewer/viewConfig.js +219 -0
- package/lib/viewer/woqlChart.js +17 -0
- package/lib/viewer/woqlChooser.js +171 -0
- package/lib/viewer/woqlGraph.js +295 -0
- package/lib/viewer/woqlPaging.js +148 -0
- package/lib/viewer/woqlResult.js +258 -0
- package/lib/viewer/woqlRule.js +312 -0
- package/lib/viewer/woqlStream.js +27 -0
- package/lib/viewer/woqlTable.js +332 -0
- package/lib/viewer/woqlView.js +107 -0
- package/lib/woql.js +1693 -0
- package/lib/woqlClient.js +2091 -0
- package/package.json +110 -0
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
/// /@ts-check
|
|
2
|
+
// we can not import woqlBuilder because woqlBuilder import WOQLLibrary
|
|
3
|
+
const WOQLQuery = require('./woqlBuilder');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} WOQLQuery - WOQL Query Builder class
|
|
7
|
+
* @property {function} triple - Add a triple pattern
|
|
8
|
+
* @property {function} and - Logical AND
|
|
9
|
+
* @property {function} or - Logical OR
|
|
10
|
+
* @property {function} select - Select variables
|
|
11
|
+
* @property {function} ask - Ask query
|
|
12
|
+
* @property {function} limit - Limit results
|
|
13
|
+
* @property {function} offset - Offset results
|
|
14
|
+
* @property {function} start - Start query
|
|
15
|
+
* @property {function} path - Path query
|
|
16
|
+
* @property {function} subquery - Subquery
|
|
17
|
+
* @property {function} from - Graph specifier
|
|
18
|
+
* @property {function} eq - Equality
|
|
19
|
+
* @property {function} not - Negation
|
|
20
|
+
* @property {function} greaterThan - Greater than
|
|
21
|
+
* @property {function} lessThan - Less than
|
|
22
|
+
* @property {function} greaterThanOrEqual - Greater than or equal
|
|
23
|
+
* @property {function} lessThanOrEqual - Less than or equal
|
|
24
|
+
* @property {function} like - String pattern matching
|
|
25
|
+
* @property {function} plus - Addition
|
|
26
|
+
* @property {function} minus - Subtraction
|
|
27
|
+
* @property {function} times - Multiplication
|
|
28
|
+
* @property {function} divide - Division
|
|
29
|
+
* @property {function} count - Count aggregation
|
|
30
|
+
* @property {function} sum - Sum aggregation
|
|
31
|
+
* @property {function} min - Minimum aggregation
|
|
32
|
+
* @property {function} max - Maximum aggregation
|
|
33
|
+
* @property {function} avg - Average aggregation
|
|
34
|
+
* @property {function} groupBy - Group by
|
|
35
|
+
* @property {function} orderBy - Order by
|
|
36
|
+
* @property {function} asc - Ascending order
|
|
37
|
+
* @property {function} desc - Descending order
|
|
38
|
+
* @property {function} json - JSON object
|
|
39
|
+
* @property {function} id - IRI
|
|
40
|
+
* @property {function} string - String literal
|
|
41
|
+
* @property {function} datetime - DateTime literal
|
|
42
|
+
* @property {function} date - Date literal
|
|
43
|
+
* @property {function} time - Time literal
|
|
44
|
+
* @property {function} boolean - Boolean literal
|
|
45
|
+
* @property {function} integer - Integer literal
|
|
46
|
+
* @property {function} decimal - Decimal literal
|
|
47
|
+
* @property {function} double - Double literal
|
|
48
|
+
* @property {function} value - Value literal
|
|
49
|
+
* @property {function} var - Variable
|
|
50
|
+
* @property {function} v - Variable shorthand
|
|
51
|
+
* @property {function} doc - Document
|
|
52
|
+
* @property {function} class - Class
|
|
53
|
+
* @property {function} property - Property
|
|
54
|
+
* @property {function} comment - Comment
|
|
55
|
+
* @property {function} label - Label
|
|
56
|
+
* @property {function} prefix - Prefix
|
|
57
|
+
* @property {function} using - Using
|
|
58
|
+
* @property {function} optional - Optional
|
|
59
|
+
* @property {function} union - Union
|
|
60
|
+
* @property {function} filter - Filter
|
|
61
|
+
* @property {function} bind - Bind
|
|
62
|
+
* @property {function} exists - Exists
|
|
63
|
+
* @property {function} notExists - Not exists
|
|
64
|
+
* @property {function} graph - Graph
|
|
65
|
+
* @property {function} named - Named graph
|
|
66
|
+
* @property {function} values - Values
|
|
67
|
+
* @property {function} minus - Minus
|
|
68
|
+
* @property {function} service - Service
|
|
69
|
+
* @property {function} load - Load
|
|
70
|
+
* @property {function} clear - Clear
|
|
71
|
+
* @property {function} drop - Drop
|
|
72
|
+
* @property {function} add - Add
|
|
73
|
+
* @property {function} move - Move
|
|
74
|
+
* @property {function} copy - Copy
|
|
75
|
+
* @property {function} insert - Insert
|
|
76
|
+
* @property {function} delete - Delete
|
|
77
|
+
* @property {function} modify - Modify
|
|
78
|
+
* @property {function} construct - Construct
|
|
79
|
+
* @property {function} describe - Describe
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @license Apache Version 2
|
|
84
|
+
* @module WOQLLibrary
|
|
85
|
+
* @constructor WOQLLibrary
|
|
86
|
+
* @description Library Functions to manage the commits graph
|
|
87
|
+
* @example
|
|
88
|
+
* const woqlLib = WOQLLibrary()
|
|
89
|
+
* woqlLib.branches()
|
|
90
|
+
*
|
|
91
|
+
* //or you can call this functions using WOQL Class
|
|
92
|
+
* WOQL.lib().branches()
|
|
93
|
+
* */
|
|
94
|
+
class WOQLLibrary {
|
|
95
|
+
default_schema_resource = 'schema/main';
|
|
96
|
+
|
|
97
|
+
default_commit_resource = '_commits';
|
|
98
|
+
|
|
99
|
+
default_meta_resource = '_meta';
|
|
100
|
+
|
|
101
|
+
masterdb_resource = '_system';
|
|
102
|
+
|
|
103
|
+
empty = '';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* General Pattern 4: Retrieves Branches, Their ID, Head Commit ID, Head Commit Time
|
|
108
|
+
* (if present, new branches have no commits)
|
|
109
|
+
*/
|
|
110
|
+
WOQLLibrary.prototype.branches = function () { // values, variables, cresource) {
|
|
111
|
+
const woql = new WOQLQuery().using('_commits').triple('v:Branch', 'rdf:type', '@schema:Branch')
|
|
112
|
+
.triple('v:Branch', '@schema:name', 'v:Name')
|
|
113
|
+
.opt()
|
|
114
|
+
.triple('v:Branch', '@schema:head', 'v:Head')
|
|
115
|
+
.triple('v:Head', '@schema:identifier', 'v:commit_identifier')
|
|
116
|
+
.triple('v:Head', '@schema:timestamp', 'v:Timestamp');
|
|
117
|
+
return woql;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* get all the commits of a specific branch
|
|
122
|
+
* if a timestamp is given, gets all the commits before the specified timestamp
|
|
123
|
+
* @param {string} [branch] - the branch name
|
|
124
|
+
* @param {number} [limit] - the max number of result
|
|
125
|
+
* @param {number} [start] - the start of the pagination
|
|
126
|
+
* @param {number} [timestamp] - Unix timestamp in seconds
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
WOQLLibrary.prototype.commits = function (branch = 'main', limit = 0, start = 0, timestamp = 0) {
|
|
130
|
+
const woql = new WOQLQuery().using('_commits');
|
|
131
|
+
if (limit) woql.limit(limit);
|
|
132
|
+
if (start) woql.start(start);
|
|
133
|
+
woql.select('v:Parent ID', 'v:Commit ID', 'v:Time', 'v:Author', 'v:Branch ID', 'v:Message');
|
|
134
|
+
|
|
135
|
+
const andArr = [new WOQLQuery().triple('v:Branch', 'name', new WOQLQuery().string(branch))
|
|
136
|
+
.triple('v:Branch', 'head', 'v:Active Commit ID')
|
|
137
|
+
.path('v:Active Commit ID', 'parent*', 'v:Parent', 'v:Path')
|
|
138
|
+
.triple('v:Parent', 'timestamp', 'v:Time')];
|
|
139
|
+
if (timestamp) {
|
|
140
|
+
andArr.push(new WOQLQuery().less('v:Time', timestamp));
|
|
141
|
+
}
|
|
142
|
+
andArr.push(new WOQLQuery().triple('v:Parent', 'identifier', 'v:Commit ID')
|
|
143
|
+
.triple('v:Parent', 'author', 'v:Author')
|
|
144
|
+
.triple('v:Parent', 'message', 'v:Message')
|
|
145
|
+
.opt()
|
|
146
|
+
.triple('v:Parent', 'parent', 'v:Parent ID'));
|
|
147
|
+
return woql.and(...andArr);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
*get commits older than the specified commit id
|
|
152
|
+
* @param {string} [commit_id] - the commit id
|
|
153
|
+
* @param {number} [limit] - the max number of result
|
|
154
|
+
*/
|
|
155
|
+
// eslint-disable-next-line camelcase
|
|
156
|
+
WOQLLibrary.prototype.previousCommits = function (commit_id, limit = 10) {
|
|
157
|
+
return new WOQLQuery().using('_commits').limit(limit).select('v:Parent ID', 'v:Message', 'v:Commit ID', 'v:Time', 'v:Author')
|
|
158
|
+
.and(
|
|
159
|
+
new WOQLQuery().and(
|
|
160
|
+
new WOQLQuery().triple('v:Active Commit ID', '@schema:identifier', new WOQLQuery().string(commit_id)),
|
|
161
|
+
new WOQLQuery().path('v:Active Commit ID', '@schema:parent+', 'v:Parent', 'v:Path'),
|
|
162
|
+
new WOQLQuery().triple('v:Parent', '@schema:identifier', 'v:Commit ID'),
|
|
163
|
+
new WOQLQuery().triple('v:Parent', '@schema:timestamp', 'v:Time'),
|
|
164
|
+
new WOQLQuery().triple('v:Parent', '@schema:author', 'v:Author'),
|
|
165
|
+
new WOQLQuery().triple('v:Parent', '@schema:message', 'v:Message'),
|
|
166
|
+
new WOQLQuery().triple('v:Parent', '@schema:parent', 'v:Parent ID'),
|
|
167
|
+
new WOQLQuery().opt().triple('v:Parent', 'parent', 'v:Parent ID'),
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Finds the id of the very first commit in a database's history
|
|
174
|
+
*
|
|
175
|
+
* This is useful for finding information about when, by who and why the database was created
|
|
176
|
+
* The first commit is the only commit in the database that does not have a parent commit
|
|
177
|
+
*
|
|
178
|
+
*/
|
|
179
|
+
WOQLLibrary.prototype.first_commit = function () {
|
|
180
|
+
const noparent = new WOQLQuery()
|
|
181
|
+
.using('_commits').select('v:Any Commit IRI')
|
|
182
|
+
.and(
|
|
183
|
+
new WOQLQuery().triple('v:Branch', 'name', new WOQLQuery().string('main'))
|
|
184
|
+
.triple('v:Branch', 'head', 'v:Active Commit ID')
|
|
185
|
+
.path('v:Active Commit ID', 'parent*', 'v:Any Commit IRI', 'v:Path'),
|
|
186
|
+
|
|
187
|
+
new WOQLQuery().triple(
|
|
188
|
+
'v:Any Commit IRI',
|
|
189
|
+
'@schema:identifier',
|
|
190
|
+
'v:Commit ID',
|
|
191
|
+
),
|
|
192
|
+
new WOQLQuery().triple(
|
|
193
|
+
'v:Any Commit IRI',
|
|
194
|
+
'@schema:author',
|
|
195
|
+
'v:Author',
|
|
196
|
+
),
|
|
197
|
+
new WOQLQuery().triple(
|
|
198
|
+
'v:Any Commit IRI',
|
|
199
|
+
'@schema:message',
|
|
200
|
+
'v:Message',
|
|
201
|
+
),
|
|
202
|
+
new WOQLQuery()
|
|
203
|
+
.not()
|
|
204
|
+
.triple(
|
|
205
|
+
'v:Any Commit IRI',
|
|
206
|
+
'@schema:parent',
|
|
207
|
+
'v:Parent IRI',
|
|
208
|
+
),
|
|
209
|
+
|
|
210
|
+
);
|
|
211
|
+
return noparent;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// RDF List Operations
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Collect all rdf:List elements into a single array.
|
|
220
|
+
* Works in two modes:
|
|
221
|
+
* - Generator: If listVar is unbound, binds it to the array of all rdf:first values
|
|
222
|
+
* - Matching: If listVar is bound to an array, succeeds only if values match
|
|
223
|
+
* Uses group_by to collect all values in order (path queries return in hop-count order).
|
|
224
|
+
* @param {string} consSubject - Variable or IRI of the list cons cell (rdf:List head)
|
|
225
|
+
* @param {string} listVar - Variable to bind/match the resulting list values array
|
|
226
|
+
* @returns {WOQLQuery} Query that binds list as array to listVar (single binding)
|
|
227
|
+
* @example
|
|
228
|
+
* // Collect all task titles from a document's task list
|
|
229
|
+
* const query = WOQL.and(
|
|
230
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
231
|
+
* WOQL.lib().rdflist_list("v:list_head", "v:all_tasks")
|
|
232
|
+
* );
|
|
233
|
+
* // Result: { bindings: [{ "v:all_tasks": ["Task A", "Task B", "Task C"] }] }
|
|
234
|
+
*/
|
|
235
|
+
WOQLLibrary.prototype.rdflist_list = function (consSubject, listVar) {
|
|
236
|
+
// Include ALL parameters + internal variables in localize spec
|
|
237
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
238
|
+
consSubject,
|
|
239
|
+
listVar,
|
|
240
|
+
cell: null,
|
|
241
|
+
value: null,
|
|
242
|
+
});
|
|
243
|
+
return localized(
|
|
244
|
+
new WOQLQuery().group_by(
|
|
245
|
+
[],
|
|
246
|
+
[v.value],
|
|
247
|
+
v.listVar,
|
|
248
|
+
new WOQLQuery().and(
|
|
249
|
+
new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.cell),
|
|
250
|
+
new WOQLQuery().triple(v.cell, 'rdf:first', v.value),
|
|
251
|
+
),
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Internal: Get first element without localization.
|
|
258
|
+
* Used by other rdflist functions to avoid nested localization.
|
|
259
|
+
* @param {string|Var} consSubject - Variable or IRI of the list cons cell
|
|
260
|
+
* @param {string|Var} valueVar - Variable to bind the first value
|
|
261
|
+
* @returns {WOQLQuery} Query that binds first value to valueVar
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
265
|
+
WOQLLibrary.prototype._rdflist_peek_raw = function (consSubject, valueVar) {
|
|
266
|
+
return new WOQLQuery().and(
|
|
267
|
+
new WOQLQuery().triple(consSubject, 'rdf:type', 'rdf:List'),
|
|
268
|
+
new WOQLQuery().triple(consSubject, 'rdf:first', valueVar),
|
|
269
|
+
);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get the first element of an rdf:List (peek operation).
|
|
274
|
+
* Works as generator (binds value) or matcher (checks if first element matches).
|
|
275
|
+
* @param {string} consSubject - Variable or IRI of the list cons cell
|
|
276
|
+
* @param {string} valueVar - Variable to bind the first value
|
|
277
|
+
* @returns {WOQLQuery} Query that binds first value to valueVar
|
|
278
|
+
* @example
|
|
279
|
+
* // Get the first task from a project's task list
|
|
280
|
+
* const query = WOQL.and(
|
|
281
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
282
|
+
* WOQL.lib().rdflist_peek("v:list_head", "v:first_task")
|
|
283
|
+
* );
|
|
284
|
+
* // Result: { bindings: [{ "v:first_task": "Task A" }] }
|
|
285
|
+
*/
|
|
286
|
+
WOQLLibrary.prototype.rdflist_peek = function (consSubject, valueVar) {
|
|
287
|
+
const [localized, v] = new WOQLQuery().localize({ consSubject, valueVar });
|
|
288
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
289
|
+
return localized(this._rdflist_peek_raw(v.consSubject, v.valueVar));
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get the last element of an rdf:List
|
|
294
|
+
* Traverses to the final cons cell (whose rdf:rest is rdf:nil) and gets its rdf:first
|
|
295
|
+
* @param {string} consSubject - Variable or IRI of the list cons cell
|
|
296
|
+
* @param {string} valueVar - Variable to bind the last value
|
|
297
|
+
* @returns {WOQLQuery} Query that binds last value to valueVar
|
|
298
|
+
*/
|
|
299
|
+
WOQLLibrary.prototype.rdflist_last = function (consSubject, valueVar) {
|
|
300
|
+
// Include ALL parameters + internal variables in localize spec
|
|
301
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
302
|
+
consSubject,
|
|
303
|
+
valueVar,
|
|
304
|
+
last_cell: null,
|
|
305
|
+
});
|
|
306
|
+
return localized(
|
|
307
|
+
new WOQLQuery().and(
|
|
308
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
|
|
309
|
+
new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.last_cell),
|
|
310
|
+
new WOQLQuery().triple(v.last_cell, 'rdf:rest', 'rdf:nil'),
|
|
311
|
+
new WOQLQuery().triple(v.last_cell, 'rdf:first', v.valueVar),
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Internal: Get element at 0-indexed position without localization.
|
|
318
|
+
* @param {string|Var} consSubject - Variable or IRI of the list cons cell
|
|
319
|
+
* @param {number} index - 0-based index (must be number >= 0)
|
|
320
|
+
* @param {string|Var} valueVar - Variable to bind the value
|
|
321
|
+
* @returns {WOQLQuery} Query that binds value at index to valueVar
|
|
322
|
+
* @private
|
|
323
|
+
*/
|
|
324
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
325
|
+
WOQLLibrary.prototype._rdflist_nth0_raw = function (consSubject, index, valueVar) {
|
|
326
|
+
if (index === 0) {
|
|
327
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
328
|
+
return this._rdflist_peek_raw(consSubject, valueVar);
|
|
329
|
+
}
|
|
330
|
+
const pathPattern = `rdf:rest{${index},${index}}`;
|
|
331
|
+
return new WOQLQuery().and(
|
|
332
|
+
new WOQLQuery().triple(consSubject, 'rdf:type', 'rdf:List'),
|
|
333
|
+
new WOQLQuery().path(consSubject, pathPattern, 'v:_nth_cell'),
|
|
334
|
+
new WOQLQuery().triple('v:_nth_cell', 'rdf:first', valueVar),
|
|
335
|
+
);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get element at 0-indexed position in an rdf:List.
|
|
340
|
+
* Works as generator (binds value) or matcher (checks if element at index matches).
|
|
341
|
+
* @param {string} consSubject - Variable or IRI of the list cons cell
|
|
342
|
+
* @param {number|string} index - 0-based index (number or variable)
|
|
343
|
+
* @param {string} valueVar - Variable to bind the value at that index
|
|
344
|
+
* @returns {WOQLQuery} Query that binds value at index to valueVar
|
|
345
|
+
* @example
|
|
346
|
+
* // Get the second task (index 1) from a project's task list
|
|
347
|
+
* const query = WOQL.and(
|
|
348
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
349
|
+
* WOQL.lib().rdflist_nth0("v:list_head", 1, "v:second_task")
|
|
350
|
+
* );
|
|
351
|
+
* // Result: { bindings: [{ "v:second_task": "Task B" }] }
|
|
352
|
+
*/
|
|
353
|
+
WOQLLibrary.prototype.rdflist_nth0 = function (consSubject, index, valueVar) {
|
|
354
|
+
if (typeof index === 'number') {
|
|
355
|
+
if (index < 0) {
|
|
356
|
+
throw new Error('rdflist_nth0 requires index >= 0.');
|
|
357
|
+
}
|
|
358
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
359
|
+
consSubject,
|
|
360
|
+
valueVar,
|
|
361
|
+
});
|
|
362
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
363
|
+
return localized(this._rdflist_nth0_raw(v.consSubject, index, v.valueVar));
|
|
364
|
+
}
|
|
365
|
+
// Dynamic index via variable - use path with length matching
|
|
366
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
367
|
+
consSubject, index, valueVar, cell: null, path: null,
|
|
368
|
+
});
|
|
369
|
+
return localized(
|
|
370
|
+
new WOQLQuery().and(
|
|
371
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
|
|
372
|
+
new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.cell, v.path),
|
|
373
|
+
new WOQLQuery().length(v.path, v.index),
|
|
374
|
+
new WOQLQuery().triple(v.cell, 'rdf:first', v.valueVar),
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get element at 1-indexed position in an rdf:List.
|
|
381
|
+
* Works as generator (binds value) or matcher (checks if element at index matches).
|
|
382
|
+
* @param {string} consSubject - Variable or IRI of the list cons cell
|
|
383
|
+
* @param {number|string} index - 1-based index (number or variable)
|
|
384
|
+
* @param {string} valueVar - Variable to bind the value at that index
|
|
385
|
+
* @returns {WOQLQuery} Query that binds value at index to valueVar
|
|
386
|
+
* @example
|
|
387
|
+
* // Get the second task (index 2, 1-based) from a project's task list
|
|
388
|
+
* const query = WOQL.and(
|
|
389
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
390
|
+
* WOQL.lib().rdflist_nth1("v:list_head", 2, "v:second_task")
|
|
391
|
+
* );
|
|
392
|
+
* // Result: { bindings: [{ "v:second_task": "Task B" }] }
|
|
393
|
+
*/
|
|
394
|
+
WOQLLibrary.prototype.rdflist_nth1 = function (consSubject, index, valueVar) {
|
|
395
|
+
if (typeof index === 'number') {
|
|
396
|
+
if (index < 1) {
|
|
397
|
+
throw new Error('rdflist_nth1 requires index >= 1.');
|
|
398
|
+
}
|
|
399
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
400
|
+
consSubject,
|
|
401
|
+
valueVar,
|
|
402
|
+
});
|
|
403
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
404
|
+
return localized(this._rdflist_nth0_raw(v.consSubject, index - 1, v.valueVar));
|
|
405
|
+
}
|
|
406
|
+
// Dynamic index via variable - not supported with localize, would need server-side arithmetic
|
|
407
|
+
throw new Error('rdflist_nth1 with variable index not yet supported with localize pattern');
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Internal: Check if a value is a member of an rdf:List without localization.
|
|
412
|
+
* Used by other rdflist functions to avoid nested localization.
|
|
413
|
+
* @param {string|Var} value - Variable or value to find in the list
|
|
414
|
+
* @param {string|Var} consSubject - Variable or IRI of the list cons cell
|
|
415
|
+
* @param {string|Var} cellVar - Cell variable (required, must be from VarsUnique)
|
|
416
|
+
* @returns {WOQLQuery} Query that succeeds if value is in the list
|
|
417
|
+
* @private
|
|
418
|
+
*/
|
|
419
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
420
|
+
WOQLLibrary.prototype._rdflist_member_raw = function (value, consSubject, cellVar) {
|
|
421
|
+
return new WOQLQuery().and(
|
|
422
|
+
new WOQLQuery().path(consSubject, 'rdf:rest*', cellVar),
|
|
423
|
+
new WOQLQuery().triple(cellVar, 'rdf:first', value),
|
|
424
|
+
);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Traverse an rdf:List and yield each element as a separate binding (streaming).
|
|
429
|
+
* Works in two modes:
|
|
430
|
+
* - Generator: If value is unbound, yields one binding per list element
|
|
431
|
+
* - Matcher: If value is bound, succeeds only if value is a member of the list
|
|
432
|
+
* Ideal for large lists and streaming use cases.
|
|
433
|
+
* @param {string} consSubject - Variable or IRI of the list cons cell (rdf:List head)
|
|
434
|
+
* @param {string} value - Variable to bind each value, or value to check membership
|
|
435
|
+
* @returns {WOQLQuery} Query that yields/matches list elements
|
|
436
|
+
* @example
|
|
437
|
+
* // Stream all tasks from a project's task list (one binding per element)
|
|
438
|
+
* const query = WOQL.and(
|
|
439
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
440
|
+
* WOQL.lib().rdflist_member("v:list_head", "v:task")
|
|
441
|
+
* );
|
|
442
|
+
* // Result: { bindings: [{ "v:task": "Task A" }, { "v:task": "Task B" }, ...] }
|
|
443
|
+
*/
|
|
444
|
+
WOQLLibrary.prototype.rdflist_member = function (consSubject, value) {
|
|
445
|
+
// Include ALL parameters + internal variables in localize spec
|
|
446
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
447
|
+
consSubject,
|
|
448
|
+
value,
|
|
449
|
+
member_cell: null,
|
|
450
|
+
});
|
|
451
|
+
return localized(
|
|
452
|
+
new WOQLQuery().and(
|
|
453
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
|
|
454
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
455
|
+
this._rdflist_member_raw(v.value, v.consSubject, v.member_cell),
|
|
456
|
+
),
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Internal: Get the length of an rdf:List without localization.
|
|
462
|
+
* Used by other rdflist functions to avoid nested localization.
|
|
463
|
+
* @param {string|Var} consSubject - Variable or IRI of the list cons cell
|
|
464
|
+
* @param {string|Var} lengthVar - Variable to bind the length
|
|
465
|
+
* @param {string|Var} pathVar - Path variable (required, must be from VarsUnique)
|
|
466
|
+
* @returns {WOQLQuery} Query that binds list length to lengthVar
|
|
467
|
+
* @private
|
|
468
|
+
*/
|
|
469
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
470
|
+
WOQLLibrary.prototype._rdflist_length_raw = function (consSubject, lengthVar, pathVar) {
|
|
471
|
+
return new WOQLQuery().and(
|
|
472
|
+
new WOQLQuery().triple(consSubject, 'rdf:type', 'rdf:List'),
|
|
473
|
+
new WOQLQuery().path(consSubject, 'rdf:rest*', 'rdf:nil', pathVar),
|
|
474
|
+
new WOQLQuery().length(pathVar, lengthVar),
|
|
475
|
+
);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Get the length of an rdf:List.
|
|
480
|
+
* Uses path query to traverse all rest links and count them.
|
|
481
|
+
* Works as generator (binds length) or matcher (checks if length matches).
|
|
482
|
+
* @param {string} consSubject - Variable or IRI of the list cons cell
|
|
483
|
+
* @param {string} lengthVar - Variable to bind the length
|
|
484
|
+
* @returns {WOQLQuery} Query that binds list length to lengthVar
|
|
485
|
+
* @example
|
|
486
|
+
* // Get the number of tasks in a project's task list
|
|
487
|
+
* const query = WOQL.and(
|
|
488
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
489
|
+
* WOQL.lib().rdflist_length("v:list_head", "v:count")
|
|
490
|
+
* );
|
|
491
|
+
* // Result: { bindings: [{ "v:count": 3 }] }
|
|
492
|
+
*/
|
|
493
|
+
WOQLLibrary.prototype.rdflist_length = function (consSubject, lengthVar) {
|
|
494
|
+
// Include ALL parameters + internal variables in localize spec
|
|
495
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
496
|
+
consSubject,
|
|
497
|
+
lengthVar,
|
|
498
|
+
length_path: null,
|
|
499
|
+
});
|
|
500
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
501
|
+
return localized(this._rdflist_length_raw(v.consSubject, v.lengthVar, v.length_path));
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Pop the first element from an rdf:List in-place.
|
|
506
|
+
* Removes the first element and promotes the second element to head position.
|
|
507
|
+
* The list head IRI remains unchanged - the second cons cell is removed.
|
|
508
|
+
* @param {string} consSubject - Variable or IRI of the list cons cell
|
|
509
|
+
* @param {string} valueVar - Variable to bind the popped value
|
|
510
|
+
* @returns {WOQLQuery} Query that pops first element and binds it to valueVar
|
|
511
|
+
* @example
|
|
512
|
+
* // Pop the first task from a project's task list
|
|
513
|
+
* const query = WOQL.and(
|
|
514
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
515
|
+
* WOQL.lib().rdflist_pop("v:list_head", "v:first_task")
|
|
516
|
+
* );
|
|
517
|
+
* // Result: list [A, B, C] becomes [B, C], binds "A" to v:first_task
|
|
518
|
+
*/
|
|
519
|
+
WOQLLibrary.prototype.rdflist_pop = function (consSubject, valueVar) {
|
|
520
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
521
|
+
consSubject, valueVar, second_cons: null, new_first: null, new_rest: null,
|
|
522
|
+
});
|
|
523
|
+
return localized(
|
|
524
|
+
new WOQLQuery().and(
|
|
525
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:first', v.valueVar),
|
|
526
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.second_cons),
|
|
527
|
+
new WOQLQuery().triple(v.second_cons, 'rdf:first', v.new_first),
|
|
528
|
+
new WOQLQuery().triple(v.second_cons, 'rdf:rest', v.new_rest),
|
|
529
|
+
new WOQLQuery().delete_triple(v.second_cons, 'rdf:type', 'rdf:List'),
|
|
530
|
+
new WOQLQuery().delete_triple(v.second_cons, 'rdf:first', v.new_first),
|
|
531
|
+
new WOQLQuery().delete_triple(v.second_cons, 'rdf:rest', v.new_rest),
|
|
532
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.valueVar),
|
|
533
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.second_cons),
|
|
534
|
+
new WOQLQuery().add_triple(v.consSubject, 'rdf:first', v.new_first),
|
|
535
|
+
new WOQLQuery().add_triple(v.consSubject, 'rdf:rest', v.new_rest),
|
|
536
|
+
),
|
|
537
|
+
);
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Push a new element to the front of an rdf:List in-place.
|
|
542
|
+
* Modifies the existing cons cell: moves old first value to a new cons cell,
|
|
543
|
+
* sets new value as rdf:first, and links to the new cons as rdf:rest.
|
|
544
|
+
* The list head IRI remains unchanged.
|
|
545
|
+
* @param {string|Var} consSubject - Variable or IRI of the list cons cell
|
|
546
|
+
* @param {string|Var} value - Value to push to the front
|
|
547
|
+
* @returns {WOQLQuery} Query that pushes value to front of list in-place
|
|
548
|
+
* @example
|
|
549
|
+
* // Push a new task to the front of a project's task list
|
|
550
|
+
* const query = WOQL.and(
|
|
551
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
552
|
+
* WOQL.lib().rdflist_push("v:list_head", WOQL.string("New Task"))
|
|
553
|
+
* );
|
|
554
|
+
* // Result: list [A, B, C] becomes [New Task, A, B, C], head IRI unchanged
|
|
555
|
+
*/
|
|
556
|
+
WOQLLibrary.prototype.rdflist_push = function (consSubject, value) {
|
|
557
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
558
|
+
consSubject, value, old_first: null, old_rest: null, new_cons: null,
|
|
559
|
+
});
|
|
560
|
+
return localized(
|
|
561
|
+
new WOQLQuery().and(
|
|
562
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:first', v.old_first),
|
|
563
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.old_rest),
|
|
564
|
+
new WOQLQuery().idgen_random('terminusdb://data/Cons/', v.new_cons),
|
|
565
|
+
new WOQLQuery().add_triple(v.new_cons, 'rdf:type', 'rdf:List'),
|
|
566
|
+
new WOQLQuery().add_triple(v.new_cons, 'rdf:first', v.old_first),
|
|
567
|
+
new WOQLQuery().add_triple(v.new_cons, 'rdf:rest', v.old_rest),
|
|
568
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.old_first),
|
|
569
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.old_rest),
|
|
570
|
+
new WOQLQuery().add_triple(v.consSubject, 'rdf:first', v.value),
|
|
571
|
+
new WOQLQuery().add_triple(v.consSubject, 'rdf:rest', v.new_cons),
|
|
572
|
+
),
|
|
573
|
+
);
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Append an element to the end of an rdf:List.
|
|
578
|
+
* Traverses to the last cell and adds new cell there.
|
|
579
|
+
* @param {string|Var} consSubject - Variable or IRI of the list head
|
|
580
|
+
* @param {string|Var} value - Value to append
|
|
581
|
+
* @param {string} [newCellVar] - Optional variable to bind the new cell
|
|
582
|
+
* @returns {WOQLQuery} Query that appends value to end of list
|
|
583
|
+
* @example
|
|
584
|
+
* // Append a new task to the end of a project's task list
|
|
585
|
+
* const query = WOQL.and(
|
|
586
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
587
|
+
* WOQL.lib().rdflist_append("v:list_head", WOQL.string("New Task"))
|
|
588
|
+
* );
|
|
589
|
+
* // Result: appends "New Task" to end of list
|
|
590
|
+
*/
|
|
591
|
+
WOQLLibrary.prototype.rdflist_append = function (consSubject, value, newCell = 'v:_append_new_cell') {
|
|
592
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
593
|
+
consSubject, value, newCell, last_cell: null,
|
|
594
|
+
});
|
|
595
|
+
return localized(
|
|
596
|
+
new WOQLQuery().and(
|
|
597
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
|
|
598
|
+
new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.last_cell),
|
|
599
|
+
new WOQLQuery().triple(v.last_cell, 'rdf:rest', 'rdf:nil'),
|
|
600
|
+
new WOQLQuery().idgen_random('terminusdb://data/Cons/', v.newCell),
|
|
601
|
+
new WOQLQuery().delete_triple(v.last_cell, 'rdf:rest', 'rdf:nil'),
|
|
602
|
+
new WOQLQuery().add_triple(v.last_cell, 'rdf:rest', v.newCell),
|
|
603
|
+
new WOQLQuery().add_triple(v.newCell, 'rdf:type', 'rdf:List'),
|
|
604
|
+
new WOQLQuery().add_triple(v.newCell, 'rdf:first', v.value),
|
|
605
|
+
new WOQLQuery().add_triple(v.newCell, 'rdf:rest', 'rdf:nil'),
|
|
606
|
+
),
|
|
607
|
+
);
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Delete all cons cells of an rdf:List and return rdf:nil as the new list value.
|
|
612
|
+
* Since a cons cell cannot become rdf:nil, this deletes all cells and binds
|
|
613
|
+
* rdf:nil to a variable so the caller can update the reference to the list.
|
|
614
|
+
* NOTE: This only removes the list structure. To delete subdocuments
|
|
615
|
+
* referenced by the list, use rdflist_member with delete_document first.
|
|
616
|
+
* @param {string} consSubject - Variable or IRI of the list head
|
|
617
|
+
* @param {string} newListVar - Variable to bind rdf:nil (the empty list)
|
|
618
|
+
* @returns {WOQLQuery} Query that deletes all cons cells and binds rdf:nil
|
|
619
|
+
* @example
|
|
620
|
+
* // Clear a list of literals and update the reference to point to rdf:nil:
|
|
621
|
+
* const clearListQuery = WOQL.and(
|
|
622
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:old_list"),
|
|
623
|
+
* WOQL.lib().rdflist_clear("v:old_list", "v:empty_list"),
|
|
624
|
+
* WOQL.delete_triple("doc:Project/1", "tasks", "v:old_list"),
|
|
625
|
+
* WOQL.add_triple("doc:Project/1", "tasks", "v:empty_list")
|
|
626
|
+
* );
|
|
627
|
+
*
|
|
628
|
+
* // Full pattern: delete subdocuments, clear list, update reference:
|
|
629
|
+
* const fullClearQuery = WOQL.and(
|
|
630
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:old_list"),
|
|
631
|
+
* WOQL.group_by([], "v:subdoc", "v:members",
|
|
632
|
+
* WOQL.and(
|
|
633
|
+
* WOQL.lib().rdflist_member("v:old_list", "v:subdoc"),
|
|
634
|
+
* WOQL.delete_document("v:subdoc")
|
|
635
|
+
* )
|
|
636
|
+
* ),
|
|
637
|
+
* WOQL.lib().rdflist_clear("v:old_list", "v:empty_list"),
|
|
638
|
+
* WOQL.delete_triple("doc:Project/1", "tasks", "v:old_list"),
|
|
639
|
+
* WOQL.add_triple("doc:Project/1", "tasks", "v:empty_list")
|
|
640
|
+
* );
|
|
641
|
+
*/
|
|
642
|
+
WOQLLibrary.prototype.rdflist_clear = function (consSubject, newListVar) {
|
|
643
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
644
|
+
consSubject,
|
|
645
|
+
newListVar,
|
|
646
|
+
cell: null,
|
|
647
|
+
cell_first: null,
|
|
648
|
+
cell_rest: null,
|
|
649
|
+
head_first: null,
|
|
650
|
+
head_rest: null,
|
|
651
|
+
});
|
|
652
|
+
return localized(
|
|
653
|
+
new WOQLQuery().and(
|
|
654
|
+
// Bind the new list value (rdf:nil = empty list)
|
|
655
|
+
new WOQLQuery().equals(v.newListVar, 'rdf:nil'),
|
|
656
|
+
// Delete all triples from tail cons cells (those reachable via rdf:rest+)
|
|
657
|
+
new WOQLQuery().optional(
|
|
658
|
+
new WOQLQuery().and(
|
|
659
|
+
new WOQLQuery().path(v.consSubject, 'rdf:rest+', v.cell),
|
|
660
|
+
new WOQLQuery().triple(v.cell, 'rdf:type', 'rdf:List'),
|
|
661
|
+
new WOQLQuery().triple(v.cell, 'rdf:first', v.cell_first),
|
|
662
|
+
new WOQLQuery().triple(v.cell, 'rdf:rest', v.cell_rest),
|
|
663
|
+
new WOQLQuery().delete_triple(v.cell, 'rdf:type', 'rdf:List'),
|
|
664
|
+
new WOQLQuery().delete_triple(v.cell, 'rdf:first', v.cell_first),
|
|
665
|
+
new WOQLQuery().delete_triple(v.cell, 'rdf:rest', v.cell_rest),
|
|
666
|
+
),
|
|
667
|
+
),
|
|
668
|
+
// Delete the head cons cell completely
|
|
669
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
|
|
670
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:first', v.head_first),
|
|
671
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.head_rest),
|
|
672
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:type', 'rdf:List'),
|
|
673
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.head_first),
|
|
674
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.head_rest),
|
|
675
|
+
),
|
|
676
|
+
);
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Insert a value at a specific position in an rdf:List.
|
|
681
|
+
* @param {string} consSubject - Variable or IRI of the list head
|
|
682
|
+
* @param {number} position - Position to insert at (0-indexed)
|
|
683
|
+
* @param {string|Var} value - Value to insert
|
|
684
|
+
* @param {string|Var} [newNodeVar] - Optional variable to bind the new node
|
|
685
|
+
* @returns {WOQLQuery} Query that inserts value at position
|
|
686
|
+
* @example
|
|
687
|
+
* // Insert a task at position 1 in a project's task list
|
|
688
|
+
* const query = WOQL.and(
|
|
689
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
690
|
+
* WOQL.lib().rdflist_insert("v:list_head", 1, WOQL.string("Inserted Task"))
|
|
691
|
+
* );
|
|
692
|
+
* // Result: [A, Inserted Task, B, C]
|
|
693
|
+
*/
|
|
694
|
+
WOQLLibrary.prototype.rdflist_insert = function (consSubject, position, value, newNodeVar = 'v:_insert_new_node') {
|
|
695
|
+
if (position < 0) {
|
|
696
|
+
throw new Error('rdflist_insert requires position >= 0.');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (position === 0) {
|
|
700
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
701
|
+
consSubject, value, newNodeVar, old_first: null, old_rest: null,
|
|
702
|
+
});
|
|
703
|
+
return localized(
|
|
704
|
+
new WOQLQuery().and(
|
|
705
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:first', v.old_first),
|
|
706
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.old_rest),
|
|
707
|
+
new WOQLQuery().idgen('list_node', [v.old_first, v.consSubject], v.newNodeVar),
|
|
708
|
+
new WOQLQuery().add_triple(v.newNodeVar, 'rdf:type', 'rdf:List'),
|
|
709
|
+
new WOQLQuery().add_triple(v.newNodeVar, 'rdf:first', v.old_first),
|
|
710
|
+
new WOQLQuery().add_triple(v.newNodeVar, 'rdf:rest', v.old_rest),
|
|
711
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.old_first),
|
|
712
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.old_rest),
|
|
713
|
+
new WOQLQuery().add_triple(v.consSubject, 'rdf:first', v.value),
|
|
714
|
+
new WOQLQuery().add_triple(v.consSubject, 'rdf:rest', v.newNodeVar),
|
|
715
|
+
),
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
720
|
+
consSubject, value, newNodeVar, pred_node: null, old_rest: null,
|
|
721
|
+
});
|
|
722
|
+
const restCount = position - 1;
|
|
723
|
+
const pathPattern = restCount === 0 ? '' : `rdf:rest{${restCount},${restCount}}`;
|
|
724
|
+
|
|
725
|
+
const findPredecessor = restCount === 0
|
|
726
|
+
? new WOQLQuery().eq(v.pred_node, v.consSubject)
|
|
727
|
+
: new WOQLQuery().path(v.consSubject, pathPattern, v.pred_node);
|
|
728
|
+
|
|
729
|
+
return localized(
|
|
730
|
+
new WOQLQuery().and(
|
|
731
|
+
findPredecessor,
|
|
732
|
+
new WOQLQuery().triple(v.pred_node, 'rdf:rest', v.old_rest),
|
|
733
|
+
new WOQLQuery().idgen('list_node', [v.value, v.pred_node], v.newNodeVar),
|
|
734
|
+
new WOQLQuery().delete_triple(v.pred_node, 'rdf:rest', v.old_rest),
|
|
735
|
+
new WOQLQuery().add_triple(v.pred_node, 'rdf:rest', v.newNodeVar),
|
|
736
|
+
new WOQLQuery().add_triple(v.newNodeVar, 'rdf:type', 'rdf:List'),
|
|
737
|
+
new WOQLQuery().add_triple(v.newNodeVar, 'rdf:first', v.value),
|
|
738
|
+
new WOQLQuery().add_triple(v.newNodeVar, 'rdf:rest', v.old_rest),
|
|
739
|
+
),
|
|
740
|
+
);
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Drop/remove a single element from an rdf:List at a specific position.
|
|
745
|
+
* @param {string} consSubject - Variable or IRI of the list head
|
|
746
|
+
* @param {number} position - Position of element to remove (0-indexed)
|
|
747
|
+
* @returns {WOQLQuery} Query that removes the element at position
|
|
748
|
+
* @example
|
|
749
|
+
* // Remove the task at position 1 from a project's task list
|
|
750
|
+
* const query = WOQL.and(
|
|
751
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
752
|
+
* WOQL.lib().rdflist_drop("v:list_head", 1)
|
|
753
|
+
* );
|
|
754
|
+
* // Result: [A, C] (B removed)
|
|
755
|
+
*/
|
|
756
|
+
WOQLLibrary.prototype.rdflist_drop = function (consSubject, position) {
|
|
757
|
+
if (position < 0) {
|
|
758
|
+
throw new Error('rdflist_drop requires position >= 0.');
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (position === 0) {
|
|
762
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
763
|
+
consSubject, old_first: null, rest_node: null, next_first: null, next_rest: null,
|
|
764
|
+
});
|
|
765
|
+
return localized(
|
|
766
|
+
new WOQLQuery().and(
|
|
767
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:first', v.old_first),
|
|
768
|
+
new WOQLQuery().triple(v.consSubject, 'rdf:rest', v.rest_node),
|
|
769
|
+
new WOQLQuery().triple(v.rest_node, 'rdf:first', v.next_first),
|
|
770
|
+
new WOQLQuery().triple(v.rest_node, 'rdf:rest', v.next_rest),
|
|
771
|
+
new WOQLQuery().delete_triple(v.rest_node, 'rdf:type', 'rdf:List'),
|
|
772
|
+
new WOQLQuery().delete_triple(v.rest_node, 'rdf:first', v.next_first),
|
|
773
|
+
new WOQLQuery().delete_triple(v.rest_node, 'rdf:rest', v.next_rest),
|
|
774
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:first', v.old_first),
|
|
775
|
+
new WOQLQuery().delete_triple(v.consSubject, 'rdf:rest', v.rest_node),
|
|
776
|
+
new WOQLQuery().add_triple(v.consSubject, 'rdf:first', v.next_first),
|
|
777
|
+
new WOQLQuery().add_triple(v.consSubject, 'rdf:rest', v.next_rest),
|
|
778
|
+
),
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
783
|
+
consSubject, pred_node: null, drop_node: null, drop_first: null, drop_rest: null,
|
|
784
|
+
});
|
|
785
|
+
const restCount = position - 1;
|
|
786
|
+
const pathPattern = restCount === 0 ? '' : `rdf:rest{${restCount},${restCount}}`;
|
|
787
|
+
|
|
788
|
+
const findPredecessor = restCount === 0
|
|
789
|
+
? new WOQLQuery().eq(v.pred_node, v.consSubject)
|
|
790
|
+
: new WOQLQuery().path(v.consSubject, pathPattern, v.pred_node);
|
|
791
|
+
|
|
792
|
+
return localized(
|
|
793
|
+
new WOQLQuery().and(
|
|
794
|
+
findPredecessor,
|
|
795
|
+
new WOQLQuery().triple(v.pred_node, 'rdf:rest', v.drop_node),
|
|
796
|
+
new WOQLQuery().triple(v.drop_node, 'rdf:first', v.drop_first),
|
|
797
|
+
new WOQLQuery().triple(v.drop_node, 'rdf:rest', v.drop_rest),
|
|
798
|
+
new WOQLQuery().delete_triple(v.drop_node, 'rdf:type', 'rdf:List'),
|
|
799
|
+
new WOQLQuery().delete_triple(v.drop_node, 'rdf:first', v.drop_first),
|
|
800
|
+
new WOQLQuery().delete_triple(v.drop_node, 'rdf:rest', v.drop_rest),
|
|
801
|
+
new WOQLQuery().delete_triple(v.pred_node, 'rdf:rest', v.drop_node),
|
|
802
|
+
new WOQLQuery().add_triple(v.pred_node, 'rdf:rest', v.drop_rest),
|
|
803
|
+
),
|
|
804
|
+
);
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Swap elements at two positions in an rdf:List by exchanging their rdf:first values.
|
|
809
|
+
* This is a simplified implementation that swaps values in-place without moving cons cells.
|
|
810
|
+
* @param {string} consSubject - Variable or IRI of the list head
|
|
811
|
+
* @param {number} posA - First position (0-indexed)
|
|
812
|
+
* @param {number} posB - Second position (0-indexed)
|
|
813
|
+
* @returns {WOQLQuery} Query that swaps elements at the two positions
|
|
814
|
+
* @example
|
|
815
|
+
* // Swap the first and third tasks in a project's task list
|
|
816
|
+
* const query = WOQL.and(
|
|
817
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
818
|
+
* WOQL.lib().rdflist_swap("v:list_head", 0, 2)
|
|
819
|
+
* );
|
|
820
|
+
* // Result: [C, B, A] (A and C swapped)
|
|
821
|
+
*/
|
|
822
|
+
WOQLLibrary.prototype.rdflist_swap = function (consSubject, posA, posB) {
|
|
823
|
+
if (posA < 0 || posB < 0) {
|
|
824
|
+
throw new Error('rdflist_swap requires positions >= 0.');
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (posA === posB) {
|
|
828
|
+
const [localized, v] = new WOQLQuery().localize({ consSubject });
|
|
829
|
+
return localized(new WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
833
|
+
consSubject, node_a: null, node_b: null, value_a: null, value_b: null,
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
// Find node at posA
|
|
837
|
+
const findNodeA = posA === 0
|
|
838
|
+
? new WOQLQuery().eq(v.node_a, v.consSubject)
|
|
839
|
+
: new WOQLQuery().path(v.consSubject, `rdf:rest{${posA},${posA}}`, v.node_a);
|
|
840
|
+
|
|
841
|
+
// Find node at posB
|
|
842
|
+
const findNodeB = posB === 0
|
|
843
|
+
? new WOQLQuery().eq(v.node_b, v.consSubject)
|
|
844
|
+
: new WOQLQuery().path(v.consSubject, `rdf:rest{${posB},${posB}}`, v.node_b);
|
|
845
|
+
|
|
846
|
+
return localized(
|
|
847
|
+
new WOQLQuery().and(
|
|
848
|
+
findNodeA,
|
|
849
|
+
findNodeB,
|
|
850
|
+
new WOQLQuery().triple(v.node_a, 'rdf:first', v.value_a),
|
|
851
|
+
new WOQLQuery().triple(v.node_b, 'rdf:first', v.value_b),
|
|
852
|
+
new WOQLQuery().delete_triple(v.node_a, 'rdf:first', v.value_a),
|
|
853
|
+
new WOQLQuery().delete_triple(v.node_b, 'rdf:first', v.value_b),
|
|
854
|
+
new WOQLQuery().add_triple(v.node_a, 'rdf:first', v.value_b),
|
|
855
|
+
new WOQLQuery().add_triple(v.node_b, 'rdf:first', v.value_a),
|
|
856
|
+
),
|
|
857
|
+
);
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Create an empty rdf:List (just rdf:nil).
|
|
862
|
+
* @param {string} listVar - Variable to bind to rdf:nil
|
|
863
|
+
* @returns {WOQLQuery} Query that creates empty list
|
|
864
|
+
* @example
|
|
865
|
+
* // Create an empty list reference
|
|
866
|
+
* const query = WOQL.lib().rdflist_empty("v:empty_list");
|
|
867
|
+
* // Result: { bindings: [{ "v:empty_list": "rdf:nil" }] }
|
|
868
|
+
*/
|
|
869
|
+
WOQLLibrary.prototype.rdflist_empty = function (listVar) {
|
|
870
|
+
return new WOQLQuery().eq(listVar, 'rdf:nil');
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Check if an rdf:List is empty (equals rdf:nil).
|
|
875
|
+
* @param {string} consSubject - Variable or IRI to check
|
|
876
|
+
* @returns {WOQLQuery} Query that succeeds if list is empty
|
|
877
|
+
* @example
|
|
878
|
+
* // Check if a project's task list is empty
|
|
879
|
+
* const query = WOQL.and(
|
|
880
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
881
|
+
* WOQL.lib().rdflist_is_empty("v:list_head")
|
|
882
|
+
* );
|
|
883
|
+
* // Succeeds only if tasks list is rdf:nil
|
|
884
|
+
*/
|
|
885
|
+
WOQLLibrary.prototype.rdflist_is_empty = function (consSubject) {
|
|
886
|
+
return new WOQLQuery().eq(consSubject, 'rdf:nil');
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Extract a slice of an rdf:List (range of elements) as an array.
|
|
891
|
+
* Works in two modes:
|
|
892
|
+
* - Generator: If resultVar is unbound, binds it to the array of sliced values
|
|
893
|
+
* - Matcher: If resultVar is bound to an array, succeeds only if values match
|
|
894
|
+
* Path queries return results in hop-count order, preserving list element order.
|
|
895
|
+
* @param {string} consSubject - Variable or IRI of the list head
|
|
896
|
+
* @param {number|string} start - Start index (inclusive) or variable
|
|
897
|
+
* @param {number|string} end - End index (exclusive) or variable
|
|
898
|
+
* @param {string} resultVar - Variable to bind/match the array of sliced values
|
|
899
|
+
* @returns {WOQLQuery} Query that extracts/matches list slice values
|
|
900
|
+
* @example
|
|
901
|
+
* // Get the first two tasks from a project's task list
|
|
902
|
+
* const query = WOQL.and(
|
|
903
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
904
|
+
* WOQL.lib().rdflist_slice("v:list_head", 0, 2, "v:first_two")
|
|
905
|
+
* );
|
|
906
|
+
* // Result: { bindings: [{ "v:first_two": ["Task A", "Task B"] }] }
|
|
907
|
+
*/
|
|
908
|
+
WOQLLibrary.prototype.rdflist_slice = function (consSubject, start, end, resultVar) {
|
|
909
|
+
if (start < 0 || end < 0) {
|
|
910
|
+
throw new Error('rdflist_slice: negative indices not supported');
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (start >= end) {
|
|
914
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
915
|
+
consSubject,
|
|
916
|
+
resultVar,
|
|
917
|
+
});
|
|
918
|
+
return localized(new WOQLQuery().eq(v.resultVar, []));
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
922
|
+
consSubject, resultVar, node: null, value: null,
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
let findNodes;
|
|
926
|
+
if (start === 0 && end === 1) {
|
|
927
|
+
findNodes = new WOQLQuery().eq(v.node, v.consSubject);
|
|
928
|
+
} else if (start === 0) {
|
|
929
|
+
findNodes = new WOQLQuery().path(v.consSubject, `rdf:rest{0,${end - 1}}`, v.node);
|
|
930
|
+
} else {
|
|
931
|
+
findNodes = new WOQLQuery().path(v.consSubject, `rdf:rest{${start},${end - 1}}`, v.node);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
return localized(
|
|
935
|
+
new WOQLQuery().group_by(
|
|
936
|
+
[],
|
|
937
|
+
[v.value],
|
|
938
|
+
v.resultVar,
|
|
939
|
+
new WOQLQuery().and(
|
|
940
|
+
findNodes,
|
|
941
|
+
new WOQLQuery().triple(v.node, 'rdf:first', v.value),
|
|
942
|
+
),
|
|
943
|
+
),
|
|
944
|
+
);
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Reverse an rdf:List in-place by swapping rdf:first values across nodes.
|
|
949
|
+
* Optimized: collects all values once via group_by, then uses slice+member
|
|
950
|
+
* to index into the collected list instead of double path traversal.
|
|
951
|
+
* @param {string} consSubject - Variable or IRI of the list to reverse
|
|
952
|
+
* @returns {WOQLQuery} Query that reverses the list in place
|
|
953
|
+
* @example
|
|
954
|
+
* // Reverse a project's task list in place
|
|
955
|
+
* const query = WOQL.and(
|
|
956
|
+
* WOQL.triple("doc:Project/1", "tasks", "v:list_head"),
|
|
957
|
+
* WOQL.lib().rdflist_reverse("v:list_head")
|
|
958
|
+
* );
|
|
959
|
+
* // Result: [A, B, C] becomes [C, B, A]
|
|
960
|
+
*/
|
|
961
|
+
WOQLLibrary.prototype.rdflist_reverse = function (consSubject) {
|
|
962
|
+
const [localized, v] = new WOQLQuery().localize({
|
|
963
|
+
consSubject,
|
|
964
|
+
node: null,
|
|
965
|
+
path: null,
|
|
966
|
+
old_value: null,
|
|
967
|
+
new_value: null,
|
|
968
|
+
pos: null,
|
|
969
|
+
len: null,
|
|
970
|
+
len_path: null,
|
|
971
|
+
all_values: null,
|
|
972
|
+
rev_pos: null,
|
|
973
|
+
rev_pos_plus1: null,
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
// Generate unique local variable name for slice result (using timestamp for uniqueness)
|
|
977
|
+
const sliceResultVar = `v:_slice_result_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
978
|
+
|
|
979
|
+
return localized(
|
|
980
|
+
new WOQLQuery().and(
|
|
981
|
+
// Get list length
|
|
982
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
983
|
+
this._rdflist_length_raw(v.consSubject, v.len, v.len_path),
|
|
984
|
+
// Collect all values into a list (single path traversal)
|
|
985
|
+
new WOQLQuery().group_by(
|
|
986
|
+
[],
|
|
987
|
+
'_rev_collect_value',
|
|
988
|
+
v.all_values,
|
|
989
|
+
new WOQLQuery().and(
|
|
990
|
+
new WOQLQuery().path(v.consSubject, 'rdf:rest*', 'v:_rev_collect_node'),
|
|
991
|
+
new WOQLQuery().triple('v:_rev_collect_node', 'rdf:first', 'v:_rev_collect_value'),
|
|
992
|
+
),
|
|
993
|
+
),
|
|
994
|
+
// For each node, get its position and current value
|
|
995
|
+
new WOQLQuery().path(v.consSubject, 'rdf:rest*', v.node, v.path),
|
|
996
|
+
new WOQLQuery().length(v.path, v.pos),
|
|
997
|
+
new WOQLQuery().triple(v.node, 'rdf:first', v.old_value),
|
|
998
|
+
// Calculate reversed position: revPos = (len - 1) - pos
|
|
999
|
+
new WOQLQuery().eval(
|
|
1000
|
+
new WOQLQuery().minus(new WOQLQuery().minus(v.len, 1), v.pos),
|
|
1001
|
+
v.rev_pos,
|
|
1002
|
+
),
|
|
1003
|
+
// Get value at reversed position: slice to single-element array, then extract with member
|
|
1004
|
+
new WOQLQuery().eval(new WOQLQuery().plus(v.rev_pos, 1), v.rev_pos_plus1),
|
|
1005
|
+
new WOQLQuery().slice(v.all_values, sliceResultVar, v.rev_pos, v.rev_pos_plus1),
|
|
1006
|
+
// Use member() on WOQL array (slice returns JavaScript array, not RDF list)
|
|
1007
|
+
new WOQLQuery().member(v.new_value, sliceResultVar),
|
|
1008
|
+
// Rewrite: delete old rdf:first, add new rdf:first
|
|
1009
|
+
new WOQLQuery().delete_triple(v.node, 'rdf:first', v.old_value),
|
|
1010
|
+
new WOQLQuery().add_triple(v.node, 'rdf:first', v.new_value),
|
|
1011
|
+
),
|
|
1012
|
+
);
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
module.exports = WOQLLibrary;
|