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