objection 3.1.5 → 3.3.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 +16 -16
- package/lib/model/AjvValidator.js +173 -18
- package/lib/model/Model.js +3 -1
- package/lib/model/graph/ModelGraphNode.js +12 -0
- package/lib/model/modelToJson.js +2 -1
- package/lib/queryBuilder/QueryBuilder.js +339 -47
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
- package/lib/queryBuilder/ReferenceBuilder.js +5 -3
- package/lib/queryBuilder/RelationExpression.js +21 -9
- package/lib/queryBuilder/StaticHookArguments.js +12 -1
- package/lib/queryBuilder/graph/GraphFetcher.js +3 -1
- package/lib/queryBuilder/graph/GraphOptions.js +21 -2
- package/lib/queryBuilder/graph/GraphUpsert.js +52 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +20 -14
- package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +113 -8
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +10 -1
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +15 -3
- package/lib/queryBuilder/join/JoinResultParser.js +22 -0
- package/lib/queryBuilder/join/RelationJoiner.js +71 -5
- package/lib/queryBuilder/operations/DelegateOperation.js +4 -0
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
- package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
- package/lib/queryBuilder/operations/InsertOperation.js +121 -12
- package/lib/queryBuilder/operations/NoneOperation.js +37 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +5 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +20 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
- package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +7 -5
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +4 -2
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +9 -20
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +45 -32
- package/lib/queryBuilder/operations/select/Selection.js +95 -4
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +9 -0
- package/lib/queryBuilder/whereGroups.js +125 -0
- package/lib/relations/Relation.js +5 -2
- package/lib/relations/RelationProperty.js +7 -3
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +1 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +5 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +6 -2
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
- package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +70 -48
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +6 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +8 -2
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
- package/lib/utils/deprecate.js +6 -0
- package/lib/utils/identifierMapping.js +23 -3
- package/lib/utils/objectUtils.js +9 -5
- package/lib/utils/parseFieldExpression.js +28 -0
- package/package.json +22 -14
- package/typings/objection/index.d.ts +339 -68
|
@@ -15,7 +15,6 @@ class WhereInEagerOperation extends EagerOperation {
|
|
|
15
15
|
super(name, opt);
|
|
16
16
|
|
|
17
17
|
this.relationsToFetch = [];
|
|
18
|
-
this.omitProps = [];
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
batchSize(knex) {
|
|
@@ -41,7 +40,7 @@ class WhereInEagerOperation extends EagerOperation {
|
|
|
41
40
|
onBuild(builder) {
|
|
42
41
|
const relationsToFetch = findRelationsToFetch(
|
|
43
42
|
builder.modelClass(),
|
|
44
|
-
this.buildFinalExpression(),
|
|
43
|
+
this.buildFinalExpression(builder),
|
|
45
44
|
);
|
|
46
45
|
|
|
47
46
|
const { selectionsToAdd, selectedProps } = findRelationPropsToSelect(builder, relationsToFetch);
|
|
@@ -50,8 +49,15 @@ class WhereInEagerOperation extends EagerOperation {
|
|
|
50
49
|
builder.select(selectionsToAdd);
|
|
51
50
|
}
|
|
52
51
|
|
|
52
|
+
// Omit the implicitly selected columns from the result once the relations
|
|
53
|
+
// have been fetched.
|
|
54
|
+
if (!builder.internalOptions().keepImplicitJoinProps) {
|
|
55
|
+
for (const prop of selectedProps) {
|
|
56
|
+
builder.addInternalSelection(prop);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
this.relationsToFetch = relationsToFetch;
|
|
54
|
-
this.omitProps = selectedProps;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
async onAfter2(builder, result) {
|
|
@@ -74,22 +80,6 @@ class WhereInEagerOperation extends EagerOperation {
|
|
|
74
80
|
{ concurrency: modelClass.getConcurrency(builder.unsafeKnex()) },
|
|
75
81
|
);
|
|
76
82
|
|
|
77
|
-
const intOpt = builder.internalOptions();
|
|
78
|
-
|
|
79
|
-
if (!this.omitProps.length || intOpt.keepImplicitJoinProps) {
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Now that relations have been fetched for `models` we can omit the
|
|
84
|
-
// columns that were implicitly selected by this class.
|
|
85
|
-
for (let i = 0, l = result.length; i < l; ++i) {
|
|
86
|
-
const model = result[i];
|
|
87
|
-
|
|
88
|
-
for (let c = 0, lc = this.omitProps.length; c < lc; ++c) {
|
|
89
|
-
modelClass.omitImpl(model, this.omitProps[c]);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
83
|
return result;
|
|
94
84
|
}
|
|
95
85
|
|
|
@@ -204,7 +194,6 @@ class WhereInEagerOperation extends EagerOperation {
|
|
|
204
194
|
const clone = super.clone();
|
|
205
195
|
|
|
206
196
|
clone.relationsToFetch = this.relationsToFetch.slice();
|
|
207
|
-
clone.omitProps = this.omitProps.slice();
|
|
208
197
|
|
|
209
198
|
return clone;
|
|
210
199
|
}
|
|
@@ -7,7 +7,7 @@ class WhereJsonHasPostgresOperation extends ObjectionToKnexConvertingOperation {
|
|
|
7
7
|
onBuildKnex(knexBuilder, builder) {
|
|
8
8
|
const args = this.getKnexArgs(builder);
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const rawArgs = jsonApi.whereJsonFieldRightStringArrayOnLeftQuery(
|
|
11
11
|
builder.knex(),
|
|
12
12
|
args[0],
|
|
13
13
|
this.opt.operator,
|
|
@@ -15,9 +15,9 @@ class WhereJsonHasPostgresOperation extends ObjectionToKnexConvertingOperation {
|
|
|
15
15
|
);
|
|
16
16
|
|
|
17
17
|
if (this.opt.bool === 'or') {
|
|
18
|
-
knexBuilder = knexBuilder.orWhereRaw(
|
|
18
|
+
knexBuilder = knexBuilder.orWhereRaw(...rawArgs);
|
|
19
19
|
} else {
|
|
20
|
-
knexBuilder = knexBuilder.whereRaw(
|
|
20
|
+
knexBuilder = knexBuilder.whereRaw(...rawArgs);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
return knexBuilder;
|
|
@@ -18,7 +18,7 @@ class WhereJsonNotObjectPostgresOperation extends ObjectionToKnexConvertingOpera
|
|
|
18
18
|
'not',
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
-
builder.orWhereRaw(jsonApi.whereJsonFieldQuery(knex, fieldExpression, 'IS', null));
|
|
21
|
+
builder.orWhereRaw(...jsonApi.whereJsonFieldQuery(knex, fieldExpression, 'IS', null));
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
if (this.opt.bool === 'or') {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const parser = require('../../../utils/parseFieldExpression');
|
|
4
4
|
const { asArray, isObject, isString } = require('../../../utils/objectUtils');
|
|
5
|
+
const { isKnexRaw, isKnexQueryBuilder } = require('../../../utils/knexUtils');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {String} FieldExpression
|
|
@@ -38,16 +39,24 @@ const { asArray, isObject, isString } = require('../../../utils/objectUtils');
|
|
|
38
39
|
* 4. Keys containing square brackets and quotes
|
|
39
40
|
* `objectColumn:['Double."Quote".[]']` and `objectColumn:["Sinlge.'Quote'.[]"]`
|
|
40
41
|
* Column `{ "Double.\"Quote\".[]" : "I was referred", "Single.'Quote'.[]" : "Mee too!" }`
|
|
42
|
+
* 5. Empty keys `objectColumn:[""]` or `objectColumn:['']` Column `{ "" : "I was referred" }`
|
|
41
43
|
* 99. Keys containing dots, square brackets, single quotes and double quotes in one json key is
|
|
42
44
|
* not currently supported
|
|
43
45
|
*/
|
|
44
46
|
|
|
47
|
+
// Returns the SQL and bindings of a field expression. The column is passed as an
|
|
48
|
+
// identifier binding (`??`), so that knex's `wrapIdentifier` (e.g. used by
|
|
49
|
+
// `knexSnakeCaseMappers()`) is applied to it.
|
|
45
50
|
function parseFieldExpression(expression, extractAsText) {
|
|
46
51
|
let parsed = parser.parseFieldExpression(expression);
|
|
47
|
-
let
|
|
52
|
+
let bindings = [parsed.columnName];
|
|
53
|
+
// Reference the bare column when there is no json path, so that Postgres can
|
|
54
|
+
// use indexes on it. `#>> '{}'` is still needed to extract the value as text.
|
|
55
|
+
if (parsed.access.length === 0 && !extractAsText) {
|
|
56
|
+
return ['??', bindings];
|
|
57
|
+
}
|
|
48
58
|
let extractor = extractAsText ? '#>>' : '#>';
|
|
49
|
-
|
|
50
|
-
return `"${middleQuotedColumnName}"${extractor}'{${jsonRefs}}'`;
|
|
59
|
+
return [`??${extractor}${parser.toPostgresJsonPathLiteral(parsed.access)}`, bindings];
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
function whereJsonbRefOnLeftJsonbValOrRefOnRight(
|
|
@@ -72,36 +81,36 @@ function whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams(
|
|
|
72
81
|
jsonObjectOrFieldExpression,
|
|
73
82
|
queryPrefix,
|
|
74
83
|
) {
|
|
75
|
-
let fieldReference = parseFieldExpression(fieldExpression);
|
|
84
|
+
let [fieldReference, bindings] = parseFieldExpression(fieldExpression);
|
|
85
|
+
let [rightHandSql, rightHandBindings] = rightHandExpression(jsonObjectOrFieldExpression);
|
|
86
|
+
let query = ['(', fieldReference, ')::jsonb', operator, rightHandSql];
|
|
87
|
+
|
|
88
|
+
if (queryPrefix) {
|
|
89
|
+
query.unshift(queryPrefix);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return [query.join(' '), [...bindings, ...rightHandBindings]];
|
|
93
|
+
}
|
|
76
94
|
|
|
95
|
+
function rightHandExpression(jsonObjectOrFieldExpression) {
|
|
77
96
|
if (isString(jsonObjectOrFieldExpression)) {
|
|
78
|
-
let rightHandReference = parseFieldExpression(jsonObjectOrFieldExpression);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
')::jsonb',
|
|
87
|
-
];
|
|
88
|
-
if (queryPrefix) {
|
|
89
|
-
refRefQuery.unshift(queryPrefix);
|
|
90
|
-
}
|
|
91
|
-
return [refRefQuery.join(' ')];
|
|
97
|
+
let [rightHandReference, bindings] = parseFieldExpression(jsonObjectOrFieldExpression);
|
|
98
|
+
return [`( ${rightHandReference} )::jsonb`, bindings];
|
|
99
|
+
} else if (
|
|
100
|
+
isKnexRaw(jsonObjectOrFieldExpression) ||
|
|
101
|
+
isKnexQueryBuilder(jsonObjectOrFieldExpression)
|
|
102
|
+
) {
|
|
103
|
+
// ref(), val(), raw() and subqueries, already converted to knex by the operation.
|
|
104
|
+
return ['( ? )::jsonb', [jsonObjectOrFieldExpression]];
|
|
92
105
|
} else if (isObject(jsonObjectOrFieldExpression)) {
|
|
93
|
-
|
|
94
|
-
if (queryPrefix) {
|
|
95
|
-
refValQuery.unshift(queryPrefix);
|
|
96
|
-
}
|
|
97
|
-
return [refValQuery.join(' '), JSON.stringify(jsonObjectOrFieldExpression)];
|
|
106
|
+
return ['?::jsonb', [JSON.stringify(jsonObjectOrFieldExpression)]];
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
throw new Error('Invalid right hand expression.');
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
function whereJsonFieldRightStringArrayOnLeftQuery(knex, fieldExpression, operator, keys) {
|
|
104
|
-
let fieldReference = parseFieldExpression(fieldExpression);
|
|
113
|
+
let [fieldReference, bindings] = parseFieldExpression(fieldExpression);
|
|
105
114
|
keys = asArray(keys);
|
|
106
115
|
|
|
107
116
|
let questionMarksArray = keys.map((key) => {
|
|
@@ -111,19 +120,19 @@ function whereJsonFieldRightStringArrayOnLeftQuery(knex, fieldExpression, operat
|
|
|
111
120
|
return '?';
|
|
112
121
|
});
|
|
113
122
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
return [
|
|
124
|
+
`${fieldReference} ${operator.replace('?', '\\?')} array[${questionMarksArray.join(',')}]`,
|
|
125
|
+
[...bindings, ...keys],
|
|
126
|
+
];
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
function whereJsonFieldQuery(knex, fieldExpression, operator, value) {
|
|
121
|
-
let fieldReference = parseFieldExpression(fieldExpression, true);
|
|
130
|
+
let [fieldReference, bindings] = parseFieldExpression(fieldExpression, true);
|
|
122
131
|
let normalizedOperator = normalizeOperator(knex, operator);
|
|
123
132
|
|
|
124
133
|
// json type comparison takes json type in string format
|
|
125
134
|
let cast;
|
|
126
|
-
let
|
|
135
|
+
let valuePlaceholder = '?';
|
|
127
136
|
let type = typeof value;
|
|
128
137
|
|
|
129
138
|
if (type === 'number') {
|
|
@@ -134,12 +143,16 @@ function whereJsonFieldQuery(knex, fieldExpression, operator, value) {
|
|
|
134
143
|
cast = '::TEXT';
|
|
135
144
|
} else if (value === null) {
|
|
136
145
|
cast = '::TEXT';
|
|
137
|
-
|
|
146
|
+
valuePlaceholder = 'NULL';
|
|
138
147
|
} else {
|
|
139
148
|
throw new Error('Value must be string, number, boolean or null.');
|
|
140
149
|
}
|
|
141
150
|
|
|
142
|
-
|
|
151
|
+
// Bind the value so that `?` in it isn't read as a binding placeholder.
|
|
152
|
+
return [
|
|
153
|
+
`(${fieldReference})${cast} ${normalizedOperator} ${valuePlaceholder}`,
|
|
154
|
+
value === null ? bindings : [...bindings, value],
|
|
155
|
+
];
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
function normalizeOperator(knex, operator) {
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isString, isObject } = require('../../../utils/objectUtils');
|
|
3
|
+
const { isString, isObject, isPlainObject } = require('../../../utils/objectUtils');
|
|
4
|
+
const { isKnexQueryBuilder, isKnexRaw } = require('../../../utils/knexUtils');
|
|
4
5
|
|
|
5
6
|
const ALIAS_REGEX = /\s+as\s+/i;
|
|
6
7
|
|
|
8
|
+
// Matches an alias at the end of a raw sql string: `as "alias"`, `as \`alias\``,
|
|
9
|
+
// `as [alias]`, `as ??`, `as :name:` or `as alias`.
|
|
10
|
+
const RAW_ALIAS_REGEX =
|
|
11
|
+
/\s+as\s+(?:"([^"]+)"|`([^`]+)`|\[([^\]]+)\]|(\?\?)|:(\w+):|([a-z_][\w$]*))\s*$/i;
|
|
12
|
+
|
|
7
13
|
class Selection {
|
|
8
14
|
constructor(table, column, alias) {
|
|
9
15
|
this.table = table;
|
|
10
16
|
this.column = column;
|
|
11
17
|
this.alias = alias;
|
|
18
|
+
this.keepsSelectAll = false;
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
get name() {
|
|
@@ -23,6 +30,12 @@ class Selection {
|
|
|
23
30
|
return createSelectionFromReference(selection);
|
|
24
31
|
} else if (selection.isObjectionRawBuilder) {
|
|
25
32
|
return createSelectionFromRaw(selection);
|
|
33
|
+
} else if (selection.isObjectionQueryBuilderBase) {
|
|
34
|
+
return createSelectionFromQueryBuilder(selection);
|
|
35
|
+
} else if (isKnexQueryBuilder(selection)) {
|
|
36
|
+
return createSelectionFromAlias(selection._single.as);
|
|
37
|
+
} else if (isKnexRaw(selection)) {
|
|
38
|
+
return createSelectionFromAlias(parseRawAlias(selection.sql, selection.bindings));
|
|
26
39
|
} else {
|
|
27
40
|
return null;
|
|
28
41
|
}
|
|
@@ -50,10 +63,11 @@ class Selection {
|
|
|
50
63
|
if (selectionInBuilder.column === '*') {
|
|
51
64
|
if (selectionInBuilder.table) {
|
|
52
65
|
if (selectionToTest.column === '*') {
|
|
53
|
-
return selectionToTest.table
|
|
66
|
+
return isSameTable(builder, selectionToTest.table, selectionInBuilder.table);
|
|
54
67
|
} else {
|
|
55
68
|
return (
|
|
56
|
-
selectionToTest.table === null ||
|
|
69
|
+
selectionToTest.table === null ||
|
|
70
|
+
isSameTable(builder, selectionToTest.table, selectionInBuilder.table)
|
|
57
71
|
);
|
|
58
72
|
}
|
|
59
73
|
} else {
|
|
@@ -67,7 +81,8 @@ class Selection {
|
|
|
67
81
|
} else {
|
|
68
82
|
return (
|
|
69
83
|
selectionToTest.column === selectionInBuilder.column &&
|
|
70
|
-
(selectionToTest.table === null ||
|
|
84
|
+
(selectionToTest.table === null ||
|
|
85
|
+
isSameTable(builder, selectionToTest.table, selectionInBuilderTable))
|
|
71
86
|
);
|
|
72
87
|
}
|
|
73
88
|
}
|
|
@@ -82,6 +97,36 @@ Object.defineProperties(Selection.prototype, {
|
|
|
82
97
|
},
|
|
83
98
|
});
|
|
84
99
|
|
|
100
|
+
// Table names that only differ in a way that knex's `wrapIdentifier` mapping
|
|
101
|
+
// (for example `knexSnakeCaseMappers()`) cancels out refer to the same table:
|
|
102
|
+
// `dataExtension` and `data_extension` both end up as `data_extension`.
|
|
103
|
+
function isSameTable(builder, table1, table2) {
|
|
104
|
+
if (table1 === table2) {
|
|
105
|
+
return true;
|
|
106
|
+
} else if (!table1 || !table2) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const knex = builder.unsafeKnex();
|
|
111
|
+
const wrapIdentifier = knex && knex.client && knex.client.config.wrapIdentifier;
|
|
112
|
+
|
|
113
|
+
if (!wrapIdentifier) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const mapTable = (table) =>
|
|
118
|
+
table
|
|
119
|
+
.split('.')
|
|
120
|
+
.map((part) => wrapIdentifier(part, identity))
|
|
121
|
+
.join('.');
|
|
122
|
+
|
|
123
|
+
return mapTable(table1) === mapTable(table2);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function identity(value) {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
|
|
85
130
|
function createSelectionFromReference(ref) {
|
|
86
131
|
return new Selection(ref.tableName, ref.column, ref.alias);
|
|
87
132
|
}
|
|
@@ -89,6 +134,52 @@ function createSelectionFromReference(ref) {
|
|
|
89
134
|
function createSelectionFromRaw(raw) {
|
|
90
135
|
if (raw.alias) {
|
|
91
136
|
return new Selection(null, null, raw.alias);
|
|
137
|
+
} else {
|
|
138
|
+
return createSelectionFromAlias(parseRawAlias(raw._sql, raw._args));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseRawAlias(sql, bindings) {
|
|
143
|
+
const match = isString(sql) && RAW_ALIAS_REGEX.exec(sql);
|
|
144
|
+
|
|
145
|
+
if (!match) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const [, doubleQuoted, backQuoted, bracketQuoted, binding, namedBinding, unquoted] = match;
|
|
150
|
+
|
|
151
|
+
if (binding) {
|
|
152
|
+
return Array.isArray(bindings) ? bindings[bindings.length - 1] : null;
|
|
153
|
+
} else if (namedBinding) {
|
|
154
|
+
if (Array.isArray(bindings) && bindings.length === 1) {
|
|
155
|
+
// `raw(sql, { name: value })` of objection.
|
|
156
|
+
bindings = bindings[0];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return isPlainObject(bindings) ? bindings[namedBinding] : null;
|
|
160
|
+
} else if (unquoted) {
|
|
161
|
+
// Some databases, like postgres, fold unquoted identifiers to lower case
|
|
162
|
+
// and others don't, so only lower case ones can be used reliably.
|
|
163
|
+
return unquoted === unquoted.toLowerCase() ? unquoted : null;
|
|
164
|
+
} else {
|
|
165
|
+
return doubleQuoted || backQuoted || bracketQuoted;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Subqueries aliased using `as()` select a column with the alias as name.
|
|
170
|
+
function createSelectionFromQueryBuilder(builder) {
|
|
171
|
+
const asOperation = builder.findLastOperation('as');
|
|
172
|
+
return createSelectionFromAlias(asOperation && asOperation.args[0]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function createSelectionFromAlias(alias) {
|
|
176
|
+
if (isString(alias)) {
|
|
177
|
+
const selection = new Selection(null, null, alias);
|
|
178
|
+
// These selections weren't recognized in the past. To not change which
|
|
179
|
+
// other columns `withGraphJoined` selects, they don't count as explicit
|
|
180
|
+
// selections when deciding whether all columns are selected.
|
|
181
|
+
selection.keepsSelectAll = true;
|
|
182
|
+
return selection;
|
|
92
183
|
} else {
|
|
93
184
|
return null;
|
|
94
185
|
}
|
|
@@ -26,6 +26,12 @@ class WhereInCompositeMsSqlOperation extends ObjectionToKnexConvertingOperation
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
buildComposite(knex, knexBuilder, columns, values) {
|
|
29
|
+
if (Array.isArray(values) && values.length === 0) {
|
|
30
|
+
// knex turns an empty `whereIn` into `1 = 0` and an empty `whereNotIn`
|
|
31
|
+
// into `1 = 1`.
|
|
32
|
+
return this.whereIn(knexBuilder, columns, []);
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
const helperColumns = columns.map((_, index) => getTempColumn(index));
|
|
30
36
|
|
|
31
37
|
if (Array.isArray(values)) {
|
|
@@ -48,7 +48,11 @@ function buildCompositeArgs(knex, columns, values) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function buildCompositeValueArgs(columns, values) {
|
|
51
|
-
if (
|
|
51
|
+
if (values.length === 0) {
|
|
52
|
+
// knex turns an empty `whereIn` into `1 = 0` and an empty `whereNotIn`
|
|
53
|
+
// into `1 = 1`.
|
|
54
|
+
return [columns, []];
|
|
55
|
+
} else if (!Array.isArray(values[0])) {
|
|
52
56
|
return [columns, [values]];
|
|
53
57
|
} else {
|
|
54
58
|
return [columns, values];
|
|
@@ -54,6 +54,15 @@ function buildCompositeArgs(columns, values) {
|
|
|
54
54
|
throw new Error(`sqlite doesn't support multi-column where in clauses`);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
if (values.length === 0) {
|
|
58
|
+
// knex turns an empty `whereIn` into `1 = 0` and an empty `whereNotIn`
|
|
59
|
+
// into `1 = 1`.
|
|
60
|
+
return {
|
|
61
|
+
method: 'whereIn',
|
|
62
|
+
args: [columns, []],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
// Sqlite doesn't support the `where in` syntax for multiple columns but
|
|
58
67
|
// we can emulate it using grouped `or` clauses.
|
|
59
68
|
return {
|
|
@@ -166,10 +166,10 @@ function peg$parse(input, options) {
|
|
|
166
166
|
peg$c4 = peg$literalExpectation('[', false),
|
|
167
167
|
peg$c5 = '"',
|
|
168
168
|
peg$c6 = peg$literalExpectation('"', false),
|
|
169
|
-
peg$c7 =
|
|
170
|
-
peg$c8 = peg$literalExpectation(
|
|
171
|
-
peg$c9 = '
|
|
172
|
-
peg$c10 = peg$literalExpectation('
|
|
169
|
+
peg$c7 = ']',
|
|
170
|
+
peg$c8 = peg$literalExpectation(']', false),
|
|
171
|
+
peg$c9 = "'",
|
|
172
|
+
peg$c10 = peg$literalExpectation("'", false),
|
|
173
173
|
peg$c11 = function (key) {
|
|
174
174
|
return { type: 'object', ref: Array.isArray(key) ? key[1] : key };
|
|
175
175
|
},
|
|
@@ -415,7 +415,7 @@ function peg$parse(input, options) {
|
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
function peg$parsebracketStringRef() {
|
|
418
|
-
var s0, s1, s2, s3, s4, s5;
|
|
418
|
+
var s0, s1, s2, s3, s4, s5, s6, s7;
|
|
419
419
|
|
|
420
420
|
s0 = peg$currPos;
|
|
421
421
|
if (input.charCodeAt(peg$currPos) === 91) {
|
|
@@ -451,8 +451,31 @@ function peg$parse(input, options) {
|
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
453
|
if (s5 !== peg$FAILED) {
|
|
454
|
-
|
|
455
|
-
|
|
454
|
+
s6 = peg$currPos;
|
|
455
|
+
peg$silentFails++;
|
|
456
|
+
if (input.charCodeAt(peg$currPos) === 93) {
|
|
457
|
+
s7 = peg$c7;
|
|
458
|
+
peg$currPos++;
|
|
459
|
+
} else {
|
|
460
|
+
s7 = peg$FAILED;
|
|
461
|
+
if (peg$silentFails === 0) {
|
|
462
|
+
peg$fail(peg$c8);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
peg$silentFails--;
|
|
466
|
+
if (s7 !== peg$FAILED) {
|
|
467
|
+
peg$currPos = s6;
|
|
468
|
+
s6 = void 0;
|
|
469
|
+
} else {
|
|
470
|
+
s6 = peg$FAILED;
|
|
471
|
+
}
|
|
472
|
+
if (s6 !== peg$FAILED) {
|
|
473
|
+
s3 = [s3, s4, s5, s6];
|
|
474
|
+
s2 = s3;
|
|
475
|
+
} else {
|
|
476
|
+
peg$currPos = s2;
|
|
477
|
+
s2 = peg$FAILED;
|
|
478
|
+
}
|
|
456
479
|
} else {
|
|
457
480
|
peg$currPos = s2;
|
|
458
481
|
s2 = peg$FAILED;
|
|
@@ -468,29 +491,52 @@ function peg$parse(input, options) {
|
|
|
468
491
|
if (s2 === peg$FAILED) {
|
|
469
492
|
s2 = peg$currPos;
|
|
470
493
|
if (input.charCodeAt(peg$currPos) === 39) {
|
|
471
|
-
s3 = peg$
|
|
494
|
+
s3 = peg$c9;
|
|
472
495
|
peg$currPos++;
|
|
473
496
|
} else {
|
|
474
497
|
s3 = peg$FAILED;
|
|
475
498
|
if (peg$silentFails === 0) {
|
|
476
|
-
peg$fail(peg$
|
|
499
|
+
peg$fail(peg$c10);
|
|
477
500
|
}
|
|
478
501
|
}
|
|
479
502
|
if (s3 !== peg$FAILED) {
|
|
480
503
|
s4 = peg$parsestringWithoutSingleQuotes();
|
|
481
504
|
if (s4 !== peg$FAILED) {
|
|
482
505
|
if (input.charCodeAt(peg$currPos) === 39) {
|
|
483
|
-
s5 = peg$
|
|
506
|
+
s5 = peg$c9;
|
|
484
507
|
peg$currPos++;
|
|
485
508
|
} else {
|
|
486
509
|
s5 = peg$FAILED;
|
|
487
510
|
if (peg$silentFails === 0) {
|
|
488
|
-
peg$fail(peg$
|
|
511
|
+
peg$fail(peg$c10);
|
|
489
512
|
}
|
|
490
513
|
}
|
|
491
514
|
if (s5 !== peg$FAILED) {
|
|
492
|
-
|
|
493
|
-
|
|
515
|
+
s6 = peg$currPos;
|
|
516
|
+
peg$silentFails++;
|
|
517
|
+
if (input.charCodeAt(peg$currPos) === 93) {
|
|
518
|
+
s7 = peg$c7;
|
|
519
|
+
peg$currPos++;
|
|
520
|
+
} else {
|
|
521
|
+
s7 = peg$FAILED;
|
|
522
|
+
if (peg$silentFails === 0) {
|
|
523
|
+
peg$fail(peg$c8);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
peg$silentFails--;
|
|
527
|
+
if (s7 !== peg$FAILED) {
|
|
528
|
+
peg$currPos = s6;
|
|
529
|
+
s6 = void 0;
|
|
530
|
+
} else {
|
|
531
|
+
s6 = peg$FAILED;
|
|
532
|
+
}
|
|
533
|
+
if (s6 !== peg$FAILED) {
|
|
534
|
+
s3 = [s3, s4, s5, s6];
|
|
535
|
+
s2 = s3;
|
|
536
|
+
} else {
|
|
537
|
+
peg$currPos = s2;
|
|
538
|
+
s2 = peg$FAILED;
|
|
539
|
+
}
|
|
494
540
|
} else {
|
|
495
541
|
peg$currPos = s2;
|
|
496
542
|
s2 = peg$FAILED;
|
|
@@ -509,12 +555,12 @@ function peg$parse(input, options) {
|
|
|
509
555
|
}
|
|
510
556
|
if (s2 !== peg$FAILED) {
|
|
511
557
|
if (input.charCodeAt(peg$currPos) === 93) {
|
|
512
|
-
s3 = peg$
|
|
558
|
+
s3 = peg$c7;
|
|
513
559
|
peg$currPos++;
|
|
514
560
|
} else {
|
|
515
561
|
s3 = peg$FAILED;
|
|
516
562
|
if (peg$silentFails === 0) {
|
|
517
|
-
peg$fail(peg$
|
|
563
|
+
peg$fail(peg$c8);
|
|
518
564
|
}
|
|
519
565
|
}
|
|
520
566
|
if (s3 !== peg$FAILED) {
|
|
@@ -554,12 +600,12 @@ function peg$parse(input, options) {
|
|
|
554
600
|
s2 = peg$parseinteger();
|
|
555
601
|
if (s2 !== peg$FAILED) {
|
|
556
602
|
if (input.charCodeAt(peg$currPos) === 93) {
|
|
557
|
-
s3 = peg$
|
|
603
|
+
s3 = peg$c7;
|
|
558
604
|
peg$currPos++;
|
|
559
605
|
} else {
|
|
560
606
|
s3 = peg$FAILED;
|
|
561
607
|
if (peg$silentFails === 0) {
|
|
562
|
-
peg$fail(peg$
|
|
608
|
+
peg$fail(peg$c8);
|
|
563
609
|
}
|
|
564
610
|
}
|
|
565
611
|
if (s3 !== peg$FAILED) {
|
|
@@ -719,21 +765,17 @@ function peg$parse(input, options) {
|
|
|
719
765
|
peg$fail(peg$c22);
|
|
720
766
|
}
|
|
721
767
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
peg$fail(peg$c22);
|
|
732
|
-
}
|
|
768
|
+
while (s2 !== peg$FAILED) {
|
|
769
|
+
s1.push(s2);
|
|
770
|
+
if (peg$c21.test(input.charAt(peg$currPos))) {
|
|
771
|
+
s2 = input.charAt(peg$currPos);
|
|
772
|
+
peg$currPos++;
|
|
773
|
+
} else {
|
|
774
|
+
s2 = peg$FAILED;
|
|
775
|
+
if (peg$silentFails === 0) {
|
|
776
|
+
peg$fail(peg$c22);
|
|
733
777
|
}
|
|
734
778
|
}
|
|
735
|
-
} else {
|
|
736
|
-
s1 = peg$FAILED;
|
|
737
779
|
}
|
|
738
780
|
if (s1 !== peg$FAILED) {
|
|
739
781
|
peg$savedPos = s0;
|
|
@@ -758,21 +800,17 @@ function peg$parse(input, options) {
|
|
|
758
800
|
peg$fail(peg$c24);
|
|
759
801
|
}
|
|
760
802
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
peg$fail(peg$c24);
|
|
771
|
-
}
|
|
803
|
+
while (s2 !== peg$FAILED) {
|
|
804
|
+
s1.push(s2);
|
|
805
|
+
if (peg$c23.test(input.charAt(peg$currPos))) {
|
|
806
|
+
s2 = input.charAt(peg$currPos);
|
|
807
|
+
peg$currPos++;
|
|
808
|
+
} else {
|
|
809
|
+
s2 = peg$FAILED;
|
|
810
|
+
if (peg$silentFails === 0) {
|
|
811
|
+
peg$fail(peg$c24);
|
|
772
812
|
}
|
|
773
813
|
}
|
|
774
|
-
} else {
|
|
775
|
-
s1 = peg$FAILED;
|
|
776
814
|
}
|
|
777
815
|
if (s1 !== peg$FAILED) {
|
|
778
816
|
peg$savedPos = s0;
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* 4. Keys containing square brackets and quotes
|
|
32
32
|
* `objectColumn:['Double."Quote".[]']` and `objectColumn:["Sinlge.'Quote'.[]"]`
|
|
33
33
|
* Column `{ "Double.\"Quote\".[]" : "I was referred", "Sinlge.'Quote'.[]" : "Mee too!" }`
|
|
34
|
+
* 5. Empty keys `objectColumn:[""]` or `objectColumn:['']` Column `{ "" : "I was referred" }`
|
|
34
35
|
* 99. Keys containing dots, square brackets, single quotes and double quotes in one json key is
|
|
35
36
|
* not currently supported
|
|
36
37
|
*
|
|
@@ -60,8 +61,8 @@ start =
|
|
|
60
61
|
bracketStringRef =
|
|
61
62
|
'['
|
|
62
63
|
key:(
|
|
63
|
-
'"' stringWithoutDoubleQuotes '"' /
|
|
64
|
-
"'" stringWithoutSingleQuotes "'" /
|
|
64
|
+
'"' stringWithoutDoubleQuotes '"' &']' /
|
|
65
|
+
"'" stringWithoutSingleQuotes "'" &']' /
|
|
65
66
|
stringWithoutSquareBrackets
|
|
66
67
|
)
|
|
67
68
|
']'
|
|
@@ -86,10 +87,10 @@ stringWithoutColon =
|
|
|
86
87
|
chars:([^:])+ { return chars.join(""); }
|
|
87
88
|
|
|
88
89
|
stringWithoutDoubleQuotes =
|
|
89
|
-
chars:([^"])
|
|
90
|
+
chars:([^"])* { return chars.join(""); }
|
|
90
91
|
|
|
91
92
|
stringWithoutSingleQuotes =
|
|
92
|
-
chars:([^'])
|
|
93
|
+
chars:([^'])* { return chars.join(""); }
|
|
93
94
|
|
|
94
95
|
stringWithoutSquareBracketsOrDots =
|
|
95
96
|
chars:([^.\x5D\x5B])+ { return chars.join(""); }
|