jsonbadger 0.5.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/LICENSE +21 -0
- package/README.md +114 -0
- package/docs/api.md +152 -0
- package/docs/examples.md +612 -0
- package/docs/local-integration-testing.md +17 -0
- package/docs/query-translation.md +98 -0
- package/index.js +2 -0
- package/package.json +58 -0
- package/src/connection/connect.js +56 -0
- package/src/connection/disconnect.js +16 -0
- package/src/connection/pool-store.js +46 -0
- package/src/connection/server-capabilities.js +59 -0
- package/src/constants/defaults.js +20 -0
- package/src/constants/id-strategies.js +29 -0
- package/src/debug/debug-logger.js +15 -0
- package/src/errors/query-error.js +23 -0
- package/src/errors/validation-error.js +23 -0
- package/src/field-types/base-field-type.js +140 -0
- package/src/field-types/builtins/advanced.js +365 -0
- package/src/field-types/builtins/index.js +585 -0
- package/src/field-types/registry.js +122 -0
- package/src/index.js +36 -0
- package/src/migration/ensure-index.js +155 -0
- package/src/migration/ensure-schema.js +16 -0
- package/src/migration/ensure-table.js +31 -0
- package/src/migration/schema-indexes-resolver.js +6 -0
- package/src/model/document-instance.js +540 -0
- package/src/model/model-factory.js +555 -0
- package/src/query/limit-skip-compiler.js +31 -0
- package/src/query/operators/all.js +10 -0
- package/src/query/operators/contains.js +7 -0
- package/src/query/operators/elem-match.js +3 -0
- package/src/query/operators/eq.js +6 -0
- package/src/query/operators/gt.js +16 -0
- package/src/query/operators/gte.js +16 -0
- package/src/query/operators/has-all-keys.js +11 -0
- package/src/query/operators/has-any-keys.js +11 -0
- package/src/query/operators/has-key.js +6 -0
- package/src/query/operators/in.js +12 -0
- package/src/query/operators/index.js +60 -0
- package/src/query/operators/jsonpath-exists.js +15 -0
- package/src/query/operators/jsonpath-match.js +15 -0
- package/src/query/operators/lt.js +16 -0
- package/src/query/operators/lte.js +16 -0
- package/src/query/operators/ne.js +6 -0
- package/src/query/operators/nin.js +12 -0
- package/src/query/operators/regex.js +8 -0
- package/src/query/operators/size.js +16 -0
- package/src/query/path-parser.js +43 -0
- package/src/query/query-builder.js +93 -0
- package/src/query/sort-compiler.js +30 -0
- package/src/query/where-compiler.js +477 -0
- package/src/schema/field-definition-parser.js +218 -0
- package/src/schema/path-introspection.js +82 -0
- package/src/schema/schema-compiler.js +212 -0
- package/src/schema/schema.js +234 -0
- package/src/sql/parameter-binder.js +13 -0
- package/src/sql/sql-runner.js +31 -0
- package/src/utils/array.js +31 -0
- package/src/utils/assert.js +27 -0
- package/src/utils/json-safe.js +9 -0
- package/src/utils/json.js +21 -0
- package/src/utils/object-path.js +33 -0
- package/src/utils/object.js +168 -0
- package/src/utils/value.js +30 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
2
|
+
import {to_array} from '#src/utils/array.js';
|
|
3
|
+
|
|
4
|
+
export default function in_operator(sql_expression, comparison_value, parameter_state) {
|
|
5
|
+
const value_list = to_array(comparison_value);
|
|
6
|
+
const normalized_values = value_list.map(function map_value(current_value) {
|
|
7
|
+
return String(current_value);
|
|
8
|
+
});
|
|
9
|
+
const placeholder = bind_parameter(parameter_state, normalized_values);
|
|
10
|
+
|
|
11
|
+
return sql_expression + ' = ANY(' + placeholder + '::text[])';
|
|
12
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import all_operator from './all.js';
|
|
2
|
+
import contains_operator from './contains.js';
|
|
3
|
+
import elem_match_operator from './elem-match.js';
|
|
4
|
+
import eq_operator from './eq.js';
|
|
5
|
+
import gt_operator from './gt.js';
|
|
6
|
+
import gte_operator from './gte.js';
|
|
7
|
+
import has_all_keys_operator from './has-all-keys.js';
|
|
8
|
+
import has_any_keys_operator from './has-any-keys.js';
|
|
9
|
+
import has_key_operator from './has-key.js';
|
|
10
|
+
import in_operator from './in.js';
|
|
11
|
+
import jsonpath_exists_operator from './jsonpath-exists.js';
|
|
12
|
+
import jsonpath_match_operator from './jsonpath-match.js';
|
|
13
|
+
import lt_operator from './lt.js';
|
|
14
|
+
import lte_operator from './lte.js';
|
|
15
|
+
import ne_operator from './ne.js';
|
|
16
|
+
import nin_operator from './nin.js';
|
|
17
|
+
import regex_operator from './regex.js';
|
|
18
|
+
import size_operator from './size.js';
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
all_operator,
|
|
22
|
+
contains_operator,
|
|
23
|
+
elem_match_operator,
|
|
24
|
+
eq_operator,
|
|
25
|
+
gt_operator,
|
|
26
|
+
gte_operator,
|
|
27
|
+
has_all_keys_operator,
|
|
28
|
+
has_any_keys_operator,
|
|
29
|
+
has_key_operator,
|
|
30
|
+
in_operator,
|
|
31
|
+
jsonpath_exists_operator,
|
|
32
|
+
jsonpath_match_operator,
|
|
33
|
+
lt_operator,
|
|
34
|
+
lte_operator,
|
|
35
|
+
ne_operator,
|
|
36
|
+
nin_operator,
|
|
37
|
+
regex_operator,
|
|
38
|
+
size_operator
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
all_operator,
|
|
43
|
+
contains_operator,
|
|
44
|
+
elem_match_operator,
|
|
45
|
+
eq_operator,
|
|
46
|
+
gt_operator,
|
|
47
|
+
gte_operator,
|
|
48
|
+
has_all_keys_operator,
|
|
49
|
+
has_any_keys_operator,
|
|
50
|
+
has_key_operator,
|
|
51
|
+
in_operator,
|
|
52
|
+
jsonpath_exists_operator,
|
|
53
|
+
jsonpath_match_operator,
|
|
54
|
+
lt_operator,
|
|
55
|
+
lte_operator,
|
|
56
|
+
ne_operator,
|
|
57
|
+
nin_operator,
|
|
58
|
+
regex_operator,
|
|
59
|
+
size_operator
|
|
60
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import QueryError from '#src/errors/query-error.js';
|
|
2
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
3
|
+
import {is_string} from '#src/utils/value.js';
|
|
4
|
+
|
|
5
|
+
export default function jsonpath_exists_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
6
|
+
if(!is_string(comparison_value) || comparison_value.trim() === '') {
|
|
7
|
+
throw new QueryError('Invalid value for $json_path_exists operator', {
|
|
8
|
+
operator: '$json_path_exists',
|
|
9
|
+
value: comparison_value
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const placeholder = bind_parameter(parameter_state, comparison_value);
|
|
14
|
+
return jsonb_expression + ' @? ' + placeholder + '::jsonpath';
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import QueryError from '#src/errors/query-error.js';
|
|
2
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
3
|
+
import {is_string} from '#src/utils/value.js';
|
|
4
|
+
|
|
5
|
+
export default function jsonpath_match_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
6
|
+
if(!is_string(comparison_value) || comparison_value.trim() === '') {
|
|
7
|
+
throw new QueryError('Invalid value for $json_path_match operator', {
|
|
8
|
+
operator: '$json_path_match',
|
|
9
|
+
value: comparison_value
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const placeholder = bind_parameter(parameter_state, comparison_value);
|
|
14
|
+
return jsonb_expression + ' @@ ' + placeholder + '::jsonpath';
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import QueryError from '#src/errors/query-error.js';
|
|
2
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
3
|
+
import {is_nan} from '#src/utils/value.js';
|
|
4
|
+
|
|
5
|
+
export default function lt_operator(sql_expression, comparison_value, parameter_state) {
|
|
6
|
+
if(is_nan(comparison_value)) {
|
|
7
|
+
throw new QueryError('Invalid value for $lt operator', {
|
|
8
|
+
operator: '$lt',
|
|
9
|
+
value: comparison_value
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const numeric_value = typeof comparison_value === 'bigint' ? comparison_value.toString() : Number(comparison_value);
|
|
14
|
+
const placeholder = bind_parameter(parameter_state, numeric_value);
|
|
15
|
+
return '(' + sql_expression + ')::numeric < ' + placeholder;
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import QueryError from '#src/errors/query-error.js';
|
|
2
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
3
|
+
import {is_nan} from '#src/utils/value.js';
|
|
4
|
+
|
|
5
|
+
export default function lte_operator(sql_expression, comparison_value, parameter_state) {
|
|
6
|
+
if(is_nan(comparison_value)) {
|
|
7
|
+
throw new QueryError('Invalid value for $lte operator', {
|
|
8
|
+
operator: '$lte',
|
|
9
|
+
value: comparison_value
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const numeric_value = typeof comparison_value === 'bigint' ? comparison_value.toString() : Number(comparison_value);
|
|
14
|
+
const placeholder = bind_parameter(parameter_state, numeric_value);
|
|
15
|
+
return '(' + sql_expression + ')::numeric <= ' + placeholder;
|
|
16
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
2
|
+
|
|
3
|
+
export default function ne_operator(sql_expression, comparison_value, parameter_state) {
|
|
4
|
+
const placeholder = bind_parameter(parameter_state, String(comparison_value));
|
|
5
|
+
return sql_expression + ' != ' + placeholder;
|
|
6
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
2
|
+
import {to_array} from '#src/utils/array.js';
|
|
3
|
+
|
|
4
|
+
export default function nin_operator(sql_expression, comparison_value, parameter_state) {
|
|
5
|
+
const value_list = to_array(comparison_value);
|
|
6
|
+
const normalized_values = value_list.map(function map_value(current_value) {
|
|
7
|
+
return String(current_value);
|
|
8
|
+
});
|
|
9
|
+
const placeholder = bind_parameter(parameter_state, normalized_values);
|
|
10
|
+
|
|
11
|
+
return 'NOT (' + sql_expression + ' = ANY(' + placeholder + '::text[]))';
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
2
|
+
|
|
3
|
+
export default function regex_operator(sql_expression, pattern_value, option_value, parameter_state) {
|
|
4
|
+
const regex_operator_value = option_value && option_value.indexOf('i') >= 0 ? '~*' : '~';
|
|
5
|
+
const placeholder = bind_parameter(parameter_state, String(pattern_value));
|
|
6
|
+
|
|
7
|
+
return sql_expression + ' ' + regex_operator_value + ' ' + placeholder;
|
|
8
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import QueryError from '#src/errors/query-error.js';
|
|
2
|
+
import {bind_parameter} from '#src/sql/parameter-binder.js';
|
|
3
|
+
import {is_nan} from '#src/utils/value.js';
|
|
4
|
+
|
|
5
|
+
export default function size_operator(jsonb_expression, comparison_value, parameter_state) {
|
|
6
|
+
if(is_nan(comparison_value)) {
|
|
7
|
+
throw new QueryError('Invalid value for $size operator', {
|
|
8
|
+
operator: '$size',
|
|
9
|
+
value: comparison_value
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const numeric_value = Number(comparison_value);
|
|
14
|
+
const placeholder = bind_parameter(parameter_state, numeric_value);
|
|
15
|
+
return 'jsonb_array_length(' + jsonb_expression + ') = ' + placeholder;
|
|
16
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {assert_path} from '#src/utils/assert.js';
|
|
2
|
+
import {build_path_literal, split_dot_path} from '#src/utils/object-path.js';
|
|
3
|
+
|
|
4
|
+
export function build_text_expression(column_reference, path_value) {
|
|
5
|
+
const path_segments = split_dot_path(path_value);
|
|
6
|
+
|
|
7
|
+
if(path_segments.length === 1) {
|
|
8
|
+
return column_reference + " ->> '" + path_segments[0] + "'";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return column_reference + " #>> '" + build_path_literal(path_segments) + "'";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function build_json_expression(column_reference, path_value) {
|
|
15
|
+
const path_segments = split_dot_path(path_value);
|
|
16
|
+
|
|
17
|
+
if(path_segments.length === 1) {
|
|
18
|
+
return column_reference + " -> '" + path_segments[0] + "'";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return column_reference + " #> '" + build_path_literal(path_segments) + "'";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function build_elem_text_expression(elem_alias, path_segments) {
|
|
25
|
+
if(path_segments.length === 1) {
|
|
26
|
+
return elem_alias + "->>'" + path_segments[0] + "'";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return elem_alias + " #>> '" + build_path_literal(path_segments) + "'";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function parse_path(path_value) {
|
|
33
|
+
assert_path(path_value, 'path');
|
|
34
|
+
|
|
35
|
+
const path_segments = split_dot_path(path_value);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
path_segments: path_segments,
|
|
39
|
+
root_path: path_segments[0],
|
|
40
|
+
child_segments: path_segments.slice(1),
|
|
41
|
+
is_nested: path_segments.length > 1
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import limit_skip_compiler from '#src/query/limit-skip-compiler.js';
|
|
2
|
+
import sort_compiler from '#src/query/sort-compiler.js';
|
|
3
|
+
import where_compiler from '#src/query/where-compiler.js';
|
|
4
|
+
|
|
5
|
+
import sql_runner from '#src/sql/sql-runner.js';
|
|
6
|
+
|
|
7
|
+
import {quote_identifier} from '#src/utils/assert.js';
|
|
8
|
+
|
|
9
|
+
export default function QueryBuilder(model_constructor, operation_name, query_filter, projection_value) {
|
|
10
|
+
this.model_constructor = model_constructor;
|
|
11
|
+
this.operation_name = operation_name;
|
|
12
|
+
this.base_filter = query_filter || {};
|
|
13
|
+
this.where_filter = {};
|
|
14
|
+
this.sort_definition = null;
|
|
15
|
+
this.limit_count = null;
|
|
16
|
+
this.skip_count = null;
|
|
17
|
+
this.projection_value = projection_value || null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
QueryBuilder.prototype.where = function (extra_filter) {
|
|
21
|
+
this.where_filter = Object.assign({}, this.where_filter, extra_filter || {});
|
|
22
|
+
return this;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
QueryBuilder.prototype.sort = function (sort_definition) {
|
|
26
|
+
this.sort_definition = sort_definition || null;
|
|
27
|
+
return this;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
QueryBuilder.prototype.limit = function (limit_value) {
|
|
31
|
+
this.limit_count = limit_value;
|
|
32
|
+
return this;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
QueryBuilder.prototype.skip = function (skip_value) {
|
|
36
|
+
this.skip_count = skip_value;
|
|
37
|
+
return this;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
QueryBuilder.prototype.exec = async function () {
|
|
41
|
+
const schema_instance = this.model_constructor.schema_instance;
|
|
42
|
+
const model_options = this.model_constructor.model_options;
|
|
43
|
+
const table_name = model_options.table_name;
|
|
44
|
+
const data_column = model_options.data_column;
|
|
45
|
+
const table_identifier = quote_identifier(table_name);
|
|
46
|
+
const data_identifier = quote_identifier(data_column);
|
|
47
|
+
const merged_filter = Object.assign({}, this.base_filter, this.where_filter);
|
|
48
|
+
const where_result = where_compiler(merged_filter, {
|
|
49
|
+
data_column: data_column,
|
|
50
|
+
schema: schema_instance
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if(this.operation_name === 'count_documents') {
|
|
55
|
+
const count_sql = 'SELECT COUNT(*)::int AS total_count FROM ' + table_identifier + ' WHERE ' + where_result.sql;
|
|
56
|
+
const count_result = await sql_runner(count_sql, where_result.params);
|
|
57
|
+
|
|
58
|
+
if(count_result.rows.length === 0) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Number(count_result.rows[0].total_count);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let sql_text = 'SELECT ' + data_identifier + ' AS data FROM ' + table_identifier + ' WHERE ' + where_result.sql;
|
|
66
|
+
const sort_sql = sort_compiler(this.sort_definition, {
|
|
67
|
+
data_column: data_column
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
sql_text += sort_sql;
|
|
71
|
+
|
|
72
|
+
if(this.operation_name === 'find_one') {
|
|
73
|
+
if(this.limit_count === null || this.limit_count === undefined) {
|
|
74
|
+
this.limit_count = 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
sql_text += limit_skip_compiler(this.limit_count, this.skip_count);
|
|
79
|
+
|
|
80
|
+
const query_result = await sql_runner(sql_text, where_result.params);
|
|
81
|
+
|
|
82
|
+
if(this.operation_name === 'find_one') {
|
|
83
|
+
if(query_result.rows.length === 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return query_result.rows[0].data;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return query_result.rows.map(function map_row(row_value) {
|
|
91
|
+
return row_value.data;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {quote_identifier} from '#src/utils/assert.js';
|
|
2
|
+
import {build_text_expression} from '#src/query/path-parser.js';
|
|
3
|
+
|
|
4
|
+
export default function sort_compiler(sort_definition, compile_options) {
|
|
5
|
+
if(!sort_definition || typeof sort_definition !== 'object') {
|
|
6
|
+
return '';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const data_column_name = compile_options?.data_column ?? 'data';
|
|
10
|
+
const data_column_reference = quote_identifier(data_column_name);
|
|
11
|
+
const sort_entries = Object.entries(sort_definition);
|
|
12
|
+
const sort_clauses = [];
|
|
13
|
+
let entry_index = 0;
|
|
14
|
+
|
|
15
|
+
while(entry_index < sort_entries.length) {
|
|
16
|
+
const sort_entry = sort_entries[entry_index];
|
|
17
|
+
const path_value = sort_entry[0];
|
|
18
|
+
const direction_value = Number(sort_entry[1]) === -1 ? 'DESC' : 'ASC';
|
|
19
|
+
const text_expression = build_text_expression(data_column_reference, path_value);
|
|
20
|
+
|
|
21
|
+
sort_clauses.push(text_expression + ' ' + direction_value);
|
|
22
|
+
entry_index += 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if(sort_clauses.length === 0) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return ' ORDER BY ' + sort_clauses.join(', ');
|
|
30
|
+
}
|