@joystick.js/db-canary 0.0.0-canary.2251 → 0.0.0-canary.2253
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/dist/client/database.js +1 -1
- package/dist/client/index.js +1 -1
- package/dist/server/cluster/master.js +4 -4
- package/dist/server/cluster/worker.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/lib/auto_index_manager.js +1 -1
- package/dist/server/lib/backup_manager.js +1 -1
- package/dist/server/lib/index_manager.js +1 -1
- package/dist/server/lib/operation_dispatcher.js +1 -1
- package/dist/server/lib/operations/admin.js +1 -1
- package/dist/server/lib/operations/bulk_write.js +1 -1
- package/dist/server/lib/operations/create_index.js +1 -1
- package/dist/server/lib/operations/delete_many.js +1 -1
- package/dist/server/lib/operations/delete_one.js +1 -1
- package/dist/server/lib/operations/find.js +1 -1
- package/dist/server/lib/operations/find_one.js +1 -1
- package/dist/server/lib/operations/insert_one.js +1 -1
- package/dist/server/lib/operations/update_one.js +1 -1
- package/dist/server/lib/send_response.js +1 -1
- package/dist/server/lib/tcp_protocol.js +1 -1
- package/package.json +2 -2
- package/src/client/database.js +159 -133
- package/src/client/index.js +285 -346
- package/src/server/cluster/master.js +265 -156
- package/src/server/cluster/worker.js +26 -18
- package/src/server/index.js +553 -330
- package/src/server/lib/auto_index_manager.js +85 -23
- package/src/server/lib/backup_manager.js +117 -70
- package/src/server/lib/index_manager.js +63 -25
- package/src/server/lib/operation_dispatcher.js +339 -168
- package/src/server/lib/operations/admin.js +343 -205
- package/src/server/lib/operations/bulk_write.js +458 -194
- package/src/server/lib/operations/create_index.js +127 -34
- package/src/server/lib/operations/delete_many.js +204 -67
- package/src/server/lib/operations/delete_one.js +164 -52
- package/src/server/lib/operations/find.js +552 -319
- package/src/server/lib/operations/find_one.js +530 -304
- package/src/server/lib/operations/insert_one.js +147 -52
- package/src/server/lib/operations/update_one.js +334 -93
- package/src/server/lib/send_response.js +37 -17
- package/src/server/lib/tcp_protocol.js +158 -53
- package/test_data_api_key_1758233848259_cglfjzhou/data.mdb +0 -0
- package/test_data_api_key_1758233848259_cglfjzhou/lock.mdb +0 -0
- package/test_data_api_key_1758233848502_urlje2utd/data.mdb +0 -0
- package/test_data_api_key_1758233848502_urlje2utd/lock.mdb +0 -0
- package/test_data_api_key_1758233848738_mtcpfe5ns/data.mdb +0 -0
- package/test_data_api_key_1758233848738_mtcpfe5ns/lock.mdb +0 -0
- package/test_data_api_key_1758233848856_9g97p6gag/data.mdb +0 -0
- package/test_data_api_key_1758233848856_9g97p6gag/lock.mdb +0 -0
- package/test_data_api_key_1758233857008_0tl9zzhj8/data.mdb +0 -0
- package/test_data_api_key_1758233857008_0tl9zzhj8/lock.mdb +0 -0
- package/test_data_api_key_1758233857120_60c2f2uhu/data.mdb +0 -0
- package/test_data_api_key_1758233857120_60c2f2uhu/lock.mdb +0 -0
- package/test_data_api_key_1758233857232_aw7fkqgd9/data.mdb +0 -0
- package/test_data_api_key_1758233857232_aw7fkqgd9/lock.mdb +0 -0
- package/test_data_api_key_1758234881285_4aeflubjb/data.mdb +0 -0
- package/test_data_api_key_1758234881285_4aeflubjb/lock.mdb +0 -0
- package/test_data_api_key_1758234881520_kb0amvtqb/data.mdb +0 -0
- package/test_data_api_key_1758234881520_kb0amvtqb/lock.mdb +0 -0
- package/test_data_api_key_1758234881756_k04gfv2va/data.mdb +0 -0
- package/test_data_api_key_1758234881756_k04gfv2va/lock.mdb +0 -0
- package/test_data_api_key_1758234881876_wn90dpo1z/data.mdb +0 -0
- package/test_data_api_key_1758234881876_wn90dpo1z/lock.mdb +0 -0
- package/test_data_api_key_1758234889461_26xz3dmbr/data.mdb +0 -0
- package/test_data_api_key_1758234889461_26xz3dmbr/lock.mdb +0 -0
- package/test_data_api_key_1758234889572_uziz7e0p5/data.mdb +0 -0
- package/test_data_api_key_1758234889572_uziz7e0p5/lock.mdb +0 -0
- package/test_data_api_key_1758234889684_5f9wmposh/data.mdb +0 -0
- package/test_data_api_key_1758234889684_5f9wmposh/lock.mdb +0 -0
- package/test_data_api_key_1758235657729_prwgm6mxr/data.mdb +0 -0
- package/test_data_api_key_1758235657729_prwgm6mxr/lock.mdb +0 -0
- package/test_data_api_key_1758235657961_rc2da0dc2/data.mdb +0 -0
- package/test_data_api_key_1758235657961_rc2da0dc2/lock.mdb +0 -0
- package/test_data_api_key_1758235658193_oqqxm0sny/data.mdb +0 -0
- package/test_data_api_key_1758235658193_oqqxm0sny/lock.mdb +0 -0
- package/test_data_api_key_1758235658309_vggac1pj6/data.mdb +0 -0
- package/test_data_api_key_1758235658309_vggac1pj6/lock.mdb +0 -0
- package/test_data_api_key_1758235665968_61ko07dd1/data.mdb +0 -0
- package/test_data_api_key_1758235665968_61ko07dd1/lock.mdb +0 -0
- package/test_data_api_key_1758235666082_50lrt6sq8/data.mdb +0 -0
- package/test_data_api_key_1758235666082_50lrt6sq8/lock.mdb +0 -0
- package/test_data_api_key_1758235666194_ykvauwlzh/data.mdb +0 -0
- package/test_data_api_key_1758235666194_ykvauwlzh/lock.mdb +0 -0
- package/test_data_api_key_1758236187207_9c4paeh09/data.mdb +0 -0
- package/test_data_api_key_1758236187207_9c4paeh09/lock.mdb +0 -0
- package/test_data_api_key_1758236187441_4n3o3gkkl/data.mdb +0 -0
- package/test_data_api_key_1758236187441_4n3o3gkkl/lock.mdb +0 -0
- package/test_data_api_key_1758236187672_jt6b21ye0/data.mdb +0 -0
- package/test_data_api_key_1758236187672_jt6b21ye0/lock.mdb +0 -0
- package/test_data_api_key_1758236187788_oo84fz9u6/data.mdb +0 -0
- package/test_data_api_key_1758236187788_oo84fz9u6/lock.mdb +0 -0
- package/test_data_api_key_1758236195507_o9zeznwlm/data.mdb +0 -0
- package/test_data_api_key_1758236195507_o9zeznwlm/lock.mdb +0 -0
- package/test_data_api_key_1758236195619_qsqd60y41/data.mdb +0 -0
- package/test_data_api_key_1758236195619_qsqd60y41/lock.mdb +0 -0
- package/test_data_api_key_1758236195731_im13iq284/data.mdb +0 -0
- package/test_data_api_key_1758236195731_im13iq284/lock.mdb +0 -0
|
@@ -11,72 +11,105 @@ import create_logger from '../logger.js';
|
|
|
11
11
|
|
|
12
12
|
const { create_context_logger } = create_logger('find');
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Splits field path into parts.
|
|
16
|
+
* @param {string} field_path - Dot-separated field path
|
|
17
|
+
* @returns {Array<string>} Array of field parts
|
|
18
|
+
*/
|
|
19
|
+
const split_field_path = (field_path) => {
|
|
20
|
+
return field_path.split('.');
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Checks if value is null or undefined.
|
|
25
|
+
* @param {any} value - Value to check
|
|
26
|
+
* @returns {boolean} True if null or undefined
|
|
27
|
+
*/
|
|
28
|
+
const is_null_or_undefined = (value) => {
|
|
29
|
+
return value === null || value === undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Collects nested values from array elements.
|
|
34
|
+
* @param {Array} array_value - Array to process
|
|
35
|
+
* @param {string} remaining_path - Remaining field path
|
|
36
|
+
* @returns {Array|undefined} Collected nested values or undefined
|
|
37
|
+
*/
|
|
38
|
+
const collect_nested_values_from_array = (array_value, remaining_path) => {
|
|
39
|
+
const nested_values = [];
|
|
40
|
+
|
|
41
|
+
for (let j = 0; j < array_value.length; j++) {
|
|
42
|
+
const item = array_value[j];
|
|
43
|
+
if (typeof item === 'object' && item !== null) {
|
|
44
|
+
const nested_value = get_field_value(item, remaining_path);
|
|
45
|
+
if (nested_value !== undefined) {
|
|
46
|
+
if (Array.isArray(nested_value)) {
|
|
47
|
+
nested_values.push(...nested_value);
|
|
48
|
+
} else {
|
|
49
|
+
nested_values.push(nested_value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return nested_values.length > 0 ? nested_values : undefined;
|
|
56
|
+
};
|
|
57
|
+
|
|
14
58
|
/**
|
|
15
59
|
* Extracts field value from document using dot notation path.
|
|
16
|
-
* Handles nested array object queries like 'reviews.rating' where reviews is an array of objects.
|
|
17
60
|
* @param {Object} document - Document to extract value from
|
|
18
|
-
* @param {string} field_path - Dot-separated field path
|
|
19
|
-
* @returns {
|
|
61
|
+
* @param {string} field_path - Dot-separated field path
|
|
62
|
+
* @returns {any} Field value or undefined if path doesn't exist
|
|
20
63
|
*/
|
|
21
64
|
const get_field_value = (document, field_path) => {
|
|
22
|
-
const parts = field_path
|
|
65
|
+
const parts = split_field_path(field_path);
|
|
23
66
|
let value = document;
|
|
24
67
|
|
|
25
68
|
for (let i = 0; i < parts.length; i++) {
|
|
26
69
|
const part = parts[i];
|
|
27
70
|
|
|
28
|
-
if (value
|
|
71
|
+
if (is_null_or_undefined(value)) {
|
|
29
72
|
return undefined;
|
|
30
73
|
}
|
|
31
74
|
|
|
32
75
|
value = value[part];
|
|
33
76
|
|
|
34
|
-
// If we got an array and there are more parts to traverse
|
|
35
77
|
if (Array.isArray(value) && i < parts.length - 1) {
|
|
36
|
-
// Extract the remaining path
|
|
37
78
|
const remaining_path = parts.slice(i + 1).join('.');
|
|
38
|
-
|
|
39
|
-
// Collect all matching values from array elements
|
|
40
|
-
const nested_values = [];
|
|
41
|
-
for (let j = 0; j < value.length; j++) {
|
|
42
|
-
const item = value[j];
|
|
43
|
-
if (typeof item === 'object' && item !== null) {
|
|
44
|
-
const nested_value = get_field_value(item, remaining_path);
|
|
45
|
-
if (nested_value !== undefined) {
|
|
46
|
-
if (Array.isArray(nested_value)) {
|
|
47
|
-
nested_values.push(...nested_value);
|
|
48
|
-
} else {
|
|
49
|
-
nested_values.push(nested_value);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Return array of values for nested array queries
|
|
56
|
-
return nested_values.length > 0 ? nested_values : undefined;
|
|
79
|
+
return collect_nested_values_from_array(value, remaining_path);
|
|
57
80
|
}
|
|
58
81
|
}
|
|
59
82
|
|
|
60
83
|
return value;
|
|
61
84
|
};
|
|
62
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Checks if current object has the specified property.
|
|
88
|
+
* @param {Object} current - Current object
|
|
89
|
+
* @param {string} property - Property name
|
|
90
|
+
* @returns {boolean} True if property exists
|
|
91
|
+
*/
|
|
92
|
+
const has_property = (current, property) => {
|
|
93
|
+
return current.hasOwnProperty(property);
|
|
94
|
+
};
|
|
95
|
+
|
|
63
96
|
/**
|
|
64
97
|
* Checks if a field exists in a document using dot notation path.
|
|
65
98
|
* @param {Object} document - Document to check
|
|
66
99
|
* @param {string} field_path - Dot-separated field path
|
|
67
|
-
* @returns {boolean} True if field exists
|
|
100
|
+
* @returns {boolean} True if field exists
|
|
68
101
|
*/
|
|
69
102
|
const field_exists = (document, field_path) => {
|
|
70
|
-
const parts = field_path
|
|
103
|
+
const parts = split_field_path(field_path);
|
|
71
104
|
let current = document;
|
|
72
105
|
|
|
73
106
|
for (let i = 0; i < parts.length; i++) {
|
|
74
|
-
if (current
|
|
107
|
+
if (is_null_or_undefined(current) || typeof current !== 'object') {
|
|
75
108
|
return false;
|
|
76
109
|
}
|
|
77
110
|
|
|
78
111
|
if (i === parts.length - 1) {
|
|
79
|
-
return current
|
|
112
|
+
return has_property(current, parts[i]);
|
|
80
113
|
}
|
|
81
114
|
|
|
82
115
|
current = current[parts[i]];
|
|
@@ -85,32 +118,264 @@ const field_exists = (document, field_path) => {
|
|
|
85
118
|
return false;
|
|
86
119
|
};
|
|
87
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Checks if array contains value.
|
|
123
|
+
* @param {Array} field_value - Array field value
|
|
124
|
+
* @param {any} operand - Value to check
|
|
125
|
+
* @returns {boolean} True if array contains value
|
|
126
|
+
*/
|
|
127
|
+
const array_contains_value = (field_value, operand) => {
|
|
128
|
+
return field_value.includes(operand);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Checks if any array element satisfies condition.
|
|
133
|
+
* @param {Array} field_value - Array field value
|
|
134
|
+
* @param {any} operand - Value to compare
|
|
135
|
+
* @param {Function} comparator - Comparison function
|
|
136
|
+
* @returns {boolean} True if any element satisfies condition
|
|
137
|
+
*/
|
|
138
|
+
const any_array_element_satisfies = (field_value, operand, comparator) => {
|
|
139
|
+
for (let i = 0; i < field_value.length; i++) {
|
|
140
|
+
if (comparator(field_value[i], operand)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Handles equality operator matching.
|
|
149
|
+
* @param {any} field_value - Field value to test
|
|
150
|
+
* @param {any} operand - Value to match
|
|
151
|
+
* @returns {boolean} True if matches
|
|
152
|
+
*/
|
|
153
|
+
const handle_equality_operator = (field_value, operand) => {
|
|
154
|
+
if (Array.isArray(field_value)) {
|
|
155
|
+
return array_contains_value(field_value, operand);
|
|
156
|
+
}
|
|
157
|
+
return field_value === operand;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Handles not equal operator matching.
|
|
162
|
+
* @param {any} field_value - Field value to test
|
|
163
|
+
* @param {any} operand - Value to match
|
|
164
|
+
* @returns {boolean} True if does not match
|
|
165
|
+
*/
|
|
166
|
+
const handle_not_equal_operator = (field_value, operand) => {
|
|
167
|
+
if (Array.isArray(field_value)) {
|
|
168
|
+
return !array_contains_value(field_value, operand);
|
|
169
|
+
}
|
|
170
|
+
return field_value !== operand;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Handles greater than operator matching.
|
|
175
|
+
* @param {any} field_value - Field value to test
|
|
176
|
+
* @param {any} operand - Value to compare
|
|
177
|
+
* @returns {boolean} True if greater than
|
|
178
|
+
*/
|
|
179
|
+
const handle_greater_than_operator = (field_value, operand) => {
|
|
180
|
+
if (Array.isArray(field_value)) {
|
|
181
|
+
return any_array_element_satisfies(field_value, operand, (val, op) => val > op);
|
|
182
|
+
}
|
|
183
|
+
return field_value > operand;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Handles greater than or equal operator matching.
|
|
188
|
+
* @param {any} field_value - Field value to test
|
|
189
|
+
* @param {any} operand - Value to compare
|
|
190
|
+
* @returns {boolean} True if greater than or equal
|
|
191
|
+
*/
|
|
192
|
+
const handle_greater_than_equal_operator = (field_value, operand) => {
|
|
193
|
+
if (Array.isArray(field_value)) {
|
|
194
|
+
return any_array_element_satisfies(field_value, operand, (val, op) => val >= op);
|
|
195
|
+
}
|
|
196
|
+
return field_value >= operand;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Handles less than operator matching.
|
|
201
|
+
* @param {any} field_value - Field value to test
|
|
202
|
+
* @param {any} operand - Value to compare
|
|
203
|
+
* @returns {boolean} True if less than
|
|
204
|
+
*/
|
|
205
|
+
const handle_less_than_operator = (field_value, operand) => {
|
|
206
|
+
if (Array.isArray(field_value)) {
|
|
207
|
+
return any_array_element_satisfies(field_value, operand, (val, op) => val < op);
|
|
208
|
+
}
|
|
209
|
+
return field_value < operand;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Handles less than or equal operator matching.
|
|
214
|
+
* @param {any} field_value - Field value to test
|
|
215
|
+
* @param {any} operand - Value to compare
|
|
216
|
+
* @returns {boolean} True if less than or equal
|
|
217
|
+
*/
|
|
218
|
+
const handle_less_than_equal_operator = (field_value, operand) => {
|
|
219
|
+
if (Array.isArray(field_value)) {
|
|
220
|
+
return any_array_element_satisfies(field_value, operand, (val, op) => val <= op);
|
|
221
|
+
}
|
|
222
|
+
return field_value <= operand;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Handles in operator matching.
|
|
227
|
+
* @param {any} field_value - Field value to test
|
|
228
|
+
* @param {Array} operand - Array of values to match
|
|
229
|
+
* @returns {boolean} True if value is in array
|
|
230
|
+
*/
|
|
231
|
+
const handle_in_operator = (field_value, operand) => {
|
|
232
|
+
if (!Array.isArray(operand)) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (Array.isArray(field_value)) {
|
|
237
|
+
return any_array_element_satisfies(field_value, operand, (val, op) => op.includes(val));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return operand.includes(field_value);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Handles not in operator matching.
|
|
245
|
+
* @param {any} field_value - Field value to test
|
|
246
|
+
* @param {Array} operand - Array of values to exclude
|
|
247
|
+
* @returns {boolean} True if value is not in array
|
|
248
|
+
*/
|
|
249
|
+
const handle_not_in_operator = (field_value, operand) => {
|
|
250
|
+
if (!Array.isArray(operand)) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (Array.isArray(field_value)) {
|
|
255
|
+
for (let i = 0; i < field_value.length; i++) {
|
|
256
|
+
if (operand.includes(field_value[i])) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return !operand.includes(field_value);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Handles exists operator matching.
|
|
268
|
+
* @param {Object} document - Document to check
|
|
269
|
+
* @param {string} field - Field name
|
|
270
|
+
* @param {boolean} operand - Whether field should exist
|
|
271
|
+
* @returns {boolean} True if existence matches expectation
|
|
272
|
+
*/
|
|
273
|
+
const handle_exists_operator = (document, field, operand) => {
|
|
274
|
+
const exists = field_exists(document, field);
|
|
275
|
+
return operand ? exists : !exists;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Handles regex operator matching.
|
|
280
|
+
* @param {any} field_value - Field value to test
|
|
281
|
+
* @param {string} pattern - Regex pattern
|
|
282
|
+
* @param {string} options - Regex options
|
|
283
|
+
* @returns {boolean} True if matches regex
|
|
284
|
+
*/
|
|
285
|
+
const handle_regex_operator = (field_value, pattern, options = '') => {
|
|
286
|
+
const regex = new RegExp(pattern, options);
|
|
287
|
+
|
|
288
|
+
if (Array.isArray(field_value)) {
|
|
289
|
+
return any_array_element_satisfies(field_value, regex, (val, reg) => {
|
|
290
|
+
return typeof val === 'string' && reg.test(val);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return regex.test(field_value);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Processes query operators for a field.
|
|
299
|
+
* @param {Object} document - Document to test
|
|
300
|
+
* @param {string} field - Field name
|
|
301
|
+
* @param {any} field_value - Field value
|
|
302
|
+
* @param {Object} value - Query value with operators
|
|
303
|
+
* @returns {boolean} True if all operators match
|
|
304
|
+
*/
|
|
305
|
+
const process_query_operators = (document, field, field_value, value) => {
|
|
306
|
+
for (const [operator, operand] of Object.entries(value)) {
|
|
307
|
+
switch (operator) {
|
|
308
|
+
case '$eq':
|
|
309
|
+
if (!handle_equality_operator(field_value, operand)) return false;
|
|
310
|
+
break;
|
|
311
|
+
case '$ne':
|
|
312
|
+
if (!handle_not_equal_operator(field_value, operand)) return false;
|
|
313
|
+
break;
|
|
314
|
+
case '$gt':
|
|
315
|
+
if (!handle_greater_than_operator(field_value, operand)) return false;
|
|
316
|
+
break;
|
|
317
|
+
case '$gte':
|
|
318
|
+
if (!handle_greater_than_equal_operator(field_value, operand)) return false;
|
|
319
|
+
break;
|
|
320
|
+
case '$lt':
|
|
321
|
+
if (!handle_less_than_operator(field_value, operand)) return false;
|
|
322
|
+
break;
|
|
323
|
+
case '$lte':
|
|
324
|
+
if (!handle_less_than_equal_operator(field_value, operand)) return false;
|
|
325
|
+
break;
|
|
326
|
+
case '$in':
|
|
327
|
+
if (!handle_in_operator(field_value, operand)) return false;
|
|
328
|
+
break;
|
|
329
|
+
case '$nin':
|
|
330
|
+
if (!handle_not_in_operator(field_value, operand)) return false;
|
|
331
|
+
break;
|
|
332
|
+
case '$exists':
|
|
333
|
+
if (!handle_exists_operator(document, field, operand)) return false;
|
|
334
|
+
break;
|
|
335
|
+
case '$regex':
|
|
336
|
+
const regex_options = value.$options || '';
|
|
337
|
+
if (!handle_regex_operator(field_value, operand, regex_options)) return false;
|
|
338
|
+
break;
|
|
339
|
+
case '$options':
|
|
340
|
+
break;
|
|
341
|
+
default:
|
|
342
|
+
throw new Error(`Unsupported query operator: ${operator}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Handles OR operator matching.
|
|
350
|
+
* @param {Object} document - Document to test
|
|
351
|
+
* @param {Array} or_conditions - Array of OR conditions
|
|
352
|
+
* @returns {boolean} True if any condition matches
|
|
353
|
+
*/
|
|
354
|
+
const handle_or_operator = (document, or_conditions) => {
|
|
355
|
+
for (let i = 0; i < or_conditions.length; i++) {
|
|
356
|
+
if (matches_filter(document, or_conditions[i])) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
};
|
|
362
|
+
|
|
88
363
|
/**
|
|
89
364
|
* Checks if a document matches the provided filter criteria.
|
|
90
|
-
* Supports MongoDB-like query operators with optimized array field matching.
|
|
91
365
|
* @param {Object} document - Document to test against filter
|
|
92
366
|
* @param {Object} filter - Filter criteria with field names and values/operators
|
|
93
|
-
* @returns {boolean} True if document matches filter
|
|
94
|
-
* @throws {Error} When unsupported query operator is used
|
|
367
|
+
* @returns {boolean} True if document matches filter
|
|
95
368
|
*/
|
|
96
369
|
const matches_filter = (document, filter) => {
|
|
97
370
|
if (!filter || Object.keys(filter).length === 0) {
|
|
98
371
|
return true;
|
|
99
372
|
}
|
|
100
373
|
|
|
101
|
-
// Handle $or operator at the top level
|
|
102
374
|
if (filter.$or && Array.isArray(filter.$or)) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
for (let i = 0; i < or_conditions.length; i++) {
|
|
106
|
-
if (matches_filter(document, or_conditions[i])) {
|
|
107
|
-
or_match = true;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
375
|
+
if (!handle_or_operator(document, filter.$or)) {
|
|
376
|
+
return false;
|
|
110
377
|
}
|
|
111
|
-
if (!or_match) return false;
|
|
112
378
|
|
|
113
|
-
// Continue checking other conditions (if any) outside of $or
|
|
114
379
|
const remaining_filter = { ...filter };
|
|
115
380
|
delete remaining_filter.$or;
|
|
116
381
|
if (Object.keys(remaining_filter).length > 0) {
|
|
@@ -123,143 +388,11 @@ const matches_filter = (document, filter) => {
|
|
|
123
388
|
const field_value = get_field_value(document, field);
|
|
124
389
|
|
|
125
390
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
case '$eq':
|
|
129
|
-
// Handle array field matching for $eq
|
|
130
|
-
if (Array.isArray(field_value)) {
|
|
131
|
-
if (!field_value.includes(operand)) return false;
|
|
132
|
-
} else if (field_value !== operand) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
|
-
case '$ne':
|
|
137
|
-
// Handle array field matching for $ne
|
|
138
|
-
if (Array.isArray(field_value)) {
|
|
139
|
-
if (field_value.includes(operand)) return false;
|
|
140
|
-
} else if (field_value === operand) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
break;
|
|
144
|
-
case '$gt':
|
|
145
|
-
if (Array.isArray(field_value)) {
|
|
146
|
-
let found = false;
|
|
147
|
-
for (let i = 0; i < field_value.length; i++) {
|
|
148
|
-
if (field_value[i] > operand) {
|
|
149
|
-
found = true;
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (!found) return false;
|
|
154
|
-
} else if (field_value <= operand) {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
break;
|
|
158
|
-
case '$gte':
|
|
159
|
-
if (Array.isArray(field_value)) {
|
|
160
|
-
let found = false;
|
|
161
|
-
for (let i = 0; i < field_value.length; i++) {
|
|
162
|
-
if (field_value[i] >= operand) {
|
|
163
|
-
found = true;
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (!found) return false;
|
|
168
|
-
} else if (field_value < operand) {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
172
|
-
case '$lt':
|
|
173
|
-
if (Array.isArray(field_value)) {
|
|
174
|
-
let found = false;
|
|
175
|
-
for (let i = 0; i < field_value.length; i++) {
|
|
176
|
-
if (field_value[i] < operand) {
|
|
177
|
-
found = true;
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (!found) return false;
|
|
182
|
-
} else if (field_value >= operand) {
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
break;
|
|
186
|
-
case '$lte':
|
|
187
|
-
if (Array.isArray(field_value)) {
|
|
188
|
-
let found = false;
|
|
189
|
-
for (let i = 0; i < field_value.length; i++) {
|
|
190
|
-
if (field_value[i] <= operand) {
|
|
191
|
-
found = true;
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (!found) return false;
|
|
196
|
-
} else if (field_value > operand) {
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
break;
|
|
200
|
-
case '$in':
|
|
201
|
-
if (!Array.isArray(operand)) return false;
|
|
202
|
-
if (Array.isArray(field_value)) {
|
|
203
|
-
// Check if any array element is in the operand array
|
|
204
|
-
let found = false;
|
|
205
|
-
for (let i = 0; i < field_value.length; i++) {
|
|
206
|
-
if (operand.includes(field_value[i])) {
|
|
207
|
-
found = true;
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (!found) return false;
|
|
212
|
-
} else if (!operand.includes(field_value)) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
break;
|
|
216
|
-
case '$nin':
|
|
217
|
-
if (!Array.isArray(operand)) return false;
|
|
218
|
-
if (Array.isArray(field_value)) {
|
|
219
|
-
// Check if any array element is in the operand array
|
|
220
|
-
for (let i = 0; i < field_value.length; i++) {
|
|
221
|
-
if (operand.includes(field_value[i])) {
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} else if (operand.includes(field_value)) {
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
case '$exists':
|
|
230
|
-
const exists = field_exists(document, field);
|
|
231
|
-
if (operand && !exists) return false;
|
|
232
|
-
if (!operand && exists) return false;
|
|
233
|
-
break;
|
|
234
|
-
case '$regex':
|
|
235
|
-
// Handle $options parameter for regex flags
|
|
236
|
-
const regex_options = value.$options || '';
|
|
237
|
-
const regex = new RegExp(operand, regex_options);
|
|
238
|
-
if (Array.isArray(field_value)) {
|
|
239
|
-
let found = false;
|
|
240
|
-
for (let i = 0; i < field_value.length; i++) {
|
|
241
|
-
if (typeof field_value[i] === 'string' && regex.test(field_value[i])) {
|
|
242
|
-
found = true;
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
if (!found) return false;
|
|
247
|
-
} else if (!regex.test(field_value)) {
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
break;
|
|
251
|
-
case '$options':
|
|
252
|
-
// $options is handled as part of $regex, skip it here
|
|
253
|
-
break;
|
|
254
|
-
default:
|
|
255
|
-
throw new Error(`Unsupported query operator: ${operator}`);
|
|
256
|
-
}
|
|
391
|
+
if (!process_query_operators(document, field, field_value, value)) {
|
|
392
|
+
return false;
|
|
257
393
|
}
|
|
258
394
|
} else {
|
|
259
|
-
|
|
260
|
-
if (Array.isArray(field_value)) {
|
|
261
|
-
if (!field_value.includes(value)) return false;
|
|
262
|
-
} else if (field_value !== value) {
|
|
395
|
+
if (!handle_equality_operator(field_value, value)) {
|
|
263
396
|
return false;
|
|
264
397
|
}
|
|
265
398
|
}
|
|
@@ -269,48 +402,90 @@ const matches_filter = (document, filter) => {
|
|
|
269
402
|
};
|
|
270
403
|
|
|
271
404
|
/**
|
|
272
|
-
*
|
|
273
|
-
*
|
|
405
|
+
* Determines if projection is inclusion-based.
|
|
406
|
+
* @param {Object} projection - Projection specification
|
|
407
|
+
* @returns {boolean} True if inclusion-based
|
|
408
|
+
*/
|
|
409
|
+
const is_inclusion_projection = (projection) => {
|
|
410
|
+
return Object.values(projection).some(value => value === 1 || value === true);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Applies inclusion projection to document.
|
|
274
415
|
* @param {Object} document - Document to project
|
|
275
|
-
* @param {Object} projection - Projection specification
|
|
276
|
-
* @returns {Object} Projected document
|
|
416
|
+
* @param {Object} projection - Projection specification
|
|
417
|
+
* @returns {Object} Projected document
|
|
418
|
+
*/
|
|
419
|
+
const apply_inclusion_projection = (document, projection) => {
|
|
420
|
+
const projected_document = { _id: document._id };
|
|
421
|
+
|
|
422
|
+
for (const [field, include] of Object.entries(projection)) {
|
|
423
|
+
if (field === '_id' && (include === 0 || include === false)) {
|
|
424
|
+
delete projected_document._id;
|
|
425
|
+
} else if (include === 1 || include === true) {
|
|
426
|
+
projected_document[field] = document[field];
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return projected_document;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Applies exclusion projection to document.
|
|
435
|
+
* @param {Object} document - Document to project
|
|
436
|
+
* @param {Object} projection - Projection specification
|
|
437
|
+
* @returns {Object} Projected document
|
|
438
|
+
*/
|
|
439
|
+
const apply_exclusion_projection = (document, projection) => {
|
|
440
|
+
const projected_document = { ...document };
|
|
441
|
+
|
|
442
|
+
for (const [field, exclude] of Object.entries(projection)) {
|
|
443
|
+
if (exclude === 0 || exclude === false) {
|
|
444
|
+
delete projected_document[field];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return projected_document;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Applies projection to a document.
|
|
453
|
+
* @param {Object} document - Document to project
|
|
454
|
+
* @param {Object} projection - Projection specification
|
|
455
|
+
* @returns {Object} Projected document
|
|
277
456
|
*/
|
|
278
457
|
const apply_projection = (document, projection) => {
|
|
279
458
|
if (!projection || Object.keys(projection).length === 0) {
|
|
280
459
|
return document;
|
|
281
460
|
}
|
|
282
461
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (is_inclusion) {
|
|
287
|
-
projected_document._id = document._id;
|
|
288
|
-
|
|
289
|
-
for (const [field, include] of Object.entries(projection)) {
|
|
290
|
-
if (field === '_id' && (include === 0 || include === false)) {
|
|
291
|
-
delete projected_document._id;
|
|
292
|
-
} else if (include === 1 || include === true) {
|
|
293
|
-
projected_document[field] = document[field];
|
|
294
|
-
}
|
|
295
|
-
}
|
|
462
|
+
if (is_inclusion_projection(projection)) {
|
|
463
|
+
return apply_inclusion_projection(document, projection);
|
|
296
464
|
} else {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
for (const [field, exclude] of Object.entries(projection)) {
|
|
300
|
-
if (exclude === 0 || exclude === false) {
|
|
301
|
-
delete projected_document[field];
|
|
302
|
-
}
|
|
303
|
-
}
|
|
465
|
+
return apply_exclusion_projection(document, projection);
|
|
304
466
|
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Compares two values for sorting.
|
|
471
|
+
* @param {any} a_value - First value
|
|
472
|
+
* @param {any} b_value - Second value
|
|
473
|
+
* @param {number} direction - Sort direction (1 or -1)
|
|
474
|
+
* @returns {number} Comparison result
|
|
475
|
+
*/
|
|
476
|
+
const compare_values_for_sort = (a_value, b_value, direction) => {
|
|
477
|
+
if (a_value === b_value) return 0;
|
|
478
|
+
if (a_value === undefined) return 1;
|
|
479
|
+
if (b_value === undefined) return -1;
|
|
305
480
|
|
|
306
|
-
|
|
481
|
+
const comparison = a_value < b_value ? -1 : a_value > b_value ? 1 : 0;
|
|
482
|
+
return direction === -1 ? -comparison : comparison;
|
|
307
483
|
};
|
|
308
484
|
|
|
309
485
|
/**
|
|
310
486
|
* Sorts an array of documents based on sort specification.
|
|
311
|
-
* Supports multi-field sorting with ascending (1) and descending (-1) directions.
|
|
312
487
|
* @param {Array<Object>} documents - Array of documents to sort
|
|
313
|
-
* @param {Object} sort - Sort specification
|
|
488
|
+
* @param {Object} sort - Sort specification
|
|
314
489
|
* @returns {Array<Object>} Sorted array of documents
|
|
315
490
|
*/
|
|
316
491
|
const apply_sort = (documents, sort) => {
|
|
@@ -320,44 +495,22 @@ const apply_sort = (documents, sort) => {
|
|
|
320
495
|
|
|
321
496
|
return documents.sort((a, b) => {
|
|
322
497
|
for (const [field, direction] of Object.entries(sort)) {
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (a_value === b_value) continue;
|
|
327
|
-
|
|
328
|
-
if (a_value === undefined) return 1;
|
|
329
|
-
if (b_value === undefined) return -1;
|
|
330
|
-
|
|
331
|
-
const comparison = a_value < b_value ? -1 : a_value > b_value ? 1 : 0;
|
|
332
|
-
|
|
333
|
-
if (direction === -1) {
|
|
334
|
-
return -comparison;
|
|
498
|
+
const comparison = compare_values_for_sort(a[field], b[field], direction);
|
|
499
|
+
if (comparison !== 0) {
|
|
500
|
+
return comparison;
|
|
335
501
|
}
|
|
336
|
-
|
|
337
|
-
return comparison;
|
|
338
502
|
}
|
|
339
|
-
|
|
340
503
|
return 0;
|
|
341
504
|
});
|
|
342
505
|
};
|
|
343
506
|
|
|
344
507
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* @param {string}
|
|
348
|
-
* @
|
|
349
|
-
* @param {Object} [filter={}] - Filter criteria for matching documents
|
|
350
|
-
* @param {Object} [options={}] - Query options
|
|
351
|
-
* @param {Object} [options.projection] - Fields to include/exclude in results
|
|
352
|
-
* @param {Object} [options.sort] - Sort specification for ordering results
|
|
353
|
-
* @param {number} [options.limit] - Maximum number of documents to return
|
|
354
|
-
* @param {number} [options.skip=0] - Number of documents to skip
|
|
355
|
-
* @returns {Promise<Array<Object>>} Promise resolving to array of matching documents
|
|
356
|
-
* @throws {Error} When database or collection name is missing or query execution fails
|
|
508
|
+
* Validates required parameters.
|
|
509
|
+
* @param {string} database_name - Database name
|
|
510
|
+
* @param {string} collection_name - Collection name
|
|
511
|
+
* @throws {Error} When parameters are missing
|
|
357
512
|
*/
|
|
358
|
-
const
|
|
359
|
-
const log = create_context_logger();
|
|
360
|
-
|
|
513
|
+
const validate_find_parameters = (database_name, collection_name) => {
|
|
361
514
|
if (!database_name) {
|
|
362
515
|
throw new Error('Database name is required');
|
|
363
516
|
}
|
|
@@ -365,6 +518,152 @@ const find = async (database_name, collection_name, filter = {}, options = {}) =
|
|
|
365
518
|
if (!collection_name) {
|
|
366
519
|
throw new Error('Collection name is required');
|
|
367
520
|
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Searches for documents using index.
|
|
525
|
+
* @param {Object} db - Database instance
|
|
526
|
+
* @param {string} database_name - Database name
|
|
527
|
+
* @param {string} collection_name - Collection name
|
|
528
|
+
* @param {Object} filter - Query filter
|
|
529
|
+
* @param {Object} index_info - Index information
|
|
530
|
+
* @returns {Array<Object>} Found documents
|
|
531
|
+
*/
|
|
532
|
+
const search_using_index = (db, database_name, collection_name, filter, index_info) => {
|
|
533
|
+
const { field, operators } = index_info;
|
|
534
|
+
const field_filter = filter[field];
|
|
535
|
+
const matching_documents = [];
|
|
536
|
+
|
|
537
|
+
record_index_usage(database_name, collection_name, field);
|
|
538
|
+
|
|
539
|
+
let document_ids = null;
|
|
540
|
+
|
|
541
|
+
if (typeof field_filter === 'object' && field_filter !== null && !Array.isArray(field_filter)) {
|
|
542
|
+
for (const operator of operators) {
|
|
543
|
+
if (field_filter[operator] !== undefined) {
|
|
544
|
+
document_ids = find_documents_by_index(database_name, collection_name, field, operator, field_filter[operator]);
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} else if (operators.includes('eq')) {
|
|
549
|
+
document_ids = find_documents_by_index(database_name, collection_name, field, 'eq', field_filter);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (document_ids) {
|
|
553
|
+
for (const document_id of document_ids) {
|
|
554
|
+
const collection_key = build_collection_key(database_name, collection_name, document_id);
|
|
555
|
+
const document_data = db.get(collection_key);
|
|
556
|
+
|
|
557
|
+
if (document_data) {
|
|
558
|
+
const document = JSON.parse(document_data);
|
|
559
|
+
if (matches_filter(document, filter)) {
|
|
560
|
+
matching_documents.push(document);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return matching_documents;
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Searches collection using full scan.
|
|
571
|
+
* @param {Object} db - Database instance
|
|
572
|
+
* @param {string} database_name - Database name
|
|
573
|
+
* @param {string} collection_name - Collection name
|
|
574
|
+
* @param {Object} filter - Query filter
|
|
575
|
+
* @returns {Array<Object>} Found documents
|
|
576
|
+
*/
|
|
577
|
+
const search_using_full_scan = (db, database_name, collection_name, filter) => {
|
|
578
|
+
const matching_documents = [];
|
|
579
|
+
const collection_prefix = `${database_name}:${collection_name}:`;
|
|
580
|
+
const range = db.getRange({ start: collection_prefix, end: collection_prefix + '\xFF' });
|
|
581
|
+
|
|
582
|
+
for (const { key, value: document_data } of range) {
|
|
583
|
+
const document = JSON.parse(document_data);
|
|
584
|
+
if (matches_filter(document, filter)) {
|
|
585
|
+
matching_documents.push(document);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return matching_documents;
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Applies pagination to documents.
|
|
594
|
+
* @param {Array<Object>} documents - Documents to paginate
|
|
595
|
+
* @param {number} skip - Number of documents to skip
|
|
596
|
+
* @param {number} limit - Maximum number of documents to return
|
|
597
|
+
* @returns {Array<Object>} Paginated documents
|
|
598
|
+
*/
|
|
599
|
+
const apply_pagination = (documents, skip, limit) => {
|
|
600
|
+
let result = documents;
|
|
601
|
+
|
|
602
|
+
if (skip > 0) {
|
|
603
|
+
result = result.slice(skip);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (limit && limit > 0) {
|
|
607
|
+
result = result.slice(0, limit);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return result;
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Records query for auto-indexing analysis.
|
|
615
|
+
* @param {Function} log - Logger function
|
|
616
|
+
* @param {string} collection_name - Collection name
|
|
617
|
+
* @param {Object} filter - Query filter
|
|
618
|
+
* @param {number} execution_time - Execution time in milliseconds
|
|
619
|
+
* @param {boolean} used_index - Whether index was used
|
|
620
|
+
* @param {string} indexed_field - Field that was indexed
|
|
621
|
+
*/
|
|
622
|
+
const record_query_for_auto_indexing = (log, collection_name, filter, execution_time, used_index, indexed_field) => {
|
|
623
|
+
try {
|
|
624
|
+
record_query(collection_name, filter, execution_time, used_index, indexed_field);
|
|
625
|
+
} catch (auto_index_error) {
|
|
626
|
+
log.warn('Failed to record query for auto-indexing', {
|
|
627
|
+
error: auto_index_error.message
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Logs find operation completion.
|
|
634
|
+
* @param {Function} log - Logger function
|
|
635
|
+
* @param {string} database_name - Database name
|
|
636
|
+
* @param {string} collection_name - Collection name
|
|
637
|
+
* @param {number} documents_found - Number of documents found
|
|
638
|
+
* @param {number} total_matching - Total matching documents
|
|
639
|
+
* @param {boolean} used_index - Whether index was used
|
|
640
|
+
* @param {string} indexed_field - Field that was indexed
|
|
641
|
+
* @param {number} execution_time - Execution time in milliseconds
|
|
642
|
+
*/
|
|
643
|
+
const log_find_completion = (log, database_name, collection_name, documents_found, total_matching, used_index, indexed_field, execution_time) => {
|
|
644
|
+
log.info('Find operation completed', {
|
|
645
|
+
database: database_name,
|
|
646
|
+
collection: collection_name,
|
|
647
|
+
documents_found,
|
|
648
|
+
total_matching,
|
|
649
|
+
used_index,
|
|
650
|
+
indexed_field,
|
|
651
|
+
execution_time_ms: execution_time
|
|
652
|
+
});
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Finds documents in a collection matching the specified filter criteria.
|
|
657
|
+
* @param {string} database_name - Name of the database
|
|
658
|
+
* @param {string} collection_name - Name of the collection to search
|
|
659
|
+
* @param {Object} filter - Filter criteria for matching documents
|
|
660
|
+
* @param {Object} options - Query options
|
|
661
|
+
* @returns {Promise<Array<Object>>} Array of matching documents
|
|
662
|
+
*/
|
|
663
|
+
const find = async (database_name, collection_name, filter = {}, options = {}) => {
|
|
664
|
+
const log = create_context_logger();
|
|
665
|
+
|
|
666
|
+
validate_find_parameters(database_name, collection_name);
|
|
368
667
|
|
|
369
668
|
const db = get_database();
|
|
370
669
|
const { projection, sort, limit, skip = 0 } = options;
|
|
@@ -378,101 +677,35 @@ const find = async (database_name, collection_name, filter = {}, options = {}) =
|
|
|
378
677
|
const index_info = can_use_index(database_name, collection_name, filter);
|
|
379
678
|
|
|
380
679
|
if (index_info) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (typeof field_filter === 'object' && field_filter !== null && !Array.isArray(field_filter)) {
|
|
386
|
-
for (const operator of operators) {
|
|
387
|
-
if (field_filter[operator] !== undefined) {
|
|
388
|
-
const document_ids = find_documents_by_index(database_name, collection_name, field, operator, field_filter[operator]);
|
|
389
|
-
|
|
390
|
-
if (document_ids) {
|
|
391
|
-
used_index = true;
|
|
392
|
-
record_index_usage(database_name, collection_name, field);
|
|
393
|
-
|
|
394
|
-
for (const document_id of document_ids) {
|
|
395
|
-
const collection_key = build_collection_key(database_name, collection_name, document_id);
|
|
396
|
-
const document_data = db.get(collection_key);
|
|
397
|
-
|
|
398
|
-
if (document_data) {
|
|
399
|
-
const document = JSON.parse(document_data);
|
|
400
|
-
if (matches_filter(document, filter)) {
|
|
401
|
-
matching_documents.push(document);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
} else if (operators.includes('eq')) {
|
|
410
|
-
const document_ids = find_documents_by_index(database_name, collection_name, field, 'eq', field_filter);
|
|
411
|
-
|
|
412
|
-
if (document_ids) {
|
|
413
|
-
used_index = true;
|
|
414
|
-
record_index_usage(database_name, collection_name, field);
|
|
415
|
-
|
|
416
|
-
for (const document_id of document_ids) {
|
|
417
|
-
const collection_key = build_collection_key(database_name, collection_name, document_id);
|
|
418
|
-
const document_data = db.get(collection_key);
|
|
419
|
-
|
|
420
|
-
if (document_data) {
|
|
421
|
-
const document = JSON.parse(document_data);
|
|
422
|
-
if (matches_filter(document, filter)) {
|
|
423
|
-
matching_documents.push(document);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
680
|
+
indexed_field = index_info.field;
|
|
681
|
+
matching_documents = search_using_index(db, database_name, collection_name, filter, index_info);
|
|
682
|
+
used_index = matching_documents.length > 0;
|
|
429
683
|
}
|
|
430
684
|
|
|
431
685
|
if (!used_index) {
|
|
432
|
-
|
|
433
|
-
const range = db.getRange({ start: collection_prefix, end: collection_prefix + '\xFF' });
|
|
434
|
-
|
|
435
|
-
for (const { key, value: document_data } of range) {
|
|
436
|
-
const document = JSON.parse(document_data);
|
|
437
|
-
if (matches_filter(document, filter)) {
|
|
438
|
-
matching_documents.push(document);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
let sorted_documents = apply_sort(matching_documents, sort);
|
|
444
|
-
|
|
445
|
-
if (skip > 0) {
|
|
446
|
-
sorted_documents = sorted_documents.slice(skip);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (limit && limit > 0) {
|
|
450
|
-
sorted_documents = sorted_documents.slice(0, limit);
|
|
686
|
+
matching_documents = search_using_full_scan(db, database_name, collection_name, filter);
|
|
451
687
|
}
|
|
452
688
|
|
|
453
|
-
const
|
|
689
|
+
const sorted_documents = apply_sort(matching_documents, sort);
|
|
690
|
+
const paginated_documents = apply_pagination(sorted_documents, skip, limit);
|
|
691
|
+
const projected_documents = paginated_documents.map(document =>
|
|
454
692
|
apply_projection(document, projection)
|
|
455
693
|
);
|
|
456
694
|
|
|
457
695
|
const execution_time = Date.now() - start_time;
|
|
458
696
|
|
|
459
|
-
|
|
460
|
-
record_query(collection_name, filter, execution_time, used_index, indexed_field);
|
|
461
|
-
} catch (auto_index_error) {
|
|
462
|
-
log.warn('Failed to record query for auto-indexing', {
|
|
463
|
-
error: auto_index_error.message
|
|
464
|
-
});
|
|
465
|
-
}
|
|
697
|
+
record_query_for_auto_indexing(log, collection_name, filter, execution_time, used_index, indexed_field);
|
|
466
698
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
699
|
+
log_find_completion(
|
|
700
|
+
log,
|
|
701
|
+
database_name,
|
|
702
|
+
collection_name,
|
|
703
|
+
projected_documents.length,
|
|
704
|
+
matching_documents.length,
|
|
705
|
+
used_index,
|
|
706
|
+
indexed_field,
|
|
707
|
+
execution_time
|
|
708
|
+
);
|
|
476
709
|
|
|
477
710
|
return projected_documents;
|
|
478
711
|
} catch (error) {
|