jsonbadger 0.5.0 → 0.6.0
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/README.md +36 -18
- package/docs/api/connection.md +144 -0
- package/docs/api/delta-tracker.md +106 -0
- package/docs/api/document.md +77 -0
- package/docs/api/field-types.md +329 -0
- package/docs/api/index.md +35 -0
- package/docs/api/model.md +392 -0
- package/docs/api/query-builder.md +81 -0
- package/docs/api/schema.md +204 -0
- package/docs/architecture-flow.md +397 -0
- package/docs/examples.md +495 -218
- package/docs/jsonb-ops.md +171 -0
- package/docs/lifecycle/model-compilation.md +111 -0
- package/docs/lifecycle.md +146 -0
- package/docs/query-translation.md +11 -10
- package/package.json +10 -3
- package/src/connection/connect.js +12 -17
- package/src/connection/connection.js +128 -0
- package/src/connection/server-capabilities.js +60 -59
- package/src/constants/defaults.js +32 -19
- package/src/constants/{id-strategies.js → id-strategy.js} +28 -29
- package/src/constants/intake-mode.js +8 -0
- package/src/debug/debug-logger.js +17 -15
- package/src/errors/model-overwrite-error.js +25 -0
- package/src/errors/query-error.js +25 -23
- package/src/errors/validation-error.js +25 -23
- package/src/field-types/base-field-type.js +137 -140
- package/src/field-types/builtins/advanced.js +365 -365
- package/src/field-types/builtins/index.js +579 -585
- package/src/field-types/field-type-namespace.js +9 -0
- package/src/field-types/registry.js +149 -122
- package/src/index.js +26 -36
- package/src/migration/ensure-index.js +157 -154
- package/src/migration/ensure-schema.js +27 -15
- package/src/migration/ensure-table.js +44 -31
- package/src/migration/schema-indexes-resolver.js +8 -6
- package/src/model/document-instance.js +29 -540
- package/src/model/document.js +60 -0
- package/src/model/factory/constants.js +36 -0
- package/src/model/factory/index.js +58 -0
- package/src/model/model.js +875 -0
- package/src/model/operations/delete-one.js +39 -0
- package/src/model/operations/insert-one.js +35 -0
- package/src/model/operations/query-builder.js +132 -0
- package/src/model/operations/update-one.js +333 -0
- package/src/model/state.js +34 -0
- package/src/schema/field-definition-parser.js +213 -218
- package/src/schema/path-introspection.js +87 -82
- package/src/schema/schema-compiler.js +126 -212
- package/src/schema/schema.js +621 -138
- package/src/sql/index.js +17 -0
- package/src/sql/jsonb/ops.js +153 -0
- package/src/{query → sql/jsonb}/path-parser.js +54 -43
- package/src/sql/jsonb/read/elem-match.js +133 -0
- package/src/{query → sql/jsonb/read}/operators/contains.js +13 -7
- package/src/sql/jsonb/read/operators/elem-match.js +9 -0
- package/src/{query → sql/jsonb/read}/operators/has-all-keys.js +17 -11
- package/src/{query → sql/jsonb/read}/operators/has-any-keys.js +18 -11
- package/src/sql/jsonb/read/operators/has-key.js +12 -0
- package/src/{query → sql/jsonb/read}/operators/jsonpath-exists.js +22 -15
- package/src/{query → sql/jsonb/read}/operators/jsonpath-match.js +22 -15
- package/src/{query → sql/jsonb/read}/operators/size.js +23 -16
- package/src/sql/parameter-binder.js +18 -13
- package/src/sql/read/build-count-query.js +12 -0
- package/src/sql/read/build-find-query.js +25 -0
- package/src/sql/read/limit-skip.js +21 -0
- package/src/sql/read/sort.js +85 -0
- package/src/sql/read/where/base-fields.js +310 -0
- package/src/sql/read/where/casting.js +90 -0
- package/src/sql/read/where/context.js +79 -0
- package/src/sql/read/where/field-clause.js +58 -0
- package/src/sql/read/where/index.js +38 -0
- package/src/sql/read/where/operator-entries.js +29 -0
- package/src/{query → sql/read/where}/operators/all.js +16 -10
- package/src/sql/read/where/operators/eq.js +12 -0
- package/src/{query → sql/read/where}/operators/gt.js +23 -16
- package/src/{query → sql/read/where}/operators/gte.js +23 -16
- package/src/{query → sql/read/where}/operators/in.js +18 -12
- package/src/sql/read/where/operators/index.js +40 -0
- package/src/{query → sql/read/where}/operators/lt.js +23 -16
- package/src/{query → sql/read/where}/operators/lte.js +23 -16
- package/src/sql/read/where/operators/ne.js +12 -0
- package/src/{query → sql/read/where}/operators/nin.js +18 -12
- package/src/{query → sql/read/where}/operators/regex.js +14 -8
- package/src/sql/read/where/operators.js +126 -0
- package/src/sql/read/where/text-operators.js +83 -0
- package/src/sql/run.js +46 -0
- package/src/sql/write/build-delete-query.js +33 -0
- package/src/sql/write/build-insert-query.js +42 -0
- package/src/sql/write/build-update-query.js +65 -0
- package/src/utils/assert.js +34 -27
- package/src/utils/delta-tracker/.archive/1 tracker-redesign-codex-v2.md +250 -0
- package/src/utils/delta-tracker/.archive/1 tracker-redesign-gemini.md +101 -0
- package/src/utils/delta-tracker/.archive/2 evaluation by gemini.txt +65 -0
- package/src/utils/delta-tracker/.archive/2 evaluation by grok.txt +39 -0
- package/src/utils/delta-tracker/.archive/3 gemini evaluate grok.txt +37 -0
- package/src/utils/delta-tracker/.archive/3 grok evaluate gemini.txt +63 -0
- package/src/utils/delta-tracker/.archive/4 gemini veredict.txt +16 -0
- package/src/utils/delta-tracker/.archive/index.1.js +587 -0
- package/src/utils/delta-tracker/.archive/index.2.js +612 -0
- package/src/utils/delta-tracker/index.js +592 -0
- package/src/utils/dirty-tracker/inline.js +335 -0
- package/src/utils/dirty-tracker/instance.js +414 -0
- package/src/utils/dirty-tracker/static.js +343 -0
- package/src/utils/json-safe.js +13 -9
- package/src/utils/object-path.js +227 -33
- package/src/utils/object.js +408 -168
- package/src/utils/string.js +55 -0
- package/src/utils/value.js +169 -30
- package/docs/api.md +0 -152
- package/src/connection/disconnect.js +0 -16
- package/src/connection/pool-store.js +0 -46
- package/src/model/model-factory.js +0 -555
- package/src/query/limit-skip-compiler.js +0 -31
- package/src/query/operators/elem-match.js +0 -3
- package/src/query/operators/eq.js +0 -6
- package/src/query/operators/has-key.js +0 -6
- package/src/query/operators/index.js +0 -60
- package/src/query/operators/ne.js +0 -6
- package/src/query/query-builder.js +0 -93
- package/src/query/sort-compiler.js +0 -30
- package/src/query/where-compiler.js +0 -477
- package/src/sql/sql-runner.js +0 -31
package/src/sql/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Expose the shared SQL builder and runner entry points.
|
|
4
|
+
*/
|
|
5
|
+
import build_update_query from '#src/sql/write/build-update-query.js';
|
|
6
|
+
import build_insert_query from '#src/sql/write/build-insert-query.js';
|
|
7
|
+
import build_delete_query from '#src/sql/write/build-delete-query.js';
|
|
8
|
+
import run from '#src/sql/run.js';
|
|
9
|
+
|
|
10
|
+
const sql = {
|
|
11
|
+
build_delete_query,
|
|
12
|
+
build_insert_query,
|
|
13
|
+
build_update_query,
|
|
14
|
+
run
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default sql;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Parse operator-style JSONB mutations and compile one SQL RHS expression.
|
|
4
|
+
*/
|
|
5
|
+
import QueryError from '#src/errors/query-error.js';
|
|
6
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
7
|
+
import {is_array} from '#src/utils/array.js';
|
|
8
|
+
import {build_path_literal, split_dot_path} from '#src/utils/object-path.js';
|
|
9
|
+
import {jsonb_stringify} from '#src/utils/json.js';
|
|
10
|
+
import {is_not_object, is_plain_object} from '#src/utils/value.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The canonical execution order for PostgreSQL JSONB mutations.
|
|
14
|
+
*/
|
|
15
|
+
const core_update_operators = ['$replace_roots', '$unset', '$set'];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Utility for parsing JSONB mutations into a flat, ordered operation list.
|
|
19
|
+
* This acts as a Syntax Firewall: it knows HOW to write PG syntax,
|
|
20
|
+
* but knows NOTHING about SQL parameters.
|
|
21
|
+
*/
|
|
22
|
+
function JsonbOps(target, operations) {
|
|
23
|
+
this.target = target;
|
|
24
|
+
this.operations = operations;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compile one JsonbOps instance into a PostgreSQL JSONB RHS expression.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} parameter_state
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
JsonbOps.prototype.compile = function (parameter_state) {
|
|
34
|
+
let expression = this.target;
|
|
35
|
+
|
|
36
|
+
for(const step of this.operations) {
|
|
37
|
+
if(step.op === '$replace_roots') {
|
|
38
|
+
const placeholder = bind_parameter(parameter_state, step.value);
|
|
39
|
+
expression = `${placeholder}::jsonb`;
|
|
40
|
+
}
|
|
41
|
+
else if(step.op === '#-') {
|
|
42
|
+
expression = `${expression} #- '${step.path}'`;
|
|
43
|
+
}
|
|
44
|
+
else if(step.op === 'jsonb_set') {
|
|
45
|
+
const placeholder = bind_parameter(parameter_state, step.value);
|
|
46
|
+
expression = `jsonb_set(${expression}, '${step.path}', ${placeholder}::jsonb, true)`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return expression;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Transforms one operator-style update definition into a JsonbOps instance.
|
|
55
|
+
*
|
|
56
|
+
* The input contract here is `$replace_roots`, `$unset`, `$set`, plus implicit
|
|
57
|
+
* top-level `$set` keys. If the caller starts from a tracker delta shape
|
|
58
|
+
* (`replace_roots`, `set`, `unset`), the orchestrator must map that delta into
|
|
59
|
+
* operator-style keys before calling `.from(...)`.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} update_definition - Operator-style update input for one JSONB mutation.
|
|
62
|
+
* @param {object} options
|
|
63
|
+
* @param {string} options.column_name - The target column (e.g. '"data"').
|
|
64
|
+
* @param {boolean} [options.coalesce=true] - Wrap base in COALESCE.
|
|
65
|
+
* @returns {JsonbOps}
|
|
66
|
+
*/
|
|
67
|
+
JsonbOps.from = function (update_definition, options) {
|
|
68
|
+
if(is_not_object(update_definition)) {
|
|
69
|
+
throw new QueryError('JsonbOps requires an object definition');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const {column_name, coalesce = true} = options || {};
|
|
73
|
+
|
|
74
|
+
if(!column_name) {
|
|
75
|
+
throw new QueryError('JsonbOps requires options.column_name');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Initialized with explicit types to avoid defensive assignment later
|
|
79
|
+
const buckets = {$replace_roots: {}, $unset: [], $set: {}};
|
|
80
|
+
let total_operations = 0;
|
|
81
|
+
|
|
82
|
+
// 1. Distribution & Auto-Wrapping
|
|
83
|
+
for(const [key, value] of Object.entries(update_definition)) {
|
|
84
|
+
if(key.startsWith('$')) {
|
|
85
|
+
if(!core_update_operators.includes(key)) {
|
|
86
|
+
throw new QueryError(`Unsupported JSONB operator: ${key}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if(key === '$unset') {
|
|
90
|
+
if(!is_array(value)) {
|
|
91
|
+
throw new QueryError('$unset expects array');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
buckets.$unset.push(...value);
|
|
95
|
+
total_operations += value.length;
|
|
96
|
+
|
|
97
|
+
} else {
|
|
98
|
+
if(!is_plain_object(value)) {
|
|
99
|
+
throw new QueryError(`${key} expects plain object`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Object.assign(buckets[key], value);
|
|
103
|
+
total_operations += Object.keys(value).length;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
} else {
|
|
107
|
+
// Implicit wrapping of raw keys into $set
|
|
108
|
+
buckets.$set[key] = value;
|
|
109
|
+
total_operations += 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if(total_operations > 1024) {
|
|
114
|
+
throw new QueryError('JSONB mutation exceeds complexity limit (1024)');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 2. Build the Linear Operation List
|
|
118
|
+
const operations = [];
|
|
119
|
+
let target = coalesce ? `COALESCE(${column_name}, '{}'::jsonb)` : column_name;
|
|
120
|
+
|
|
121
|
+
// A. Replace Roots (Redefines the starting target)
|
|
122
|
+
if(Object.keys(buckets.$replace_roots).length > 0) {
|
|
123
|
+
operations.push({
|
|
124
|
+
op: '$replace_roots',
|
|
125
|
+
value: jsonb_stringify(buckets.$replace_roots)
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// B. Unsets
|
|
130
|
+
if(buckets.$unset.length > 0) {
|
|
131
|
+
for(const path of buckets.$unset) {
|
|
132
|
+
operations.push({
|
|
133
|
+
op: '#-',
|
|
134
|
+
path: build_path_literal(split_dot_path(path))
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// C. Sets
|
|
140
|
+
if(Object.keys(buckets.$set).length > 0) {
|
|
141
|
+
for(const [path, value] of Object.entries(buckets.$set)) {
|
|
142
|
+
operations.push({
|
|
143
|
+
op: 'jsonb_set',
|
|
144
|
+
path: build_path_literal(split_dot_path(path)),
|
|
145
|
+
value: jsonb_stringify(value)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return new JsonbOps(target, operations);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export {JsonbOps};
|
|
@@ -1,43 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Parse JSONB paths and build SQL path expressions for JSONB query helpers.
|
|
4
|
+
*/
|
|
5
|
+
import {assert_path} from '#src/utils/assert.js';
|
|
6
|
+
import {build_path_literal, split_dot_path} from '#src/utils/object-path.js';
|
|
7
|
+
|
|
8
|
+
function build_text_expression(column_reference, path_value) {
|
|
9
|
+
const path_segments = split_dot_path(path_value);
|
|
10
|
+
|
|
11
|
+
if(path_segments.length === 1) {
|
|
12
|
+
return column_reference + " ->> '" + path_segments[0] + "'";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return column_reference + " #>> '" + build_path_literal(path_segments) + "'";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function build_json_expression(column_reference, path_value) {
|
|
19
|
+
const path_segments = split_dot_path(path_value);
|
|
20
|
+
|
|
21
|
+
if(path_segments.length === 1) {
|
|
22
|
+
return column_reference + " -> '" + path_segments[0] + "'";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return column_reference + " #> '" + build_path_literal(path_segments) + "'";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function build_elem_text_expression(elem_alias, path_segments) {
|
|
29
|
+
if(path_segments.length === 1) {
|
|
30
|
+
return elem_alias + "->>'" + path_segments[0] + "'";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return elem_alias + " #>> '" + build_path_literal(path_segments) + "'";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parse_path(path_value) {
|
|
37
|
+
assert_path(path_value, 'path');
|
|
38
|
+
|
|
39
|
+
const path_segments = split_dot_path(path_value);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
path_segments: path_segments,
|
|
43
|
+
root_path: path_segments[0],
|
|
44
|
+
child_segments: path_segments.slice(1),
|
|
45
|
+
is_nested: path_segments.length > 1
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
build_text_expression,
|
|
51
|
+
build_json_expression,
|
|
52
|
+
build_elem_text_expression,
|
|
53
|
+
parse_path
|
|
54
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile JSONB nested-array and $elem_match predicates for read queries.
|
|
4
|
+
*/
|
|
5
|
+
import {build_elem_text_expression, build_json_expression} from '#src/sql/jsonb/path-parser.js';
|
|
6
|
+
import elem_match_operator from '#src/sql/jsonb/read/operators/elem-match.js';
|
|
7
|
+
import {eq_operator, regex_operator} from '#src/sql/read/where/operators/index.js';
|
|
8
|
+
|
|
9
|
+
import {has_operator_entries} from '#src/sql/read/where/context.js';
|
|
10
|
+
import {compile_operator_entry_clauses} from '#src/sql/read/where/operator-entries.js';
|
|
11
|
+
import {compile_text_operator} from '#src/sql/read/where/text-operators.js';
|
|
12
|
+
|
|
13
|
+
import {split_dot_path} from '#src/utils/object-path.js';
|
|
14
|
+
import {is_object} from '#src/utils/value.js';
|
|
15
|
+
import QueryError from '#src/errors/query-error.js';
|
|
16
|
+
|
|
17
|
+
const unsupported_elem_match_operator_message = 'Unsupported operator inside $elem_match';
|
|
18
|
+
|
|
19
|
+
function compile_nested_array_operator(operator_name, operator_value, regex_options, path_info, data_column_reference, parameter_state) {
|
|
20
|
+
const array_expression = build_json_expression(data_column_reference, path_info.root_path);
|
|
21
|
+
const elem_expression = build_elem_text_expression('elem', path_info.child_segments);
|
|
22
|
+
const predicate = compile_text_operator({
|
|
23
|
+
name: operator_name,
|
|
24
|
+
value: operator_value,
|
|
25
|
+
regex_options,
|
|
26
|
+
text_expression: elem_expression,
|
|
27
|
+
parameter_state,
|
|
28
|
+
error_message: unsupported_elem_match_operator_message
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if(!predicate) {
|
|
32
|
+
throw new QueryError(unsupported_elem_match_operator_message + ': ' + operator_name, {
|
|
33
|
+
operator: operator_name
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return elem_match_operator(array_expression, predicate);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function compile_elem_match_clause(path_value, elem_match_value, data_column_reference, parameter_state) {
|
|
41
|
+
const array_expression = build_json_expression(data_column_reference, path_value);
|
|
42
|
+
const predicate_list = [];
|
|
43
|
+
|
|
44
|
+
if(elem_match_value instanceof RegExp) {
|
|
45
|
+
predicate_list.push(regex_operator("elem #>> '{}'", elem_match_value.source, elem_match_value.flags, parameter_state));
|
|
46
|
+
return elem_match_operator(array_expression, predicate_list.join(' AND '));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if(!is_object(elem_match_value)) {
|
|
50
|
+
predicate_list.push(eq_operator("elem #>> '{}'", elem_match_value, parameter_state));
|
|
51
|
+
return elem_match_operator(array_expression, predicate_list.join(' AND '));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if(has_operator_entries(elem_match_value)) {
|
|
55
|
+
const option_value = elem_match_value.$options || '';
|
|
56
|
+
const compile_operator_predicate = (operator_name, operator_value) => {
|
|
57
|
+
const predicate = compile_text_operator({
|
|
58
|
+
name: operator_name,
|
|
59
|
+
value: operator_value,
|
|
60
|
+
regex_options: option_value,
|
|
61
|
+
text_expression: "elem #>> '{}'",
|
|
62
|
+
parameter_state,
|
|
63
|
+
error_message: unsupported_elem_match_operator_message
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if(!predicate) {
|
|
67
|
+
throw new QueryError(unsupported_elem_match_operator_message + ': ' + operator_name, {
|
|
68
|
+
operator: operator_name
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return predicate;
|
|
73
|
+
};
|
|
74
|
+
const operator_predicates = compile_operator_entry_clauses(elem_match_value, compile_operator_predicate);
|
|
75
|
+
predicate_list.push.apply(predicate_list, operator_predicates);
|
|
76
|
+
|
|
77
|
+
return elem_match_operator(array_expression, predicate_list.join(' AND '));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const elem_entries = Object.entries(elem_match_value);
|
|
81
|
+
let entry_index = 0;
|
|
82
|
+
|
|
83
|
+
while(entry_index < elem_entries.length) {
|
|
84
|
+
const entry_pair = elem_entries[entry_index];
|
|
85
|
+
const nested_path = entry_pair[0];
|
|
86
|
+
const nested_value = entry_pair[1];
|
|
87
|
+
const nested_segments = split_dot_path(nested_path);
|
|
88
|
+
const elem_expression = build_elem_text_expression('elem', nested_segments);
|
|
89
|
+
|
|
90
|
+
if(nested_value instanceof RegExp) {
|
|
91
|
+
predicate_list.push(regex_operator(elem_expression, nested_value.source, nested_value.flags, parameter_state));
|
|
92
|
+
entry_index += 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if(is_object(nested_value) && has_operator_entries(nested_value)) {
|
|
97
|
+
const option_value = nested_value.$options || '';
|
|
98
|
+
const compile_nested_predicate = (operator_name, operator_value) => {
|
|
99
|
+
const predicate = compile_text_operator({
|
|
100
|
+
name: operator_name,
|
|
101
|
+
value: operator_value,
|
|
102
|
+
regex_options: option_value,
|
|
103
|
+
text_expression: elem_expression,
|
|
104
|
+
parameter_state,
|
|
105
|
+
error_message: unsupported_elem_match_operator_message
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if(!predicate) {
|
|
109
|
+
throw new QueryError(unsupported_elem_match_operator_message + ': ' + operator_name, {
|
|
110
|
+
operator: operator_name
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return predicate;
|
|
115
|
+
};
|
|
116
|
+
const nested_predicates = compile_operator_entry_clauses(nested_value, compile_nested_predicate);
|
|
117
|
+
predicate_list.push.apply(predicate_list, nested_predicates);
|
|
118
|
+
|
|
119
|
+
entry_index += 1;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
predicate_list.push(eq_operator(elem_expression, nested_value, parameter_state));
|
|
124
|
+
entry_index += 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return elem_match_operator(array_expression, predicate_list.join(' AND '));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
compile_elem_match_clause,
|
|
132
|
+
compile_nested_array_operator
|
|
133
|
+
};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile the SQL fragment for the $contains read-query operator.
|
|
4
|
+
*/
|
|
5
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
6
|
+
import {jsonb_stringify} from '#src/utils/json.js';
|
|
7
|
+
|
|
8
|
+
function contains_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
9
|
+
const placeholder = bind_parameter(parameter_state, jsonb_stringify(comparison_value));
|
|
10
|
+
return jsonb_expression + ' @> ' + placeholder + '::jsonb';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default contains_operator;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile the SQL fragment for the $elem-match read-query operator.
|
|
4
|
+
*/
|
|
5
|
+
function elem_match_operator(array_expression, predicate_expression) {
|
|
6
|
+
return 'EXISTS (SELECT 1 FROM jsonb_array_elements(' + array_expression + ') AS elem WHERE ' + predicate_expression + ')';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default elem_match_operator;
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile the SQL fragment for the $has-all-keys read-query operator.
|
|
4
|
+
*/
|
|
5
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
6
|
+
import {to_array} from '#src/utils/array.js';
|
|
7
|
+
|
|
8
|
+
function has_all_keys_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
9
|
+
const key_list = to_array(comparison_value).map(function map_key(key_value) {
|
|
10
|
+
return String(key_value);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const placeholder = bind_parameter(parameter_state, key_list);
|
|
14
|
+
return jsonb_expression + ' ?& ' + placeholder + '::text[]';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default has_all_keys_operator;
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile the SQL fragment for the $has-any-keys read-query operator.
|
|
4
|
+
*/
|
|
5
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
6
|
+
import {to_array} from '#src/utils/array.js';
|
|
7
|
+
|
|
8
|
+
function has_any_keys_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
9
|
+
const key_list = to_array(comparison_value).map(function map_key(key_value) {
|
|
10
|
+
return String(key_value);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const placeholder = bind_parameter(parameter_state, key_list);
|
|
14
|
+
|
|
15
|
+
return jsonb_expression + ' ?| ' + placeholder + '::text[]';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default has_any_keys_operator;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile the SQL fragment for the $has-key read-query operator.
|
|
4
|
+
*/
|
|
5
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
6
|
+
|
|
7
|
+
function has_key_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
8
|
+
const placeholder = bind_parameter(parameter_state, String(comparison_value));
|
|
9
|
+
return jsonb_expression + ' ? ' + placeholder;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default has_key_operator;
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile the SQL fragment for the $jsonpath-exists read-query operator.
|
|
4
|
+
*/
|
|
5
|
+
import QueryError from '#src/errors/query-error.js';
|
|
6
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
7
|
+
import {is_string} from '#src/utils/value.js';
|
|
8
|
+
|
|
9
|
+
function jsonpath_exists_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
10
|
+
if(!is_string(comparison_value) || comparison_value.trim() === '') {
|
|
11
|
+
throw new QueryError('Invalid value for $json_path_exists operator', {
|
|
12
|
+
operator: '$json_path_exists',
|
|
13
|
+
value: comparison_value
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const placeholder = bind_parameter(parameter_state, comparison_value);
|
|
18
|
+
|
|
19
|
+
return jsonb_expression + ' @? ' + placeholder + '::jsonpath';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default jsonpath_exists_operator;
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile the SQL fragment for the $jsonpath-match read-query operator.
|
|
4
|
+
*/
|
|
5
|
+
import QueryError from '#src/errors/query-error.js';
|
|
6
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
7
|
+
import {is_string} from '#src/utils/value.js';
|
|
8
|
+
|
|
9
|
+
function jsonpath_match_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
10
|
+
if(!is_string(comparison_value) || comparison_value.trim() === '') {
|
|
11
|
+
throw new QueryError('Invalid value for $json_path_match operator', {
|
|
12
|
+
operator: '$json_path_match',
|
|
13
|
+
value: comparison_value
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const placeholder = bind_parameter(parameter_state, comparison_value);
|
|
18
|
+
|
|
19
|
+
return jsonb_expression + ' @@ ' + placeholder + '::jsonpath';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default jsonpath_match_operator;
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
/*
|
|
2
|
+
* MODULE RESPONSIBILITY
|
|
3
|
+
* Compile the SQL fragment for the $size read-query operator.
|
|
4
|
+
*/
|
|
5
|
+
import QueryError from '#src/errors/query-error.js';
|
|
6
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
7
|
+
import {is_nan} from '#src/utils/value.js';
|
|
8
|
+
|
|
9
|
+
function size_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
10
|
+
if(is_nan(comparison_value)) {
|
|
11
|
+
throw new QueryError('Invalid value for $size operator', {
|
|
12
|
+
operator: '$size',
|
|
13
|
+
value: comparison_value
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const numeric_value = Number(comparison_value);
|
|
18
|
+
const placeholder = bind_parameter(parameter_state, numeric_value);
|
|
19
|
+
|
|
20
|
+
return 'jsonb_array_length(' + jsonb_expression + ') = ' + placeholder;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default size_operator;
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
return {
|
|
3
|
-
params: [],
|
|
4
|
-
current_index: start_index || 1
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const placeholder = '$' + parameter_state.current_index;
|
|
10
|
-
parameter_state.params.push(value);
|
|
11
|
-
parameter_state.current_index += 1;
|
|
12
|
-
return placeholder;
|
|
13
|
-
}
|
|
1
|
+
function create_parameter_state(start_index) {
|
|
2
|
+
return {
|
|
3
|
+
params: [],
|
|
4
|
+
current_index: start_index || 1
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function bind_parameter(parameter_state, value) {
|
|
9
|
+
const placeholder = '$' + parameter_state.current_index;
|
|
10
|
+
parameter_state.params.push(value);
|
|
11
|
+
parameter_state.current_index += 1;
|
|
12
|
+
return placeholder;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
create_parameter_state,
|
|
17
|
+
bind_parameter
|
|
18
|
+
};
|