@rvoh/dream 0.44.7 → 0.44.9
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/cjs/src/bin/index.js +7 -87
- package/dist/cjs/src/dream/LeftJoinLoadBuilder.js +1 -1
- package/dist/cjs/src/dream/LoadBuilder.js +1 -1
- package/dist/cjs/src/dream/Query.js +46 -1417
- package/dist/cjs/src/dream/QueryDriver/Base.js +311 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +1771 -0
- package/dist/cjs/src/dream/QueryDriver/Postgres.js +6 -0
- package/dist/cjs/src/dream/internal/saveDream.js +4 -17
- package/dist/cjs/src/serializer/SerializerRenderer.js +1 -1
- package/dist/esm/src/bin/index.js +7 -87
- package/dist/esm/src/dream/LeftJoinLoadBuilder.js +1 -1
- package/dist/esm/src/dream/LoadBuilder.js +1 -1
- package/dist/esm/src/dream/Query.js +46 -1417
- package/dist/esm/src/dream/QueryDriver/Base.js +308 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +1768 -0
- package/dist/esm/src/dream/QueryDriver/Postgres.js +3 -0
- package/dist/esm/src/dream/internal/saveDream.js +4 -17
- package/dist/esm/src/serializer/SerializerRenderer.js +1 -1
- package/dist/types/src/bin/index.d.ts +0 -2
- package/dist/types/src/dream/Query.d.ts +27 -155
- package/dist/types/src/dream/QueryDriver/Base.d.ts +242 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +354 -0
- package/dist/types/src/dream/QueryDriver/Postgres.d.ts +4 -0
- package/dist/types/src/dream/internal/executeDatabaseQuery.d.ts +2 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/Benchmark.html +2 -2
- package/docs/classes/CalendarDate.html +2 -2
- package/docs/classes/CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/Decorators.html +19 -19
- package/docs/classes/Dream.html +131 -131
- package/docs/classes/DreamApp.html +4 -4
- package/docs/classes/DreamBin.html +3 -4
- package/docs/classes/DreamCLI.html +4 -4
- package/docs/classes/DreamImporter.html +2 -2
- package/docs/classes/DreamLogos.html +2 -2
- package/docs/classes/DreamMigrationHelpers.html +7 -7
- package/docs/classes/DreamSerializerBuilder.html +2 -2
- package/docs/classes/DreamTransaction.html +2 -2
- package/docs/classes/Encrypt.html +2 -2
- package/docs/classes/Env.html +2 -2
- package/docs/classes/GlobalNameNotSet.html +3 -3
- package/docs/classes/NonLoadedAssociation.html +3 -3
- package/docs/classes/ObjectSerializerBuilder.html +2 -2
- package/docs/classes/Query.html +86 -57
- package/docs/classes/Range.html +2 -2
- package/docs/classes/RecordNotFound.html +3 -3
- package/docs/classes/ValidationError.html +3 -3
- package/docs/functions/DreamSerializer.html +1 -1
- package/docs/functions/ObjectSerializer.html +1 -1
- package/docs/functions/ReplicaSafe.html +1 -1
- package/docs/functions/STI.html +1 -1
- package/docs/functions/SoftDelete.html +1 -1
- package/docs/functions/camelize.html +1 -1
- package/docs/functions/capitalize.html +1 -1
- package/docs/functions/cloneDeepSafe.html +1 -1
- package/docs/functions/closeAllDbConnections.html +1 -1
- package/docs/functions/compact.html +1 -1
- package/docs/functions/dreamDbConnections.html +1 -1
- package/docs/functions/dreamPath.html +1 -1
- package/docs/functions/expandStiClasses.html +1 -1
- package/docs/functions/generateDream.html +1 -1
- package/docs/functions/globalClassNameFromFullyQualifiedModelName.html +1 -1
- package/docs/functions/groupBy.html +1 -1
- package/docs/functions/hyphenize.html +1 -1
- package/docs/functions/inferSerializerFromDreamOrViewModel.html +1 -1
- package/docs/functions/inferSerializersFromDreamClassOrViewModelClass.html +1 -1
- package/docs/functions/intersection.html +1 -1
- package/docs/functions/isDreamSerializer.html +1 -1
- package/docs/functions/isEmpty.html +1 -1
- package/docs/functions/loadRepl.html +1 -1
- package/docs/functions/lookupClassByGlobalName.html +1 -1
- package/docs/functions/normalizeUnicode.html +1 -1
- package/docs/functions/pascalize.html +1 -1
- package/docs/functions/pgErrorType.html +1 -1
- package/docs/functions/range-1.html +1 -1
- package/docs/functions/relativeDreamPath.html +1 -1
- package/docs/functions/round.html +1 -1
- package/docs/functions/serializerNameFromFullyQualifiedModelName.html +1 -1
- package/docs/functions/sharedPathPrefix.html +1 -1
- package/docs/functions/snakeify.html +1 -1
- package/docs/functions/sort.html +1 -1
- package/docs/functions/sortBy.html +1 -1
- package/docs/functions/sortObjectByKey.html +1 -1
- package/docs/functions/sortObjectByValue.html +1 -1
- package/docs/functions/standardizeFullyQualifiedModelName.html +1 -1
- package/docs/functions/uncapitalize.html +1 -1
- package/docs/functions/uniq.html +1 -1
- package/docs/functions/untypedDb.html +1 -1
- package/docs/functions/validateColumn.html +1 -1
- package/docs/functions/validateTable.html +1 -1
- package/docs/interfaces/BelongsToStatement.html +2 -2
- package/docs/interfaces/DecoratorContext.html +2 -2
- package/docs/interfaces/DreamAppInitOptions.html +2 -2
- package/docs/interfaces/DreamAppOpts.html +2 -2
- package/docs/interfaces/EncryptOptions.html +2 -2
- package/docs/interfaces/InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/OpenapiDescription.html +2 -2
- package/docs/interfaces/OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/SerializerRendererOpts.html +2 -2
- package/docs/types/Camelized.html +1 -1
- package/docs/types/CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/DateTime.html +1 -1
- package/docs/types/DbConnectionType.html +1 -1
- package/docs/types/DbTypes.html +1 -1
- package/docs/types/DreamAssociationMetadata.html +1 -1
- package/docs/types/DreamAttributes.html +1 -1
- package/docs/types/DreamClassColumn.html +1 -1
- package/docs/types/DreamColumn.html +1 -1
- package/docs/types/DreamColumnNames.html +1 -1
- package/docs/types/DreamLogLevel.html +1 -1
- package/docs/types/DreamLogger.html +1 -1
- package/docs/types/DreamModelSerializerType.html +1 -1
- package/docs/types/DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/DreamParamSafeAttributes.html +1 -1
- package/docs/types/DreamParamSafeColumnNames.html +1 -1
- package/docs/types/DreamSerializable.html +1 -1
- package/docs/types/DreamSerializableArray.html +1 -1
- package/docs/types/DreamSerializerKey.html +1 -1
- package/docs/types/DreamSerializers.html +1 -1
- package/docs/types/DreamTableSchema.html +1 -1
- package/docs/types/DreamVirtualColumns.html +1 -1
- package/docs/types/EncryptAlgorithm.html +1 -1
- package/docs/types/HasManyStatement.html +1 -1
- package/docs/types/HasOneStatement.html +1 -1
- package/docs/types/Hyphenized.html +1 -1
- package/docs/types/IdType.html +1 -1
- package/docs/types/OpenapiAllTypes.html +1 -1
- package/docs/types/OpenapiFormats.html +1 -1
- package/docs/types/OpenapiNumberFormats.html +1 -1
- package/docs/types/OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/OpenapiSchemaArray.html +1 -1
- package/docs/types/OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/OpenapiSchemaBase.html +1 -1
- package/docs/types/OpenapiSchemaBody.html +1 -1
- package/docs/types/OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/OpenapiSchemaExpressionAllOf.html +1 -1
- package/docs/types/OpenapiSchemaExpressionAnyOf.html +1 -1
- package/docs/types/OpenapiSchemaExpressionOneOf.html +1 -1
- package/docs/types/OpenapiSchemaExpressionRef.html +1 -1
- package/docs/types/OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
- package/docs/types/OpenapiSchemaInteger.html +1 -1
- package/docs/types/OpenapiSchemaNull.html +1 -1
- package/docs/types/OpenapiSchemaNumber.html +1 -1
- package/docs/types/OpenapiSchemaObject.html +1 -1
- package/docs/types/OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionAllOf.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionOneOf.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
- package/docs/types/OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/OpenapiSchemaString.html +1 -1
- package/docs/types/OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/OpenapiTypeField.html +1 -1
- package/docs/types/Pascalized.html +1 -1
- package/docs/types/PrimaryKeyType.html +1 -1
- package/docs/types/RoundingPrecision.html +1 -1
- package/docs/types/SerializerCasing.html +1 -1
- package/docs/types/SimpleObjectSerializerType.html +1 -1
- package/docs/types/Snakeified.html +1 -1
- package/docs/types/Timestamp.html +1 -1
- package/docs/types/UpdateableAssociationProperties.html +1 -1
- package/docs/types/UpdateableProperties.html +1 -1
- package/docs/types/ValidationType.html +1 -1
- package/docs/types/ViewModel.html +1 -1
- package/docs/types/ViewModelClass.html +1 -1
- package/docs/types/WhereStatementForDream.html +1 -1
- package/docs/types/WhereStatementForDreamClass.html +1 -1
- package/docs/variables/DateTime-1.html +1 -1
- package/docs/variables/DreamConst.html +1 -1
- package/docs/variables/TRIGRAM_OPERATORS.html +1 -1
- package/docs/variables/openapiPrimitiveTypes-1.html +1 -1
- package/docs/variables/openapiShorthandPrimitiveTypes-1.html +1 -1
- package/docs/variables/ops.html +1 -1
- package/docs/variables/primaryKeyTypes.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1768 @@
|
|
|
1
|
+
import { sql, } from 'kysely';
|
|
2
|
+
import pluralize from 'pluralize-esm';
|
|
3
|
+
import DreamCLI from '../../cli/index.js';
|
|
4
|
+
import _db from '../../db/index.js';
|
|
5
|
+
import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
|
|
6
|
+
import DreamApp from '../../dream-app/index.js';
|
|
7
|
+
import CannotAssociateThroughPolymorphic from '../../errors/associations/CannotAssociateThroughPolymorphic.js';
|
|
8
|
+
import CannotJoinPolymorphicBelongsToError from '../../errors/associations/CannotJoinPolymorphicBelongsToError.js';
|
|
9
|
+
import JoinAttemptedOnMissingAssociation from '../../errors/associations/JoinAttemptedOnMissingAssociation.js';
|
|
10
|
+
import MissingRequiredAssociationAndClause from '../../errors/associations/MissingRequiredAssociationAndClause.js';
|
|
11
|
+
import MissingRequiredPassthroughForAssociationAndClause from '../../errors/associations/MissingRequiredPassthroughForAssociationAndClause.js';
|
|
12
|
+
import MissingThroughAssociation from '../../errors/associations/MissingThroughAssociation.js';
|
|
13
|
+
import MissingThroughAssociationSource from '../../errors/associations/MissingThroughAssociationSource.js';
|
|
14
|
+
import CannotNegateSimilarityClause from '../../errors/CannotNegateSimilarityClause.js';
|
|
15
|
+
import CannotPassUndefinedAsAValueToAWhereClause from '../../errors/CannotPassUndefinedAsAValueToAWhereClause.js';
|
|
16
|
+
import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
|
|
17
|
+
import CalendarDate from '../../helpers/CalendarDate.js';
|
|
18
|
+
import camelize from '../../helpers/camelize.js';
|
|
19
|
+
import generateMigration from '../../helpers/cli/generateMigration.js';
|
|
20
|
+
import compact from '../../helpers/compact.js';
|
|
21
|
+
import { DateTime } from '../../helpers/DateTime.js';
|
|
22
|
+
import loadPgClient from '../../helpers/db/loadPgClient.js';
|
|
23
|
+
import runMigration from '../../helpers/db/runMigration.js';
|
|
24
|
+
import EnvInternal from '../../helpers/EnvInternal.js';
|
|
25
|
+
import isEmpty from '../../helpers/isEmpty.js';
|
|
26
|
+
import isObject from '../../helpers/isObject.js';
|
|
27
|
+
import namespaceColumn from '../../helpers/namespaceColumn.js';
|
|
28
|
+
import normalizeUnicode from '../../helpers/normalizeUnicode.js';
|
|
29
|
+
import objectPathsToArrays from '../../helpers/objectPathsToArrays.js';
|
|
30
|
+
import protectAgainstPollutingAssignment from '../../helpers/protectAgainstPollutingAssignment.js';
|
|
31
|
+
import { Range } from '../../helpers/range.js';
|
|
32
|
+
import snakeify from '../../helpers/snakeify.js';
|
|
33
|
+
import sqlAttributes from '../../helpers/sqlAttributes.js';
|
|
34
|
+
import uniq from '../../helpers/uniq.js';
|
|
35
|
+
import CurriedOpsStatement from '../../ops/curried-ops-statement.js';
|
|
36
|
+
import OpsStatement from '../../ops/ops-statement.js';
|
|
37
|
+
import { DreamConst } from '../constants.js';
|
|
38
|
+
import executeDatabaseQuery from '../internal/executeDatabaseQuery.js';
|
|
39
|
+
import extractAssociationMetadataFromAssociationName from '../internal/extractAssociationMetadataFromAssociationName.js';
|
|
40
|
+
import orderByDirection from '../internal/orderByDirection.js';
|
|
41
|
+
import shouldBypassDefaultScope from '../internal/shouldBypassDefaultScope.js';
|
|
42
|
+
import SimilarityBuilder from '../internal/similarity/SimilarityBuilder.js';
|
|
43
|
+
import sqlResultToDreamInstance from '../internal/sqlResultToDreamInstance.js';
|
|
44
|
+
import Query from '../Query.js';
|
|
45
|
+
import QueryDriverBase from './Base.js';
|
|
46
|
+
import writeSyncFile from '../../bin/helpers/sync.js';
|
|
47
|
+
import SchemaBuilder from '../../helpers/cli/SchemaBuilder.js';
|
|
48
|
+
import createDb from '../../helpers/db/createDb.js';
|
|
49
|
+
import _dropDb from '../../helpers/db/dropDb.js';
|
|
50
|
+
export default class KyselyQueryDriver extends QueryDriverBase {
|
|
51
|
+
// ATTENTION FRED
|
|
52
|
+
// stop trying to make this async. You never learn...
|
|
53
|
+
//
|
|
54
|
+
// if you are attempting to leverage a kysely-based connection using a different driver,
|
|
55
|
+
// you would want to extend this KyselyQueryDriver class, and then change this method
|
|
56
|
+
// to return a custom kysely instance, tailored to the specific database connection
|
|
57
|
+
// you are attempting to support. By default, dream is postgres-only, so our internal _db
|
|
58
|
+
// function will return a kysely instance, tightly coupled to a postgres-specific connection.
|
|
59
|
+
dbFor(sqlCommandType) {
|
|
60
|
+
if (this.dreamTransaction?.kyselyTransaction)
|
|
61
|
+
return this.dreamTransaction?.kyselyTransaction;
|
|
62
|
+
return _db(this.dbConnectionType(sqlCommandType));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* migrate the database. Must respond to the NODE_ENV value.
|
|
66
|
+
*/
|
|
67
|
+
static async migrate() {
|
|
68
|
+
const dreamApp = DreamApp.getOrFail();
|
|
69
|
+
const primaryDbConf = dreamApp.dbConnectionConfig('primary');
|
|
70
|
+
DreamCLI.logger.logStartProgress(`migrating ${primaryDbConf.name}...`);
|
|
71
|
+
await runMigration({ mode: 'migrate' });
|
|
72
|
+
DreamCLI.logger.logEndProgress();
|
|
73
|
+
await this.duplicateDatabase();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* rollback the database. Must respond to the NODE_ENV value.
|
|
77
|
+
*/
|
|
78
|
+
static async rollback(opts) {
|
|
79
|
+
const dreamApp = DreamApp.getOrFail();
|
|
80
|
+
const primaryDbConf = dreamApp.dbConnectionConfig('primary');
|
|
81
|
+
DreamCLI.logger.logStartProgress(`rolling back ${primaryDbConf.name}...`);
|
|
82
|
+
let step = opts.steps;
|
|
83
|
+
while (step > 0) {
|
|
84
|
+
await runMigration({ mode: 'rollback' });
|
|
85
|
+
step -= 1;
|
|
86
|
+
}
|
|
87
|
+
DreamCLI.logger.logEndProgress();
|
|
88
|
+
await this.duplicateDatabase();
|
|
89
|
+
}
|
|
90
|
+
static async generateMigration(migrationName, columnsWithTypes) {
|
|
91
|
+
await generateMigration({ migrationName, columnsWithTypes });
|
|
92
|
+
}
|
|
93
|
+
static async sync(onSync) {
|
|
94
|
+
DreamCLI.logger.logStartProgress('writing db schema...');
|
|
95
|
+
await writeSyncFile();
|
|
96
|
+
DreamCLI.logger.logEndProgress();
|
|
97
|
+
DreamCLI.logger.logStartProgress('building dream schema...');
|
|
98
|
+
await new SchemaBuilder().build();
|
|
99
|
+
DreamCLI.logger.logEndProgress();
|
|
100
|
+
// intentionally leaving logs off here, since it allows other
|
|
101
|
+
// onSync handlers to determine their own independent logging approach
|
|
102
|
+
await onSync();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* create the database. Must respond to the NODE_ENV value.
|
|
106
|
+
*/
|
|
107
|
+
static async dbCreate() {
|
|
108
|
+
const dreamApp = DreamApp.getOrFail();
|
|
109
|
+
const primaryDbConf = dreamApp.dbConnectionConfig('primary');
|
|
110
|
+
DreamCLI.logger.logStartProgress(`creating ${primaryDbConf.name}...`);
|
|
111
|
+
await createDb('primary');
|
|
112
|
+
DreamCLI.logger.logEndProgress();
|
|
113
|
+
// TODO: add support for creating replicas. Began doing it below, but it is very tricky,
|
|
114
|
+
// and we don't need it at the moment, so kicking off for future development when we have more time
|
|
115
|
+
// to flesh this out.
|
|
116
|
+
// if (connectionRetriever.hasReplicaConfig()) {
|
|
117
|
+
// const replicaDbConf = connectionRetriever.getConnectionConf('replica')
|
|
118
|
+
// console.log(`creating ${process.env[replicaDbConf.name]}`)
|
|
119
|
+
// await createDb('replica')
|
|
120
|
+
// }
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* delete the database. Must respond to the NODE_ENV value.
|
|
124
|
+
*/
|
|
125
|
+
static async dbDrop() {
|
|
126
|
+
const dreamApp = DreamApp.getOrFail();
|
|
127
|
+
const primaryDbConf = dreamApp.dbConnectionConfig('primary');
|
|
128
|
+
DreamCLI.logger.logStartProgress(`dropping ${primaryDbConf.name}...`);
|
|
129
|
+
await _dropDb('primary');
|
|
130
|
+
DreamCLI.logger.logEndProgress();
|
|
131
|
+
// TODO: add support for dropping replicas. Began doing it below, but it is very tricky,
|
|
132
|
+
// and we don't need it at the moment, so kicking off for future development when we have more time
|
|
133
|
+
// to flesh this out.
|
|
134
|
+
// if (connectionRetriever.hasReplicaConfig()) {
|
|
135
|
+
// const replicaDbConf = connectionRetriever.getConnectionConf('replica')
|
|
136
|
+
// console.log(`dropping ${process.env[replicaDbConf.name]}`)
|
|
137
|
+
// await _dropDb('replica')
|
|
138
|
+
// }
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Converts the given dream class into a Kysely query, enabling
|
|
142
|
+
* you to build custom queries using the Kysely API
|
|
143
|
+
*
|
|
144
|
+
* ```ts
|
|
145
|
+
* await User.query().toKysely('select').where('email', '=', 'how@yadoin').execute()
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* @param type - the type of Kysely query builder instance you would like to obtain
|
|
149
|
+
* @returns A Kysely query. Depending on the type passed, it will return either a SelectQueryBuilder, DeleteQueryBuilder, or an UpdateQueryBuilder
|
|
150
|
+
*/
|
|
151
|
+
toKysely(type) {
|
|
152
|
+
switch (type) {
|
|
153
|
+
case 'select':
|
|
154
|
+
return this.buildSelect();
|
|
155
|
+
case 'delete':
|
|
156
|
+
return this.buildDelete();
|
|
157
|
+
case 'update':
|
|
158
|
+
return this.buildUpdate({});
|
|
159
|
+
// TODO: in the future, we should support insert type, but don't yet, since inserts are done outside
|
|
160
|
+
// the query class for some reason.
|
|
161
|
+
default: {
|
|
162
|
+
// protection so that if a new QueryType is ever added, this will throw a type error at build time
|
|
163
|
+
const _never = type;
|
|
164
|
+
throw new Error(`Unhandled QueryType: ${_never}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* @internal
|
|
170
|
+
*
|
|
171
|
+
* This method is used internally by a Query driver to
|
|
172
|
+
* take the result of a single row in a database, and
|
|
173
|
+
* turn that row into the provided dream instance.
|
|
174
|
+
*
|
|
175
|
+
* If needed, the return type can be overriden to
|
|
176
|
+
* explicitly define the resulting dream instance,
|
|
177
|
+
* in cases where a proper type for the dream class
|
|
178
|
+
* cannot be inferred, i.e.
|
|
179
|
+
*
|
|
180
|
+
* ```ts
|
|
181
|
+
* this.dbResultToDreamInstance<typeof Dream, DreamInstance>(result, this.dreamClass)
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
dbResultToDreamInstance(result, dreamClass) {
|
|
185
|
+
return sqlResultToDreamInstance(dreamClass, result);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* @internal
|
|
189
|
+
*
|
|
190
|
+
* Used for applying first and last queries
|
|
191
|
+
*
|
|
192
|
+
* @returns A dream instance or null
|
|
193
|
+
*/
|
|
194
|
+
async takeOne() {
|
|
195
|
+
if (this.query['joinLoadActivated']) {
|
|
196
|
+
let query;
|
|
197
|
+
if (this.query['whereStatements'].find(whereStatement => whereStatement[this.dreamClass.primaryKey] ||
|
|
198
|
+
whereStatement[this.query['namespacedPrimaryKey']])) {
|
|
199
|
+
// the query already includes a primary key where statement
|
|
200
|
+
query = this.query;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// otherwise find the primary key and apply it to the query
|
|
204
|
+
const primaryKeyValue = (await this.query.limit(1).pluck(this.query['namespacedPrimaryKey']))[0];
|
|
205
|
+
if (primaryKeyValue === undefined)
|
|
206
|
+
return null;
|
|
207
|
+
query = this.query.where({ [this.query['namespacedPrimaryKey']]: primaryKeyValue });
|
|
208
|
+
}
|
|
209
|
+
const driverClass = this.constructor;
|
|
210
|
+
return (await new driverClass(query)['executeJoinLoad']())[0] || null;
|
|
211
|
+
}
|
|
212
|
+
const kyselyQuery = new KyselyQueryDriver(this.query.limit(1)).buildSelect();
|
|
213
|
+
const results = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirst');
|
|
214
|
+
if (results) {
|
|
215
|
+
const theFirst = this.dbResultToDreamInstance(results, this.dreamClass);
|
|
216
|
+
if (theFirst)
|
|
217
|
+
await this.applyPreload(this.query['preloadStatements'], this.query['preloadOnStatements'], [theFirst]);
|
|
218
|
+
return theFirst;
|
|
219
|
+
}
|
|
220
|
+
else
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Retrieves an array containing all records matching the Query.
|
|
225
|
+
* Be careful using this, since it will attempt to pull every
|
|
226
|
+
* record into memory at once. When querying might return a large
|
|
227
|
+
* number of records, consider using `.findEach`, which will pull
|
|
228
|
+
* the records in batches.
|
|
229
|
+
*
|
|
230
|
+
* ```ts
|
|
231
|
+
* await User.query().all()
|
|
232
|
+
* ```
|
|
233
|
+
*
|
|
234
|
+
* @returns an array of dreams
|
|
235
|
+
*/
|
|
236
|
+
async takeAll(options = {}) {
|
|
237
|
+
if (this.query['joinLoadActivated'])
|
|
238
|
+
return await this.executeJoinLoad(options);
|
|
239
|
+
const kyselyQuery = this.buildSelect(options);
|
|
240
|
+
const results = await executeDatabaseQuery(kyselyQuery, 'execute');
|
|
241
|
+
const theAll = results.map(r => this.dbResultToDreamInstance(r, this.dreamClass));
|
|
242
|
+
await this.applyPreload(this.query['preloadStatements'], this.query['preloadOnStatements'], theAll);
|
|
243
|
+
return theAll;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Retrieves the max value of the specified column
|
|
247
|
+
* for this Query
|
|
248
|
+
*
|
|
249
|
+
* ```ts
|
|
250
|
+
* await User.query().max('id')
|
|
251
|
+
* // 99
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* @param columnName - a column name on the model
|
|
255
|
+
* @returns the max value of the specified column for this Query
|
|
256
|
+
*
|
|
257
|
+
*/
|
|
258
|
+
async max(columnName) {
|
|
259
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
260
|
+
const { max } = this.dbFor('select').fn;
|
|
261
|
+
let kyselyQuery = new KyselyQueryDriver(this.query).buildSelect({
|
|
262
|
+
bypassSelectAll: true,
|
|
263
|
+
bypassOrder: true,
|
|
264
|
+
});
|
|
265
|
+
kyselyQuery = kyselyQuery.select(max(columnName));
|
|
266
|
+
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
267
|
+
return data.max;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Retrieves the min value of the specified column
|
|
271
|
+
* for this Query
|
|
272
|
+
*
|
|
273
|
+
* ```ts
|
|
274
|
+
* await User.query().min('id')
|
|
275
|
+
* // 1
|
|
276
|
+
* ```
|
|
277
|
+
*
|
|
278
|
+
* @param columnName - a column name on the model
|
|
279
|
+
* @returns the min value of the specified column for this Query
|
|
280
|
+
*/
|
|
281
|
+
async min(columnName) {
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
283
|
+
const { min } = this.dbFor('select').fn;
|
|
284
|
+
let kyselyQuery = new KyselyQueryDriver(this.query).buildSelect({
|
|
285
|
+
bypassSelectAll: true,
|
|
286
|
+
bypassOrder: true,
|
|
287
|
+
});
|
|
288
|
+
kyselyQuery = kyselyQuery.select(min(columnName));
|
|
289
|
+
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
290
|
+
return data.min;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Retrieves the number of records in the database
|
|
294
|
+
*
|
|
295
|
+
* ```ts
|
|
296
|
+
* await User.query().count()
|
|
297
|
+
* ```
|
|
298
|
+
*
|
|
299
|
+
* @returns The number of records in the database
|
|
300
|
+
*/
|
|
301
|
+
async count() {
|
|
302
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
303
|
+
const { count } = this.dbFor('select').fn;
|
|
304
|
+
const distinctColumn = this.query['distinctColumn'];
|
|
305
|
+
const query = this.query.clone({ distinctColumn: null });
|
|
306
|
+
let kyselyQuery = new KyselyQueryDriver(query).buildSelect({ bypassSelectAll: true, bypassOrder: true });
|
|
307
|
+
const countClause = distinctColumn
|
|
308
|
+
? count(sql `DISTINCT ${distinctColumn}`)
|
|
309
|
+
: count(query['namespaceColumn'](query.dreamInstance.primaryKey));
|
|
310
|
+
kyselyQuery = kyselyQuery.select(countClause.as('tablecount'));
|
|
311
|
+
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
312
|
+
return parseInt(data.tablecount.toString());
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* @internal
|
|
316
|
+
*
|
|
317
|
+
* Runs the query and extracts plucked values
|
|
318
|
+
*
|
|
319
|
+
* @returns An array of plucked values
|
|
320
|
+
*/
|
|
321
|
+
async pluck(...fields) {
|
|
322
|
+
let kyselyQuery = new KyselyQueryDriver(this.query['removeAllDefaultScopesExceptOnAssociations']()).buildSelect({
|
|
323
|
+
bypassSelectAll: true,
|
|
324
|
+
});
|
|
325
|
+
const aliases = [];
|
|
326
|
+
fields.forEach((field) => {
|
|
327
|
+
// field will already be namespaced in a join situation, but when the field to pluck is on the
|
|
328
|
+
// base model, it will be underscored (to match the table name), but when the selected column
|
|
329
|
+
// comes back from Kysely camelCased
|
|
330
|
+
aliases.push(field.includes('_') ? camelize(field) : field);
|
|
331
|
+
// namespace the selection so that when plucking the same column name from
|
|
332
|
+
// multpile tables, they don't get saved as the same name (e.g. select results with two `id` columns,
|
|
333
|
+
// which the pg package then returns in an object with a single `id` key)
|
|
334
|
+
kyselyQuery = kyselyQuery.select(`${this.namespaceColumn(field)} as ${field}`);
|
|
335
|
+
});
|
|
336
|
+
return (await executeDatabaseQuery(kyselyQuery, 'execute')).map(singleResult => aliases.map(alias => singleResult[alias]));
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Returns a new Kysely SelectQueryBuilder instance to be used
|
|
340
|
+
* in a sub Query
|
|
341
|
+
*
|
|
342
|
+
* ```ts
|
|
343
|
+
* const records = await User.where({
|
|
344
|
+
* id: Post.query().nestedSelect('userId'),
|
|
345
|
+
* }).all()
|
|
346
|
+
* // [User{id: 1}, ...]
|
|
347
|
+
* ```
|
|
348
|
+
*
|
|
349
|
+
* @param selection - the column to use for your nested Query
|
|
350
|
+
* @returns A Kysely SelectQueryBuilder instance
|
|
351
|
+
*/
|
|
352
|
+
nestedSelect(selection) {
|
|
353
|
+
const query = this.buildSelect({
|
|
354
|
+
bypassSelectAll: true,
|
|
355
|
+
bypassOrder: true,
|
|
356
|
+
});
|
|
357
|
+
return query.select(this.namespaceColumn(selection));
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* executes provided query instance as a deletion query.
|
|
361
|
+
* @returns the number of deleted rows
|
|
362
|
+
*/
|
|
363
|
+
async delete() {
|
|
364
|
+
const deletionResult = await executeDatabaseQuery(this.buildDelete(), 'executeTakeFirst');
|
|
365
|
+
return Number(deletionResult?.numDeletedRows || 0);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* executes provided query instance as an update query.
|
|
369
|
+
* @returns the number of updated rows
|
|
370
|
+
*/
|
|
371
|
+
async update(attributes) {
|
|
372
|
+
const kyselyQuery = this.buildUpdate(attributes);
|
|
373
|
+
const res = await executeDatabaseQuery(kyselyQuery, 'execute');
|
|
374
|
+
const resultData = Array.from(res.entries())?.[0]?.[1];
|
|
375
|
+
return Number(resultData?.numUpdatedRows || 0);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* persists any unsaved changes to the database. If a transaction
|
|
379
|
+
* is provided as a second argument, it will use that transaction
|
|
380
|
+
* to encapsulate the persisting of the dream, as well as any
|
|
381
|
+
* subsequent model hooks that are fired.
|
|
382
|
+
*/
|
|
383
|
+
static async saveDream(dream, txn = null) {
|
|
384
|
+
const db = txn?.kyselyTransaction ?? _db('primary');
|
|
385
|
+
const sqlifiedAttributes = sqlAttributes(dream);
|
|
386
|
+
if (dream.isPersisted) {
|
|
387
|
+
const query = db
|
|
388
|
+
.updateTable(dream.table)
|
|
389
|
+
.set(sqlifiedAttributes)
|
|
390
|
+
.where(namespaceColumn(dream.primaryKey, dream.table), '=', dream.primaryKeyValue);
|
|
391
|
+
return await executeDatabaseQuery(query.returning([...dream.columns()]), 'executeTakeFirstOrThrow');
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
const query = db
|
|
395
|
+
.insertInto(dream.table)
|
|
396
|
+
.values(sqlifiedAttributes)
|
|
397
|
+
.returning([...dream.columns()]);
|
|
398
|
+
return await executeDatabaseQuery(query, 'executeTakeFirstOrThrow');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
dbConnectionType(sqlCommandType) {
|
|
402
|
+
if (this.dreamTransaction)
|
|
403
|
+
return 'primary';
|
|
404
|
+
switch (sqlCommandType) {
|
|
405
|
+
case 'select':
|
|
406
|
+
return this.connectionOverride || (this.isReplicaSafe() ? 'replica' : 'primary');
|
|
407
|
+
default:
|
|
408
|
+
return 'primary';
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
isReplicaSafe() {
|
|
412
|
+
return this.innerJoinDreamClasses.reduce((accumulator, dreamClass) => accumulator && dreamClass['replicaSafe'], this.dreamClass['replicaSafe']);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Returns the sql that would be executed by this Query
|
|
416
|
+
*
|
|
417
|
+
* ```ts
|
|
418
|
+
* User.where({ email: 'how@yadoin' }).sql()
|
|
419
|
+
* // {
|
|
420
|
+
* // query: {
|
|
421
|
+
* // kind: 'SelectQueryNode',
|
|
422
|
+
* // from: { kind: 'FromNode', froms: [Array] },
|
|
423
|
+
* // selections: [ [Object] ],
|
|
424
|
+
* // distinctOn: undefined,
|
|
425
|
+
* // joins: undefined,
|
|
426
|
+
* // groupBy: undefined,
|
|
427
|
+
* // orderBy: undefined,
|
|
428
|
+
* // where: { kind: 'WhereNode', where: [Object] },
|
|
429
|
+
* // frontModifiers: undefined,
|
|
430
|
+
* // endModifiers: undefined,
|
|
431
|
+
* // limit: undefined,
|
|
432
|
+
* // offset: undefined,
|
|
433
|
+
* // with: undefined,
|
|
434
|
+
* // having: undefined,
|
|
435
|
+
* // explain: undefined,
|
|
436
|
+
* // setOperations: undefined
|
|
437
|
+
* // },
|
|
438
|
+
* // sql: 'select "users".* from "users" where ("users"."email" = $1 and "users"."deleted_at" is null)',
|
|
439
|
+
* // parameters: [ 'how@yadoin' ]
|
|
440
|
+
* //}
|
|
441
|
+
* ```
|
|
442
|
+
*
|
|
443
|
+
* @returns An object representing the underlying sql statement
|
|
444
|
+
*
|
|
445
|
+
*/
|
|
446
|
+
sql() {
|
|
447
|
+
const kyselyQuery = this.buildSelect();
|
|
448
|
+
return kyselyQuery.compile();
|
|
449
|
+
}
|
|
450
|
+
buildDelete() {
|
|
451
|
+
const kyselyQuery = this.dbFor('delete').deleteFrom(this.query['baseSqlAlias']);
|
|
452
|
+
const results = this.attachLimitAndOrderStatementsToNonSelectQuery(kyselyQuery);
|
|
453
|
+
return new KyselyQueryDriver(results.clone).buildCommon(results.kyselyQuery);
|
|
454
|
+
}
|
|
455
|
+
buildSelect({ bypassSelectAll = false, bypassOrder = false, columns, } = {}) {
|
|
456
|
+
let kyselyQuery;
|
|
457
|
+
if (this.query['baseSelectQuery']) {
|
|
458
|
+
const connectionOverride = this.query['connectionOverride'];
|
|
459
|
+
const query = connectionOverride
|
|
460
|
+
? this.query['baseSelectQuery'].connection(connectionOverride)
|
|
461
|
+
: this.query['baseSelectQuery'];
|
|
462
|
+
kyselyQuery = new KyselyQueryDriver(query).buildSelect({ bypassSelectAll: true });
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
const from = this.query['baseSqlAlias'] === this.query['tableName']
|
|
466
|
+
? this.query['tableName']
|
|
467
|
+
: `${this.query['tableName']} as ${this.query['baseSqlAlias']}`;
|
|
468
|
+
kyselyQuery = this.dbFor('select').selectFrom(from);
|
|
469
|
+
}
|
|
470
|
+
if (this.query['distinctColumn']) {
|
|
471
|
+
kyselyQuery = kyselyQuery.distinctOn(this.query['distinctColumn']);
|
|
472
|
+
}
|
|
473
|
+
kyselyQuery = this.buildCommon(kyselyQuery);
|
|
474
|
+
kyselyQuery = this.conditionallyAttachSimilarityColumnsToSelect(kyselyQuery, {
|
|
475
|
+
bypassOrder: bypassOrder || !!this.query['distinctColumn'],
|
|
476
|
+
});
|
|
477
|
+
if (this.query['orderStatements'].length && !bypassOrder) {
|
|
478
|
+
this.query['orderStatements'].forEach(orderStatement => {
|
|
479
|
+
kyselyQuery = kyselyQuery.orderBy(this.namespaceColumn(orderStatement.column), orderByDirection(orderStatement.direction));
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
if (this.query['limitStatement'])
|
|
483
|
+
kyselyQuery = kyselyQuery.limit(this.query['limitStatement']);
|
|
484
|
+
if (this.query['offsetStatement'])
|
|
485
|
+
kyselyQuery = kyselyQuery.offset(this.query['offsetStatement']);
|
|
486
|
+
if (columns) {
|
|
487
|
+
kyselyQuery = kyselyQuery.select(this.columnsWithRequiredLoadColumns(columns).map(column => this.namespaceColumn(column)));
|
|
488
|
+
}
|
|
489
|
+
else if (!bypassSelectAll) {
|
|
490
|
+
kyselyQuery = kyselyQuery.selectAll(this.query['baseSqlAlias']);
|
|
491
|
+
}
|
|
492
|
+
// even though we manually bypass explicit order statements above,
|
|
493
|
+
// associations can contain their own ordering systems. If we do not
|
|
494
|
+
// escape all orders, we can mistakenly allow an order clause to sneak in.
|
|
495
|
+
if (bypassOrder)
|
|
496
|
+
kyselyQuery = kyselyQuery.clearOrderBy();
|
|
497
|
+
return kyselyQuery;
|
|
498
|
+
}
|
|
499
|
+
buildUpdate(attributes) {
|
|
500
|
+
let kyselyQuery = this.dbFor('update')
|
|
501
|
+
.updateTable(this.query['tableName'])
|
|
502
|
+
.set(attributes);
|
|
503
|
+
kyselyQuery = this.conditionallyAttachSimilarityColumnsToUpdate(kyselyQuery);
|
|
504
|
+
const results = this.attachLimitAndOrderStatementsToNonSelectQuery(kyselyQuery);
|
|
505
|
+
return new KyselyQueryDriver(results.clone).buildCommon(results.kyselyQuery);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* @internal
|
|
509
|
+
*
|
|
510
|
+
* Used to hydrate dreams with the provided associations
|
|
511
|
+
*/
|
|
512
|
+
hydrateAssociation(dreams, association, preloadedDreamsAndWhatTheyPointTo) {
|
|
513
|
+
switch (association.type) {
|
|
514
|
+
case 'HasMany':
|
|
515
|
+
dreams.forEach((dream) => {
|
|
516
|
+
dream[association.as] = [];
|
|
517
|
+
});
|
|
518
|
+
break;
|
|
519
|
+
default:
|
|
520
|
+
dreams.forEach((dream) => {
|
|
521
|
+
dream[associationToGetterSetterProp(association)] = null;
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
// dreams is a Rating
|
|
525
|
+
// Rating belongs to: rateables (Posts / Compositions)
|
|
526
|
+
// loadedAssociations is an array of Posts and Compositions
|
|
527
|
+
// if rating.rateable_id === loadedAssociation.primaryKeyvalue
|
|
528
|
+
// rating.rateable = loadedAssociation
|
|
529
|
+
preloadedDreamsAndWhatTheyPointTo.forEach(preloadedDreamAndWhatItPointsTo => {
|
|
530
|
+
dreams
|
|
531
|
+
.filter(dream => dream.primaryKeyValue === preloadedDreamAndWhatItPointsTo.pointsToPrimaryKey)
|
|
532
|
+
.forEach((dream) => {
|
|
533
|
+
if (association.type === 'HasMany') {
|
|
534
|
+
dream[association.as].push(preloadedDreamAndWhatItPointsTo.dream);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
// in a HasOne context, order clauses will be applied in advance,
|
|
538
|
+
// prior to hydration. Considering, we only want to set the first
|
|
539
|
+
// result and ignore other results, so we will use ||= to set.
|
|
540
|
+
dream[association.as] ||= preloadedDreamAndWhatItPointsTo.dream;
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
if (association.type === 'HasMany') {
|
|
545
|
+
dreams.forEach((dream) => Object.freeze(dream[association.as]));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* @internal
|
|
550
|
+
*
|
|
551
|
+
* Used by loadBuider
|
|
552
|
+
*/
|
|
553
|
+
async hydratePreload(dream) {
|
|
554
|
+
await this.applyPreload(this.query['preloadStatements'], this.query['preloadOnStatements'], dream);
|
|
555
|
+
}
|
|
556
|
+
static async duplicateDatabase() {
|
|
557
|
+
const dreamApp = DreamApp.getOrFail();
|
|
558
|
+
const parallelTests = dreamApp.parallelTests;
|
|
559
|
+
if (!parallelTests)
|
|
560
|
+
return;
|
|
561
|
+
DreamCLI.logger.logStartProgress(`duplicating db for parallel tests...`);
|
|
562
|
+
const dbConf = dreamApp.dbConnectionConfig('primary');
|
|
563
|
+
const client = await loadPgClient({ useSystemDb: true });
|
|
564
|
+
if (EnvInternal.boolean('DREAM_CORE_DEVELOPMENT')) {
|
|
565
|
+
const replicaTestWorkerDatabaseName = `replica_test_${dbConf.name}`;
|
|
566
|
+
DreamCLI.logger.logContinueProgress(`creating fake replica test database ${replicaTestWorkerDatabaseName}...`, { logPrefix: ' ├ [db]', logPrefixColor: 'cyan' });
|
|
567
|
+
await client.query(`DROP DATABASE IF EXISTS ${replicaTestWorkerDatabaseName};`);
|
|
568
|
+
await client.query(`CREATE DATABASE ${replicaTestWorkerDatabaseName} TEMPLATE ${dbConf.name};`);
|
|
569
|
+
}
|
|
570
|
+
for (let i = 2; i <= parallelTests; i++) {
|
|
571
|
+
const workerDatabaseName = `${dbConf.name}_${i}`;
|
|
572
|
+
DreamCLI.logger.logContinueProgress(`creating duplicate test database ${workerDatabaseName} for concurrent tests...`, { logPrefix: ' ├ [db]', logPrefixColor: 'cyan' });
|
|
573
|
+
await client.query(`DROP DATABASE IF EXISTS ${workerDatabaseName};`);
|
|
574
|
+
await client.query(`CREATE DATABASE ${workerDatabaseName} TEMPLATE ${dbConf.name};`);
|
|
575
|
+
}
|
|
576
|
+
await client.end();
|
|
577
|
+
DreamCLI.logger.logEndProgress();
|
|
578
|
+
}
|
|
579
|
+
aliasWhereStatements(whereStatements, alias) {
|
|
580
|
+
return whereStatements.map(whereStatement => this.aliasWhereStatement(whereStatement, alias));
|
|
581
|
+
}
|
|
582
|
+
aliasWhereStatement(whereStatement, alias) {
|
|
583
|
+
return Object.keys(whereStatement).reduce((aliasedWhere, key) => {
|
|
584
|
+
aliasedWhere[this.namespaceColumn(key, alias)] = whereStatement[key];
|
|
585
|
+
return aliasedWhere;
|
|
586
|
+
}, {});
|
|
587
|
+
}
|
|
588
|
+
rawifiedSelfOnClause({ associationAlias, selfAlias, selfAndClause, }) {
|
|
589
|
+
const alphanumericUnderscoreRegexp = /[^a-zA-Z0-9_]/g;
|
|
590
|
+
selfAlias = selfAlias.replace(alphanumericUnderscoreRegexp, '');
|
|
591
|
+
return Object.keys(selfAndClause).reduce((acc, key) => {
|
|
592
|
+
const selfColumn = selfAndClause[key]?.replace(alphanumericUnderscoreRegexp, '');
|
|
593
|
+
if (!selfColumn)
|
|
594
|
+
return acc;
|
|
595
|
+
acc[this.namespaceColumn(key, associationAlias)] = sql.raw(`"${snakeify(selfAlias)}"."${snakeify(selfColumn)}"`);
|
|
596
|
+
return acc;
|
|
597
|
+
}, {});
|
|
598
|
+
}
|
|
599
|
+
attachLimitAndOrderStatementsToNonSelectQuery(kyselyQuery) {
|
|
600
|
+
if (this.query['limitStatement'] || this.query['orderStatements'].length) {
|
|
601
|
+
kyselyQuery = kyselyQuery.where((eb) => {
|
|
602
|
+
const subquery = this.query.nestedSelect(this.dreamInstance.primaryKey);
|
|
603
|
+
return eb(this.dreamInstance.primaryKey, 'in', subquery);
|
|
604
|
+
});
|
|
605
|
+
return {
|
|
606
|
+
kyselyQuery,
|
|
607
|
+
clone: this.query.clone({
|
|
608
|
+
where: null,
|
|
609
|
+
whereNot: null,
|
|
610
|
+
order: null,
|
|
611
|
+
limit: null,
|
|
612
|
+
}),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
return { kyselyQuery, clone: this.query };
|
|
616
|
+
}
|
|
617
|
+
columnsWithRequiredLoadColumns(columns) {
|
|
618
|
+
return uniq(compact([this.dreamClass.primaryKey, this.dreamClass['isSTIBase'] ? 'type' : null, ...columns]));
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* @internal
|
|
622
|
+
*
|
|
623
|
+
*/
|
|
624
|
+
async executeJoinLoad(options = {}) {
|
|
625
|
+
const query = this.query['limit'](null).offset(null);
|
|
626
|
+
let kyselyQuery = new KyselyQueryDriver(query).buildSelect({ bypassSelectAll: true });
|
|
627
|
+
const aliasToDreamClassesMap = {
|
|
628
|
+
[this.query['baseSqlAlias']]: this.dreamClass,
|
|
629
|
+
...this.joinStatementsToDreamClassesMap(this.query['leftJoinStatements']),
|
|
630
|
+
};
|
|
631
|
+
const associationAliasToColumnAliasMap = {};
|
|
632
|
+
const aliasToAssociationsMap = this.joinStatementsToAssociationsMap(this.query['leftJoinStatements']);
|
|
633
|
+
const aliases = Object.keys(aliasToDreamClassesMap);
|
|
634
|
+
let nextColumnAliasCounter = 0;
|
|
635
|
+
aliases.forEach((aliasOrExpression) => {
|
|
636
|
+
const alias = extractAssociationMetadataFromAssociationName(aliasOrExpression).alias;
|
|
637
|
+
if (alias === undefined)
|
|
638
|
+
throw new UnexpectedUndefined();
|
|
639
|
+
associationAliasToColumnAliasMap[alias] ||= {};
|
|
640
|
+
const aliasedDreamClass = aliasToDreamClassesMap[alias];
|
|
641
|
+
if (aliasedDreamClass === undefined)
|
|
642
|
+
throw new UnexpectedUndefined();
|
|
643
|
+
const association = aliasToAssociationsMap[alias];
|
|
644
|
+
const columns = alias === this.query['baseSqlAlias']
|
|
645
|
+
? options.columns
|
|
646
|
+
? this.columnsWithRequiredLoadColumns(options.columns)
|
|
647
|
+
: this.dreamClass.columns()
|
|
648
|
+
: aliasedDreamClass.columns();
|
|
649
|
+
columns.forEach((column) => {
|
|
650
|
+
const columnAlias = `dr${nextColumnAliasCounter++}`;
|
|
651
|
+
kyselyQuery = kyselyQuery.select(`${this.namespaceColumn(column, alias)} as ${columnAlias}`);
|
|
652
|
+
const columnAliasMap = associationAliasToColumnAliasMap[alias];
|
|
653
|
+
if (columnAliasMap === undefined)
|
|
654
|
+
throw new UnexpectedUndefined();
|
|
655
|
+
columnAliasMap[column] = columnAlias;
|
|
656
|
+
});
|
|
657
|
+
if (association?.type === 'HasOne' || association?.type === 'HasMany') {
|
|
658
|
+
const setupPreloadData = (dbColumnName) => {
|
|
659
|
+
const columnAlias = `dr${nextColumnAliasCounter++}`;
|
|
660
|
+
const columnAliasMap = associationAliasToColumnAliasMap[association.through];
|
|
661
|
+
if (columnAliasMap === undefined)
|
|
662
|
+
throw new UnexpectedUndefined();
|
|
663
|
+
columnAliasMap[dbColumnName] = columnAlias;
|
|
664
|
+
kyselyQuery = kyselyQuery.select(`${this.namespaceColumn(dbColumnName, association.through)} as ${columnAlias}`);
|
|
665
|
+
};
|
|
666
|
+
if (association.through && association.preloadThroughColumns) {
|
|
667
|
+
if (isObject(association.preloadThroughColumns)) {
|
|
668
|
+
const preloadMap = association.preloadThroughColumns;
|
|
669
|
+
Object.keys(preloadMap).forEach(columnName => setupPreloadData(columnName));
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
const preloadArray = association.preloadThroughColumns;
|
|
673
|
+
preloadArray.forEach(columnName => setupPreloadData(columnName));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
const queryResults = await executeDatabaseQuery(kyselyQuery, 'execute');
|
|
679
|
+
const aliasToDreamIdMap = queryResults.reduce((aliasToDreamIdMap, singleSqlResult) => {
|
|
680
|
+
this.fleshOutJoinLoadExecutionResults({
|
|
681
|
+
currentAlias: this.query['baseSqlAlias'],
|
|
682
|
+
singleSqlResult,
|
|
683
|
+
aliasToDreamIdMap,
|
|
684
|
+
associationAliasToColumnAliasMap,
|
|
685
|
+
aliasToAssociationsMap,
|
|
686
|
+
aliasToDreamClassesMap,
|
|
687
|
+
leftJoinStatements: this.query['leftJoinStatements'],
|
|
688
|
+
});
|
|
689
|
+
return aliasToDreamIdMap;
|
|
690
|
+
}, {});
|
|
691
|
+
const baseModelIdToDreamMap = aliasToDreamIdMap[this.query['baseSqlAlias']] || new Map();
|
|
692
|
+
return compact(Array.from(baseModelIdToDreamMap.values()));
|
|
693
|
+
}
|
|
694
|
+
fleshOutJoinLoadExecutionResults({ currentAlias, singleSqlResult, aliasToDreamIdMap, associationAliasToColumnAliasMap, aliasToAssociationsMap, aliasToDreamClassesMap, leftJoinStatements, }) {
|
|
695
|
+
const dreamClass = aliasToDreamClassesMap[currentAlias];
|
|
696
|
+
if (dreamClass === undefined)
|
|
697
|
+
throw new UnexpectedUndefined();
|
|
698
|
+
const columnToColumnAliasMap = associationAliasToColumnAliasMap[currentAlias];
|
|
699
|
+
if (columnToColumnAliasMap === undefined)
|
|
700
|
+
throw new UnexpectedUndefined();
|
|
701
|
+
const primaryKeyName = dreamClass.primaryKey;
|
|
702
|
+
if (primaryKeyName === undefined)
|
|
703
|
+
throw new UnexpectedUndefined();
|
|
704
|
+
const columnAlias = columnToColumnAliasMap[primaryKeyName];
|
|
705
|
+
if (columnAlias === undefined)
|
|
706
|
+
throw new UnexpectedUndefined();
|
|
707
|
+
const primaryKeyValue = singleSqlResult[columnAlias];
|
|
708
|
+
if (!primaryKeyValue)
|
|
709
|
+
return null;
|
|
710
|
+
aliasToDreamIdMap[currentAlias] ||= new Map();
|
|
711
|
+
if (!aliasToDreamIdMap[currentAlias].get(primaryKeyValue)) {
|
|
712
|
+
const columnValueMap = Object.keys(columnToColumnAliasMap).reduce((columnNameValueMap, columnName) => {
|
|
713
|
+
const columnAlias = columnToColumnAliasMap[columnName];
|
|
714
|
+
if (columnAlias === undefined)
|
|
715
|
+
throw new UnexpectedUndefined();
|
|
716
|
+
columnNameValueMap[columnName] = singleSqlResult[columnAlias];
|
|
717
|
+
return columnNameValueMap;
|
|
718
|
+
}, {});
|
|
719
|
+
const dream = this.dbResultToDreamInstance(columnValueMap, dreamClass);
|
|
720
|
+
const association = aliasToAssociationsMap[currentAlias];
|
|
721
|
+
if (association && association.through && association.preloadThroughColumns) {
|
|
722
|
+
const throughAssociationColumnToColumnAliasMap = associationAliasToColumnAliasMap[association.through];
|
|
723
|
+
if (throughAssociationColumnToColumnAliasMap === undefined)
|
|
724
|
+
throw new UnexpectedUndefined();
|
|
725
|
+
this.hydratePreloadedThroughColumns({
|
|
726
|
+
association,
|
|
727
|
+
columnToColumnAliasMap: throughAssociationColumnToColumnAliasMap,
|
|
728
|
+
dream,
|
|
729
|
+
singleSqlResult,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
aliasToDreamIdMap[protectAgainstPollutingAssignment(currentAlias)]?.set(primaryKeyValue, dream);
|
|
733
|
+
}
|
|
734
|
+
const dream = aliasToDreamIdMap[currentAlias].get(primaryKeyValue);
|
|
735
|
+
Object.keys(leftJoinStatements).forEach(nextAlias => {
|
|
736
|
+
const { name: associationName, alias } = extractAssociationMetadataFromAssociationName(nextAlias);
|
|
737
|
+
const association = dreamClass['getAssociationMetadata'](associationName);
|
|
738
|
+
if (association === undefined)
|
|
739
|
+
throw new UnexpectedUndefined();
|
|
740
|
+
const associatedDream = this.fleshOutJoinLoadExecutionResults({
|
|
741
|
+
currentAlias: alias,
|
|
742
|
+
singleSqlResult,
|
|
743
|
+
aliasToDreamIdMap,
|
|
744
|
+
associationAliasToColumnAliasMap,
|
|
745
|
+
aliasToAssociationsMap,
|
|
746
|
+
aliasToDreamClassesMap,
|
|
747
|
+
leftJoinStatements: leftJoinStatements[nextAlias],
|
|
748
|
+
});
|
|
749
|
+
const hasMany = association.type === 'HasMany';
|
|
750
|
+
// initialize by trying to access the association, which throws an exception if not yet initialized
|
|
751
|
+
try {
|
|
752
|
+
;
|
|
753
|
+
dream[association.as];
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
if (hasMany)
|
|
757
|
+
dream[association.as] = [];
|
|
758
|
+
else
|
|
759
|
+
dream[associationToGetterSetterProp(association)] = null;
|
|
760
|
+
}
|
|
761
|
+
if (!associatedDream)
|
|
762
|
+
return;
|
|
763
|
+
if (hasMany) {
|
|
764
|
+
if (!dream[association.as].includes(associatedDream))
|
|
765
|
+
dream[association.as].push(associatedDream);
|
|
766
|
+
}
|
|
767
|
+
else
|
|
768
|
+
dream[associationToGetterSetterProp(association)] = associatedDream;
|
|
769
|
+
});
|
|
770
|
+
return dream;
|
|
771
|
+
}
|
|
772
|
+
hydratePreloadedThroughColumns({ association, columnToColumnAliasMap, dream, singleSqlResult, }) {
|
|
773
|
+
if (!association.through)
|
|
774
|
+
return;
|
|
775
|
+
if (!dream.preloadedThroughColumns)
|
|
776
|
+
return;
|
|
777
|
+
let columnNames = [];
|
|
778
|
+
const columnNameToPreloadedThroughColumnNameMap = {};
|
|
779
|
+
if (isObject(association.preloadThroughColumns)) {
|
|
780
|
+
const preloadMap = association.preloadThroughColumns;
|
|
781
|
+
columnNames = Object.keys(preloadMap).map(columnName => {
|
|
782
|
+
columnNameToPreloadedThroughColumnNameMap[columnName] = preloadMap[columnName];
|
|
783
|
+
return columnName;
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
else if (Array.isArray(association.preloadThroughColumns)) {
|
|
787
|
+
columnNames = association.preloadThroughColumns.map(columnName => {
|
|
788
|
+
columnNameToPreloadedThroughColumnNameMap[columnName] = columnName;
|
|
789
|
+
return columnName;
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
columnNames.forEach(columnName => {
|
|
793
|
+
const preloadedThroughColumnName = columnNameToPreloadedThroughColumnNameMap[columnName];
|
|
794
|
+
if (preloadedThroughColumnName === undefined)
|
|
795
|
+
throw new UnexpectedUndefined();
|
|
796
|
+
const columnAlias = columnToColumnAliasMap[columnName];
|
|
797
|
+
if (columnAlias === undefined) {
|
|
798
|
+
throw new UnexpectedUndefined();
|
|
799
|
+
}
|
|
800
|
+
;
|
|
801
|
+
dream.preloadedThroughColumns[preloadedThroughColumnName] = singleSqlResult[columnAlias];
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
applyJoinAndStatement(join, joinAndStatement, rootTableOrAssociationAlias) {
|
|
805
|
+
if (!joinAndStatement)
|
|
806
|
+
return join;
|
|
807
|
+
join = this._applyJoinAndStatements(join, joinAndStatement.and, rootTableOrAssociationAlias);
|
|
808
|
+
join = this._applyJoinAndStatements(join, joinAndStatement.andNot, rootTableOrAssociationAlias, {
|
|
809
|
+
negate: true,
|
|
810
|
+
});
|
|
811
|
+
join = this._applyJoinAndAnyStatements(join, joinAndStatement.andAny, rootTableOrAssociationAlias);
|
|
812
|
+
return join;
|
|
813
|
+
}
|
|
814
|
+
_applyJoinAndStatements(join, joinAndStatement, rootTableOrAssociationAlias, { negate = false, } = {}) {
|
|
815
|
+
if (!joinAndStatement)
|
|
816
|
+
return join;
|
|
817
|
+
return join.on((eb) => this.joinAndStatementToExpressionWrapper(joinAndStatement, rootTableOrAssociationAlias, eb, {
|
|
818
|
+
negate,
|
|
819
|
+
disallowSimilarityOperator: negate,
|
|
820
|
+
}));
|
|
821
|
+
}
|
|
822
|
+
_applyJoinAndAnyStatements(join, joinAndAnyStatement, rootTableOrAssociationAlias) {
|
|
823
|
+
if (!joinAndAnyStatement)
|
|
824
|
+
return join;
|
|
825
|
+
if (!joinAndAnyStatement.length)
|
|
826
|
+
return join;
|
|
827
|
+
return join.on((eb) => {
|
|
828
|
+
return eb.or(joinAndAnyStatement.map(joinAndStatement => this.joinAndStatementToExpressionWrapper(joinAndStatement, rootTableOrAssociationAlias, eb)));
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
joinAndStatementToExpressionWrapper(joinAndStatement, rootTableOrAssociationAlias, eb, { negate = false, disallowSimilarityOperator = true, } = {}) {
|
|
832
|
+
return this.whereStatementToExpressionWrapper(eb, Object.keys(joinAndStatement).reduce((agg, key) => {
|
|
833
|
+
agg[this.namespaceColumn(key.toString(), rootTableOrAssociationAlias)] = joinAndStatement[key];
|
|
834
|
+
return agg;
|
|
835
|
+
}, {}), {
|
|
836
|
+
negate,
|
|
837
|
+
disallowSimilarityOperator,
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
buildCommon(kyselyQuery) {
|
|
841
|
+
this.checkForQueryViolations();
|
|
842
|
+
const query = this.conditionallyApplyDefaultScopes();
|
|
843
|
+
if (!isEmpty(query['innerJoinStatements'])) {
|
|
844
|
+
kyselyQuery = this.recursivelyJoin({
|
|
845
|
+
query: kyselyQuery,
|
|
846
|
+
joinStatement: query['innerJoinStatements'],
|
|
847
|
+
joinAndStatements: query['innerJoinAndStatements'],
|
|
848
|
+
dreamClass: query['dreamClass'],
|
|
849
|
+
previousAssociationTableOrAlias: this.query['baseSqlAlias'],
|
|
850
|
+
joinType: 'inner',
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
if (!isEmpty(query['leftJoinStatements'])) {
|
|
854
|
+
kyselyQuery = this.recursivelyJoin({
|
|
855
|
+
query: kyselyQuery,
|
|
856
|
+
joinStatement: query['leftJoinStatements'],
|
|
857
|
+
joinAndStatements: query['leftJoinAndStatements'],
|
|
858
|
+
dreamClass: query['dreamClass'],
|
|
859
|
+
previousAssociationTableOrAlias: this.query['baseSqlAlias'],
|
|
860
|
+
joinType: 'left',
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
if (query['whereStatements'].length ||
|
|
864
|
+
query['whereNotStatements'].length ||
|
|
865
|
+
query['whereAnyStatements'].length) {
|
|
866
|
+
kyselyQuery = kyselyQuery.where((eb) => eb.and([
|
|
867
|
+
...this.aliasWhereStatements(query['whereStatements'], query['baseSqlAlias']).map(whereStatement => this.whereStatementToExpressionWrapper(eb, whereStatement, {
|
|
868
|
+
disallowSimilarityOperator: false,
|
|
869
|
+
})),
|
|
870
|
+
...this.aliasWhereStatements(query['whereNotStatements'], query['baseSqlAlias']).map(whereNotStatement => this.whereStatementToExpressionWrapper(eb, whereNotStatement, { negate: true })),
|
|
871
|
+
...query['whereAnyStatements'].map(whereAnyStatements => eb.or(this.aliasWhereStatements(whereAnyStatements, query['baseSqlAlias']).map(whereAnyStatement => this.whereStatementToExpressionWrapper(eb, whereAnyStatement)))),
|
|
872
|
+
]));
|
|
873
|
+
}
|
|
874
|
+
return kyselyQuery;
|
|
875
|
+
}
|
|
876
|
+
whereStatementToExpressionWrapper(eb, whereStatement, { negate = false, disallowSimilarityOperator = true, } = {}) {
|
|
877
|
+
const clauses = compact(Object.keys(whereStatement)
|
|
878
|
+
.filter(key => whereStatement[key] !== DreamConst.required)
|
|
879
|
+
.map(attr => {
|
|
880
|
+
const val = whereStatement[attr];
|
|
881
|
+
if (val?.isOpsStatement &&
|
|
882
|
+
val.shouldBypassWhereStatement) {
|
|
883
|
+
if (disallowSimilarityOperator)
|
|
884
|
+
throw new Error('Similarity operator may not be used in whereAny');
|
|
885
|
+
// some ops statements are handled specifically in the select portion of the query,
|
|
886
|
+
// and should be ommited from the where clause directly
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const { a, b, c, a2, b2, c2 } = this.dreamWhereStatementToExpressionBuilderParts(attr, val);
|
|
890
|
+
// postgres is unable to handle WHERE IN statements with blank arrays, such as in
|
|
891
|
+
// "WHERE id IN ()", meaning that:
|
|
892
|
+
// 1. If we receive a blank array during an IN comparison,
|
|
893
|
+
// then we need to simply regurgitate a where statement which
|
|
894
|
+
// guarantees no records.
|
|
895
|
+
// 2. If we receive a blank array during a NOT IN comparison,
|
|
896
|
+
// then it is the same as the where statement not being present at all,
|
|
897
|
+
// resulting in a noop on our end
|
|
898
|
+
//
|
|
899
|
+
if (Array.isArray(c)) {
|
|
900
|
+
if ((b === 'in' && c.includes(null)) || (b === 'not in' && !c.includes(null))) {
|
|
901
|
+
return this.inArrayWithNull_or_notInArrayWithoutNull_ExpressionBuilder(eb, a, b, c);
|
|
902
|
+
}
|
|
903
|
+
else if (negate && b === 'in' && !c.includes(null)) {
|
|
904
|
+
return this.inArrayWithoutNullExpressionBuilder(eb, a, b, c);
|
|
905
|
+
}
|
|
906
|
+
else if (b === 'not in' && c.includes(null)) {
|
|
907
|
+
return this.notInArrayWithNullExpressionBuilder(eb, a, b, c);
|
|
908
|
+
}
|
|
909
|
+
const compactedC = compact(c);
|
|
910
|
+
if (b === 'in' && compactedC.length === 0) {
|
|
911
|
+
// in an empty array means match nothing
|
|
912
|
+
return sql `FALSE`;
|
|
913
|
+
}
|
|
914
|
+
else if (b === 'not in' && compactedC.length === 0) {
|
|
915
|
+
// not in an empty array means match everything
|
|
916
|
+
return sql `TRUE`;
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
return eb(a, b, compactedC);
|
|
920
|
+
}
|
|
921
|
+
//
|
|
922
|
+
}
|
|
923
|
+
else if (b === '=' && c === null) {
|
|
924
|
+
return eb(a, 'is', null);
|
|
925
|
+
//
|
|
926
|
+
}
|
|
927
|
+
else if (b === '!=' && c === null) {
|
|
928
|
+
return eb(a, 'is not', null);
|
|
929
|
+
//
|
|
930
|
+
}
|
|
931
|
+
else if (b === '=' && negate) {
|
|
932
|
+
return eb.and([eb(a, '=', c), eb(a, 'is not', null)]);
|
|
933
|
+
//
|
|
934
|
+
}
|
|
935
|
+
else if (b === '!=' && c !== null) {
|
|
936
|
+
return eb.or([eb(a, '!=', c), eb(a, 'is', null)]);
|
|
937
|
+
//
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
const expression = eb(a, b, c);
|
|
941
|
+
if (b2)
|
|
942
|
+
return expression.and(eb(a2, b2, c2));
|
|
943
|
+
return expression;
|
|
944
|
+
}
|
|
945
|
+
}));
|
|
946
|
+
return negate ? eb.not(eb.parens(eb.and(clauses))) : eb.and(clauses);
|
|
947
|
+
}
|
|
948
|
+
inArrayWithNull_or_notInArrayWithoutNull_ExpressionBuilder(eb, a, b, c) {
|
|
949
|
+
const isNullStatement = eb(a, 'is', null);
|
|
950
|
+
const compactedC = compact(c);
|
|
951
|
+
if (compactedC.length)
|
|
952
|
+
return eb.or([eb(a, b, compactedC), isNullStatement]);
|
|
953
|
+
// not in an empty array means match everything
|
|
954
|
+
if (b === 'not in')
|
|
955
|
+
return sql `TRUE`;
|
|
956
|
+
return isNullStatement;
|
|
957
|
+
}
|
|
958
|
+
inArrayWithoutNullExpressionBuilder(eb, a, b, c) {
|
|
959
|
+
const isNotNullStatement = eb(a, 'is not', null);
|
|
960
|
+
const compactedC = compact(c);
|
|
961
|
+
if (compactedC.length)
|
|
962
|
+
return eb.and([eb(a, 'in', compactedC), isNotNullStatement]);
|
|
963
|
+
// in an empty array means match nothing
|
|
964
|
+
return sql `FALSE`;
|
|
965
|
+
}
|
|
966
|
+
notInArrayWithNullExpressionBuilder(eb, a, b, c) {
|
|
967
|
+
const isNullStatement = eb(a, 'is not', null);
|
|
968
|
+
const compactedC = compact(c);
|
|
969
|
+
if (compactedC.length)
|
|
970
|
+
return eb.and([eb(a, 'not in', compactedC), isNullStatement]);
|
|
971
|
+
return isNullStatement;
|
|
972
|
+
}
|
|
973
|
+
dreamWhereStatementToExpressionBuilderParts(attr, val) {
|
|
974
|
+
let a;
|
|
975
|
+
let b;
|
|
976
|
+
let c;
|
|
977
|
+
let a2 = null;
|
|
978
|
+
let b2 = null;
|
|
979
|
+
let c2 = null;
|
|
980
|
+
if (val instanceof Function && val !== DreamConst.passthrough) {
|
|
981
|
+
val = val();
|
|
982
|
+
}
|
|
983
|
+
else if (val === DreamConst.passthrough) {
|
|
984
|
+
const column = attr.split('.').at(-1);
|
|
985
|
+
if (this.query['passthroughOnStatement'][column] === undefined)
|
|
986
|
+
throw new MissingRequiredPassthroughForAssociationAndClause(column);
|
|
987
|
+
val = this.query['passthroughOnStatement'][column];
|
|
988
|
+
}
|
|
989
|
+
if (val === null) {
|
|
990
|
+
a = attr;
|
|
991
|
+
b = 'is';
|
|
992
|
+
c = val;
|
|
993
|
+
}
|
|
994
|
+
else if (['SelectQueryBuilder', 'SelectQueryBuilderImpl'].includes(val?.constructor?.name)) {
|
|
995
|
+
a = attr;
|
|
996
|
+
b = 'in';
|
|
997
|
+
c = val;
|
|
998
|
+
}
|
|
999
|
+
else if (Array.isArray(val)) {
|
|
1000
|
+
a = attr;
|
|
1001
|
+
b = 'in';
|
|
1002
|
+
c = val.map(v => v instanceof DateTime || v instanceof CalendarDate
|
|
1003
|
+
? v.toSQL()
|
|
1004
|
+
: typeof v === 'string'
|
|
1005
|
+
? normalizeUnicode(v)
|
|
1006
|
+
: v);
|
|
1007
|
+
}
|
|
1008
|
+
else if (val instanceof CurriedOpsStatement) {
|
|
1009
|
+
val = val.toOpsStatement(this.dreamClass, attr);
|
|
1010
|
+
a = attr;
|
|
1011
|
+
b = val.operator;
|
|
1012
|
+
c = val.value;
|
|
1013
|
+
}
|
|
1014
|
+
else if (val instanceof OpsStatement) {
|
|
1015
|
+
a = attr;
|
|
1016
|
+
b = val.operator;
|
|
1017
|
+
c = val.value;
|
|
1018
|
+
}
|
|
1019
|
+
else if (val instanceof Range) {
|
|
1020
|
+
const rangeStart = val.begin;
|
|
1021
|
+
const rangeEnd = val.end;
|
|
1022
|
+
const excludeEnd = val.excludeEnd;
|
|
1023
|
+
if (rangeStart && rangeEnd) {
|
|
1024
|
+
a = attr;
|
|
1025
|
+
b = '>=';
|
|
1026
|
+
c = rangeStart;
|
|
1027
|
+
a2 = attr;
|
|
1028
|
+
b2 = excludeEnd ? '<' : '<=';
|
|
1029
|
+
c2 = rangeEnd;
|
|
1030
|
+
}
|
|
1031
|
+
else if (rangeStart) {
|
|
1032
|
+
a = attr;
|
|
1033
|
+
b = '>=';
|
|
1034
|
+
c = rangeStart;
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
a = attr;
|
|
1038
|
+
b = excludeEnd ? '<' : '<=';
|
|
1039
|
+
c = rangeEnd;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
a = attr;
|
|
1044
|
+
b = '=';
|
|
1045
|
+
c = val;
|
|
1046
|
+
}
|
|
1047
|
+
if (c instanceof DateTime || c instanceof CalendarDate)
|
|
1048
|
+
c = c.toSQL();
|
|
1049
|
+
else if (typeof c === 'string')
|
|
1050
|
+
c = normalizeUnicode(c);
|
|
1051
|
+
if (c2 instanceof DateTime || c2 instanceof CalendarDate)
|
|
1052
|
+
c2 = c2.toSQL();
|
|
1053
|
+
else if (typeof c2 === 'string')
|
|
1054
|
+
c2 = normalizeUnicode(c2);
|
|
1055
|
+
if (a && c === undefined)
|
|
1056
|
+
throw new CannotPassUndefinedAsAValueToAWhereClause(this.dreamClass, a);
|
|
1057
|
+
if (a2 && c2 === undefined)
|
|
1058
|
+
throw new CannotPassUndefinedAsAValueToAWhereClause(this.dreamClass, a2);
|
|
1059
|
+
return { a, b, c, a2, b2, c2 };
|
|
1060
|
+
}
|
|
1061
|
+
recursivelyJoin({ query, joinStatement, joinAndStatements, dreamClass, previousAssociationTableOrAlias, joinType, }) {
|
|
1062
|
+
for (const currentAssociationTableOrAlias of Object.keys(joinStatement)) {
|
|
1063
|
+
const results = this.applyOneJoin({
|
|
1064
|
+
query,
|
|
1065
|
+
dreamClass,
|
|
1066
|
+
previousAssociationTableOrAlias,
|
|
1067
|
+
currentAssociationTableOrAlias,
|
|
1068
|
+
joinAndStatements,
|
|
1069
|
+
joinType,
|
|
1070
|
+
});
|
|
1071
|
+
query = results.query;
|
|
1072
|
+
const association = results.association;
|
|
1073
|
+
query = this.recursivelyJoin({
|
|
1074
|
+
query,
|
|
1075
|
+
joinStatement: joinStatement[currentAssociationTableOrAlias],
|
|
1076
|
+
joinAndStatements: joinAndStatements[currentAssociationTableOrAlias],
|
|
1077
|
+
dreamClass: association.modelCB(),
|
|
1078
|
+
previousAssociationTableOrAlias: currentAssociationTableOrAlias,
|
|
1079
|
+
joinType,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
return query;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* @internal
|
|
1086
|
+
*
|
|
1087
|
+
*
|
|
1088
|
+
*/
|
|
1089
|
+
joinStatementsToDreamClassesMap(joinStatements) {
|
|
1090
|
+
const associationsToDreamClassesMap = {};
|
|
1091
|
+
objectPathsToArrays(joinStatements).forEach(associationChain => this.associationNamesToDreamClassesMap(associationChain, associationsToDreamClassesMap));
|
|
1092
|
+
return associationsToDreamClassesMap;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* @internal
|
|
1096
|
+
*
|
|
1097
|
+
*
|
|
1098
|
+
*/
|
|
1099
|
+
joinStatementsToAssociationsMap(joinStatements) {
|
|
1100
|
+
const associationsToAssociationsMap = {};
|
|
1101
|
+
objectPathsToArrays(joinStatements).forEach(associationChain => this.associationNamesToAssociationsMap(associationChain, associationsToAssociationsMap));
|
|
1102
|
+
return associationsToAssociationsMap;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* @internal
|
|
1106
|
+
*
|
|
1107
|
+
*
|
|
1108
|
+
*/
|
|
1109
|
+
associationNamesToDreamClassesMap(associationNames, associationsToDreamClassesMap = {}) {
|
|
1110
|
+
const namesToAssociationsAndDreamClasses = this.associationNamesToAssociationDataAndDreamClassesMap(associationNames);
|
|
1111
|
+
return Object.keys(namesToAssociationsAndDreamClasses).reduce((remap, associationName) => {
|
|
1112
|
+
const associationAndDreamClass = namesToAssociationsAndDreamClasses[associationName];
|
|
1113
|
+
if (associationAndDreamClass === undefined)
|
|
1114
|
+
throw new UnexpectedUndefined();
|
|
1115
|
+
remap[associationName] = associationAndDreamClass.dreamClass;
|
|
1116
|
+
return remap;
|
|
1117
|
+
}, associationsToDreamClassesMap);
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* @internal
|
|
1121
|
+
*
|
|
1122
|
+
*
|
|
1123
|
+
*/
|
|
1124
|
+
associationNamesToAssociationsMap(associationNames, associationsToAssociations = {}) {
|
|
1125
|
+
const namesToAssociationsAndDreamClasses = this.associationNamesToAssociationDataAndDreamClassesMap(associationNames);
|
|
1126
|
+
return Object.keys(namesToAssociationsAndDreamClasses).reduce((remap, associationName) => {
|
|
1127
|
+
const associationAndDreamClass = namesToAssociationsAndDreamClasses[associationName];
|
|
1128
|
+
if (associationAndDreamClass === undefined)
|
|
1129
|
+
throw new UnexpectedUndefined();
|
|
1130
|
+
remap[associationName] = associationAndDreamClass.association;
|
|
1131
|
+
return remap;
|
|
1132
|
+
}, associationsToAssociations);
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* @internal
|
|
1136
|
+
*/
|
|
1137
|
+
associationNamesToAssociationDataAndDreamClassesMap(associationNames) {
|
|
1138
|
+
const associationsToDreamClassesMap = {};
|
|
1139
|
+
associationNames.reduce((dreamClass, associationName) => {
|
|
1140
|
+
const { name, alias } = extractAssociationMetadataFromAssociationName(associationName);
|
|
1141
|
+
const association = dreamClass['getAssociationMetadata'](name);
|
|
1142
|
+
if (association === undefined)
|
|
1143
|
+
throw new UnexpectedUndefined();
|
|
1144
|
+
const through = association.through;
|
|
1145
|
+
if (through) {
|
|
1146
|
+
const { throughAssociation, throughAssociationDreamClass } = this.throughAssociationDetails(dreamClass, through);
|
|
1147
|
+
associationsToDreamClassesMap[through] = {
|
|
1148
|
+
association: throughAssociation,
|
|
1149
|
+
dreamClass: throughAssociationDreamClass,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
const nextDreamClass = association.modelCB();
|
|
1153
|
+
if (alias === undefined)
|
|
1154
|
+
throw new UnexpectedUndefined();
|
|
1155
|
+
associationsToDreamClassesMap[alias] = { association, dreamClass: nextDreamClass };
|
|
1156
|
+
return nextDreamClass;
|
|
1157
|
+
}, this.dreamClass);
|
|
1158
|
+
return associationsToDreamClassesMap;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* @internal
|
|
1162
|
+
*/
|
|
1163
|
+
throughAssociationDetails(dreamClass, through) {
|
|
1164
|
+
const throughAssociation = dreamClass['getAssociationMetadata'](through);
|
|
1165
|
+
if (throughAssociation === undefined)
|
|
1166
|
+
throw new UnexpectedUndefined();
|
|
1167
|
+
const throughAssociationDreamClass = throughAssociation.modelCB();
|
|
1168
|
+
return { throughAssociation, throughAssociationDreamClass };
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* @internal
|
|
1172
|
+
*
|
|
1173
|
+
* Returns a namespaced column name
|
|
1174
|
+
*
|
|
1175
|
+
* @returns A string
|
|
1176
|
+
*/
|
|
1177
|
+
namespaceColumn(column, alias = this.query['baseSqlAlias']) {
|
|
1178
|
+
return namespaceColumn(column, alias);
|
|
1179
|
+
}
|
|
1180
|
+
checkForQueryViolations() {
|
|
1181
|
+
const invalidWhereNotClauses = this.similarityStatementBuilder().whereNotStatementsWithSimilarityClauses();
|
|
1182
|
+
if (invalidWhereNotClauses.length) {
|
|
1183
|
+
const invalidWhereNotClause = invalidWhereNotClauses[0];
|
|
1184
|
+
if (invalidWhereNotClause === undefined)
|
|
1185
|
+
throw new UnexpectedUndefined();
|
|
1186
|
+
const { tableName, columnName, opsStatement } = invalidWhereNotClause;
|
|
1187
|
+
throw new CannotNegateSimilarityClause(tableName, columnName, opsStatement.value);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
similarityStatementBuilder() {
|
|
1191
|
+
return new SimilarityBuilder(this.dreamInstance, {
|
|
1192
|
+
where: [...this.query['whereStatements']],
|
|
1193
|
+
whereNot: [...this.query['whereNotStatements']],
|
|
1194
|
+
joinAndStatements: this.query['innerJoinAndStatements'],
|
|
1195
|
+
transaction: this.query['dreamTransaction'],
|
|
1196
|
+
connection: this.query['connectionOverride'],
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* @internal
|
|
1201
|
+
*
|
|
1202
|
+
* Applies a preload statement
|
|
1203
|
+
*/
|
|
1204
|
+
async applyPreload(preloadStatement, preloadOnStatements, dream) {
|
|
1205
|
+
const keys = Object.keys(preloadStatement);
|
|
1206
|
+
for (const key of keys) {
|
|
1207
|
+
const nestedDreams = await this.applyOnePreload(key, dream, this.applyablePreloadOnStatements(preloadOnStatements[key]));
|
|
1208
|
+
if (nestedDreams) {
|
|
1209
|
+
await this.applyPreload(preloadStatement[key], preloadOnStatements[key], nestedDreams);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* @internal
|
|
1215
|
+
*
|
|
1216
|
+
* retrieves on statements that can be applied to a preload
|
|
1217
|
+
*/
|
|
1218
|
+
applyablePreloadOnStatements(preloadOnStatements) {
|
|
1219
|
+
if (preloadOnStatements === undefined)
|
|
1220
|
+
return undefined;
|
|
1221
|
+
return Object.keys(preloadOnStatements).reduce((agg, key) => {
|
|
1222
|
+
const value = preloadOnStatements[key];
|
|
1223
|
+
if (value === undefined)
|
|
1224
|
+
throw new UnexpectedUndefined();
|
|
1225
|
+
// filter out plain objects, but not ops and not and/andNot/andAny statements
|
|
1226
|
+
// because plain objects are just the next level of nested preload
|
|
1227
|
+
if (key === 'and' ||
|
|
1228
|
+
key === 'andNot' ||
|
|
1229
|
+
key === 'andAny' ||
|
|
1230
|
+
value === null ||
|
|
1231
|
+
value.constructor !== Object) {
|
|
1232
|
+
agg[key] = value;
|
|
1233
|
+
}
|
|
1234
|
+
return agg;
|
|
1235
|
+
}, {});
|
|
1236
|
+
}
|
|
1237
|
+
conditionallyApplyDefaultScopes() {
|
|
1238
|
+
if (this.query['bypassAllDefaultScopes'] || this.query['bypassAllDefaultScopesExceptOnAssociations'])
|
|
1239
|
+
return this.query;
|
|
1240
|
+
const thisScopes = this.dreamClass['scopes'].default;
|
|
1241
|
+
let query = this.query;
|
|
1242
|
+
for (const scope of thisScopes) {
|
|
1243
|
+
if (!shouldBypassDefaultScope(scope.method, {
|
|
1244
|
+
defaultScopesToBypass: [
|
|
1245
|
+
...this.query['defaultScopesToBypass'],
|
|
1246
|
+
...this.query['defaultScopesToBypassExceptOnAssociations'],
|
|
1247
|
+
],
|
|
1248
|
+
})) {
|
|
1249
|
+
query = this.dreamClass[scope.method](query);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
return query;
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Each association in the chain is pushed onto `throughAssociations`
|
|
1256
|
+
* and `applyOneJoin` is recursively called. The trick is that the
|
|
1257
|
+
* through associations don't get written into the SQL; they
|
|
1258
|
+
* locate the next association we need to build into the SQL,
|
|
1259
|
+
* which is only run by the association that started the `through`
|
|
1260
|
+
* chain. The final association at the end of the `through` chain _is_
|
|
1261
|
+
* written into the SQL as a full association, but the modifications from
|
|
1262
|
+
* the `through` association are only added when the recursion returns
|
|
1263
|
+
* back to the association that kicked off the through associations.
|
|
1264
|
+
*/
|
|
1265
|
+
joinsBridgeThroughAssociations({ query, dreamClass, association, previousAssociationTableOrAlias, throughAssociations, joinType, }) {
|
|
1266
|
+
if (association.type === 'BelongsTo' || !association.through) {
|
|
1267
|
+
return {
|
|
1268
|
+
query,
|
|
1269
|
+
dreamClass,
|
|
1270
|
+
association,
|
|
1271
|
+
previousAssociationTableOrAlias,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
else {
|
|
1275
|
+
throughAssociations.push(association);
|
|
1276
|
+
// We have entered joinsBridgeThroughAssociations with the
|
|
1277
|
+
// CompositionAssetAudits HasOne User association, which
|
|
1278
|
+
// is through compositionAsset
|
|
1279
|
+
// We now apply the compositionAsset association (a BelongsTo)
|
|
1280
|
+
// to the query
|
|
1281
|
+
const { query: queryWithThroughAssociationApplied } = this.applyOneJoin({
|
|
1282
|
+
query,
|
|
1283
|
+
dreamClass,
|
|
1284
|
+
previousAssociationTableOrAlias,
|
|
1285
|
+
currentAssociationTableOrAlias: association.through,
|
|
1286
|
+
throughAssociations,
|
|
1287
|
+
joinType,
|
|
1288
|
+
});
|
|
1289
|
+
// The through association has both a `through` and a `source`. The `source`
|
|
1290
|
+
// is the association on the model that has now been joined. In our example,
|
|
1291
|
+
// the `source` is the `user` association on the CompositionAsset model
|
|
1292
|
+
const { newAssociation, throughAssociation, throughClass } = this.followThroughAssociation(dreamClass, association);
|
|
1293
|
+
if (newAssociation.through) {
|
|
1294
|
+
// This new association is itself a through association, so we recursively
|
|
1295
|
+
// call joinsBridgeThroughAssociations
|
|
1296
|
+
return this.joinsBridgeThroughAssociations({
|
|
1297
|
+
query: queryWithThroughAssociationApplied,
|
|
1298
|
+
dreamClass: throughClass,
|
|
1299
|
+
association: newAssociation,
|
|
1300
|
+
previousAssociationTableOrAlias: throughAssociation.as,
|
|
1301
|
+
throughAssociations,
|
|
1302
|
+
joinType,
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
else {
|
|
1306
|
+
// This new association is not a through association, so
|
|
1307
|
+
// this is the target association we were looking for
|
|
1308
|
+
return {
|
|
1309
|
+
query: queryWithThroughAssociationApplied,
|
|
1310
|
+
dreamClass: association.modelCB(),
|
|
1311
|
+
association: newAssociation,
|
|
1312
|
+
throughClass,
|
|
1313
|
+
previousAssociationTableOrAlias: association.through,
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* @internal
|
|
1320
|
+
*
|
|
1321
|
+
* Used to bridge through associations
|
|
1322
|
+
*/
|
|
1323
|
+
followThroughAssociation(dreamClass, association) {
|
|
1324
|
+
const throughAssociation = association.through && dreamClass['getAssociationMetadata'](association.through);
|
|
1325
|
+
if (!throughAssociation)
|
|
1326
|
+
throw new MissingThroughAssociation({
|
|
1327
|
+
dreamClass,
|
|
1328
|
+
association,
|
|
1329
|
+
});
|
|
1330
|
+
const throughClass = throughAssociation.modelCB();
|
|
1331
|
+
if (Array.isArray(throughClass))
|
|
1332
|
+
throw new CannotAssociateThroughPolymorphic({
|
|
1333
|
+
dreamClass,
|
|
1334
|
+
association,
|
|
1335
|
+
});
|
|
1336
|
+
const newAssociation = getSourceAssociation(throughClass, association.source);
|
|
1337
|
+
if (!newAssociation)
|
|
1338
|
+
throw new MissingThroughAssociationSource({
|
|
1339
|
+
dreamClass,
|
|
1340
|
+
throughClass,
|
|
1341
|
+
association,
|
|
1342
|
+
});
|
|
1343
|
+
return { throughAssociation, throughClass, newAssociation };
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Each association in the chain is pushed onto `throughAssociations`
|
|
1347
|
+
* and `applyOneJoin` is recursively called. The trick is that the
|
|
1348
|
+
* through associations don't get written into the SQL; they
|
|
1349
|
+
* locate the next association we need to build into the SQL,
|
|
1350
|
+
* which is only run by the association that started the `through`
|
|
1351
|
+
* chain. The final association at the end of the `through` chain _is_
|
|
1352
|
+
* written into the SQL as a full association, but the modifications from
|
|
1353
|
+
* the `through` association are only added when the recursion returns
|
|
1354
|
+
* back to the association that kicked off the through associations.
|
|
1355
|
+
*/
|
|
1356
|
+
applyOneJoin({ query, dreamClass, previousAssociationTableOrAlias, currentAssociationTableOrAlias, joinAndStatements = {}, throughAssociations = [], joinType, }) {
|
|
1357
|
+
const { name, alias } = extractAssociationMetadataFromAssociationName(currentAssociationTableOrAlias);
|
|
1358
|
+
const joinAndStatement = joinAndStatements[currentAssociationTableOrAlias];
|
|
1359
|
+
previousAssociationTableOrAlias = extractAssociationMetadataFromAssociationName(previousAssociationTableOrAlias).alias;
|
|
1360
|
+
currentAssociationTableOrAlias = alias;
|
|
1361
|
+
let association = dreamClass['getAssociationMetadata'](name);
|
|
1362
|
+
if (!association) {
|
|
1363
|
+
throw new JoinAttemptedOnMissingAssociation({
|
|
1364
|
+
dreamClass,
|
|
1365
|
+
associationName: currentAssociationTableOrAlias,
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
const results = this.joinsBridgeThroughAssociations({
|
|
1369
|
+
query,
|
|
1370
|
+
dreamClass,
|
|
1371
|
+
association,
|
|
1372
|
+
previousAssociationTableOrAlias,
|
|
1373
|
+
throughAssociations,
|
|
1374
|
+
joinType,
|
|
1375
|
+
});
|
|
1376
|
+
query = results.query;
|
|
1377
|
+
dreamClass = results.dreamClass;
|
|
1378
|
+
association = results.association;
|
|
1379
|
+
const timeToApplyThroughAssociations = throughAssociations.length && throughAssociations[0]?.source === association.as;
|
|
1380
|
+
const originalPreviousAssociationTableOrAlias = previousAssociationTableOrAlias;
|
|
1381
|
+
previousAssociationTableOrAlias = results.previousAssociationTableOrAlias;
|
|
1382
|
+
const throughClass = results.throughClass;
|
|
1383
|
+
if (timeToApplyThroughAssociations) {
|
|
1384
|
+
/**
|
|
1385
|
+
* Each association in the chain is pushed onto `throughAssociations`
|
|
1386
|
+
* and `applyOneJoin` is recursively called. The trick is that the
|
|
1387
|
+
* through associations don't get written into the SQL; they
|
|
1388
|
+
* locate the next association we need to build into the SQL,
|
|
1389
|
+
* which is only run by the association that started the `through`
|
|
1390
|
+
* chain (thus the
|
|
1391
|
+
* `throughAssociations.length && throughAssociations[0].source === association.as`
|
|
1392
|
+
* above). The final association at the end of the `through` chain _is_
|
|
1393
|
+
* written into the SQL as a full association, but the modifications from
|
|
1394
|
+
* the `through` association are only added when the recursion returns
|
|
1395
|
+
* back to the association that kicked off the through associations.
|
|
1396
|
+
*/
|
|
1397
|
+
throughAssociations.forEach((throughAssociation) => {
|
|
1398
|
+
if (throughAssociation.type === 'HasMany') {
|
|
1399
|
+
if (query?.distinctOn && throughAssociation.distinct) {
|
|
1400
|
+
query = query.distinctOn(this.distinctColumnNameForAssociation({
|
|
1401
|
+
association: throughAssociation,
|
|
1402
|
+
tableNameOrAlias: currentAssociationTableOrAlias,
|
|
1403
|
+
foreignKey: throughAssociation.primaryKey(),
|
|
1404
|
+
}));
|
|
1405
|
+
}
|
|
1406
|
+
if (throughAssociation.order) {
|
|
1407
|
+
query = this.applyOrderStatementForAssociation({
|
|
1408
|
+
query,
|
|
1409
|
+
association: throughAssociation,
|
|
1410
|
+
tableNameOrAlias: currentAssociationTableOrAlias,
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
if (association.type === 'BelongsTo') {
|
|
1417
|
+
if (Array.isArray(association.modelCB()))
|
|
1418
|
+
throw new CannotJoinPolymorphicBelongsToError({
|
|
1419
|
+
dreamClass,
|
|
1420
|
+
association,
|
|
1421
|
+
innerJoinStatements: this.query['innerJoinStatements'],
|
|
1422
|
+
leftJoinStatements: this.query['leftJoinStatements'],
|
|
1423
|
+
});
|
|
1424
|
+
const to = association.modelCB().table;
|
|
1425
|
+
const joinTableExpression = currentAssociationTableOrAlias === to
|
|
1426
|
+
? currentAssociationTableOrAlias
|
|
1427
|
+
: `${to} as ${currentAssociationTableOrAlias}`;
|
|
1428
|
+
query = query[(joinType === 'inner' ? 'innerJoin' : 'leftJoin')](joinTableExpression, (join) => {
|
|
1429
|
+
join = join.onRef(this.namespaceColumn(association.foreignKey(), previousAssociationTableOrAlias), '=', this.namespaceColumn(association.primaryKey(), currentAssociationTableOrAlias));
|
|
1430
|
+
if (timeToApplyThroughAssociations) {
|
|
1431
|
+
throughAssociations.forEach((throughAssociation) => {
|
|
1432
|
+
join = this.applyAssociationAndStatementsToJoinStatement({
|
|
1433
|
+
join,
|
|
1434
|
+
association: throughAssociation,
|
|
1435
|
+
currentAssociationTableOrAlias,
|
|
1436
|
+
previousAssociationTableOrAlias: originalPreviousAssociationTableOrAlias,
|
|
1437
|
+
joinAndStatements,
|
|
1438
|
+
});
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
join = this.conditionallyApplyDefaultScopesDependentOnAssociation({
|
|
1442
|
+
join,
|
|
1443
|
+
tableNameOrAlias: currentAssociationTableOrAlias,
|
|
1444
|
+
association,
|
|
1445
|
+
});
|
|
1446
|
+
join = this.applyJoinAndStatement(join, joinAndStatement, currentAssociationTableOrAlias);
|
|
1447
|
+
return join;
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
else {
|
|
1451
|
+
const to = association.modelCB().table;
|
|
1452
|
+
const joinTableExpression = currentAssociationTableOrAlias === to
|
|
1453
|
+
? currentAssociationTableOrAlias
|
|
1454
|
+
: `${to} as ${currentAssociationTableOrAlias}`;
|
|
1455
|
+
query = query[(joinType === 'inner' ? 'innerJoin' : 'leftJoin')](joinTableExpression, (join) => {
|
|
1456
|
+
join = join.onRef(this.namespaceColumn(association.primaryKey(), previousAssociationTableOrAlias), '=', this.namespaceColumn(association.foreignKey(), currentAssociationTableOrAlias));
|
|
1457
|
+
if (association.polymorphic) {
|
|
1458
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(eb, this.aliasWhereStatement({
|
|
1459
|
+
[association.foreignKeyTypeField()]: throughClass
|
|
1460
|
+
? throughClass['stiBaseClassOrOwnClassName']
|
|
1461
|
+
: dreamClass['stiBaseClassOrOwnClassName'],
|
|
1462
|
+
}, currentAssociationTableOrAlias)));
|
|
1463
|
+
}
|
|
1464
|
+
if (timeToApplyThroughAssociations) {
|
|
1465
|
+
throughAssociations.forEach((throughAssociation) => {
|
|
1466
|
+
join = this.applyAssociationAndStatementsToJoinStatement({
|
|
1467
|
+
join,
|
|
1468
|
+
association: throughAssociation,
|
|
1469
|
+
currentAssociationTableOrAlias,
|
|
1470
|
+
previousAssociationTableOrAlias: originalPreviousAssociationTableOrAlias,
|
|
1471
|
+
joinAndStatements,
|
|
1472
|
+
});
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
join = this.applyAssociationAndStatementsToJoinStatement({
|
|
1476
|
+
join,
|
|
1477
|
+
association,
|
|
1478
|
+
currentAssociationTableOrAlias,
|
|
1479
|
+
previousAssociationTableOrAlias,
|
|
1480
|
+
joinAndStatements,
|
|
1481
|
+
});
|
|
1482
|
+
join = this.conditionallyApplyDefaultScopesDependentOnAssociation({
|
|
1483
|
+
join,
|
|
1484
|
+
tableNameOrAlias: currentAssociationTableOrAlias,
|
|
1485
|
+
association,
|
|
1486
|
+
});
|
|
1487
|
+
join = this.applyJoinAndStatement(join, joinAndStatement, currentAssociationTableOrAlias);
|
|
1488
|
+
return join;
|
|
1489
|
+
});
|
|
1490
|
+
if (association.type === 'HasMany') {
|
|
1491
|
+
if (association.order) {
|
|
1492
|
+
query = this.applyOrderStatementForAssociation({
|
|
1493
|
+
query,
|
|
1494
|
+
tableNameOrAlias: currentAssociationTableOrAlias,
|
|
1495
|
+
association,
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
if (query.distinctOn && association.distinct) {
|
|
1499
|
+
query = query.distinctOn(this.distinctColumnNameForAssociation({
|
|
1500
|
+
association,
|
|
1501
|
+
tableNameOrAlias: currentAssociationTableOrAlias,
|
|
1502
|
+
foreignKey: association.foreignKey(),
|
|
1503
|
+
}));
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return {
|
|
1508
|
+
query,
|
|
1509
|
+
association,
|
|
1510
|
+
previousAssociationTableOrAlias,
|
|
1511
|
+
currentAssociationTableOrAlias,
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
applyOrderStatementForAssociation({ query, tableNameOrAlias, association, }) {
|
|
1515
|
+
if (!query.orderBy)
|
|
1516
|
+
return query;
|
|
1517
|
+
let selectQuery = query;
|
|
1518
|
+
const orderStatement = association.order;
|
|
1519
|
+
if (typeof orderStatement === 'string') {
|
|
1520
|
+
selectQuery = selectQuery.orderBy(this.namespaceColumn(orderStatement, tableNameOrAlias), 'asc');
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
Object.keys(orderStatement).forEach(column => {
|
|
1524
|
+
const direction = orderStatement[column];
|
|
1525
|
+
selectQuery = selectQuery.orderBy(this.namespaceColumn(column, tableNameOrAlias), direction);
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
return selectQuery;
|
|
1529
|
+
}
|
|
1530
|
+
distinctColumnNameForAssociation({ association, tableNameOrAlias, foreignKey, }) {
|
|
1531
|
+
if (!association.distinct)
|
|
1532
|
+
return null;
|
|
1533
|
+
if (association.distinct === true)
|
|
1534
|
+
return this.namespaceColumn(foreignKey, tableNameOrAlias);
|
|
1535
|
+
return this.namespaceColumn(association.distinct, tableNameOrAlias);
|
|
1536
|
+
}
|
|
1537
|
+
applyAssociationAndStatementsToJoinStatement({ join, currentAssociationTableOrAlias, previousAssociationTableOrAlias, association, joinAndStatements, }) {
|
|
1538
|
+
if (association.and) {
|
|
1539
|
+
this.throwUnlessAllRequiredWhereClausesProvided(association, currentAssociationTableOrAlias, joinAndStatements);
|
|
1540
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(eb, this.aliasWhereStatement(association.and, currentAssociationTableOrAlias), { disallowSimilarityOperator: false }));
|
|
1541
|
+
}
|
|
1542
|
+
if (association.andNot) {
|
|
1543
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(eb, this.aliasWhereStatement(association.andNot, currentAssociationTableOrAlias), { negate: true }));
|
|
1544
|
+
}
|
|
1545
|
+
if (association.andAny) {
|
|
1546
|
+
join = join.on((eb) => eb.or(association.andAny.map(whereAnyStatement => this.whereStatementToExpressionWrapper(eb, this.aliasWhereStatement(whereAnyStatement, currentAssociationTableOrAlias), { disallowSimilarityOperator: false }))));
|
|
1547
|
+
}
|
|
1548
|
+
if (association.selfAnd) {
|
|
1549
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(eb, this.rawifiedSelfOnClause({
|
|
1550
|
+
associationAlias: association.as,
|
|
1551
|
+
selfAlias: previousAssociationTableOrAlias,
|
|
1552
|
+
selfAndClause: association.selfAnd,
|
|
1553
|
+
})));
|
|
1554
|
+
}
|
|
1555
|
+
if (association.selfAndNot) {
|
|
1556
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(eb, this.rawifiedSelfOnClause({
|
|
1557
|
+
associationAlias: association.as,
|
|
1558
|
+
selfAlias: previousAssociationTableOrAlias,
|
|
1559
|
+
selfAndClause: association.selfAndNot,
|
|
1560
|
+
}), { negate: true }));
|
|
1561
|
+
}
|
|
1562
|
+
return join;
|
|
1563
|
+
}
|
|
1564
|
+
throwUnlessAllRequiredWhereClausesProvided(association, namespace, joinAndStatements) {
|
|
1565
|
+
const andClause = association.and;
|
|
1566
|
+
const columnsRequiringAndStatements = Object.keys(andClause).reduce((agg, column) => {
|
|
1567
|
+
if (andClause[column] === DreamConst.required)
|
|
1568
|
+
agg.push(column);
|
|
1569
|
+
return agg;
|
|
1570
|
+
}, []);
|
|
1571
|
+
const missingRequiredWhereStatements = columnsRequiringAndStatements.filter(column => joinAndStatements[namespace]?.and?.[column] === undefined);
|
|
1572
|
+
if (missingRequiredWhereStatements.length)
|
|
1573
|
+
throw new MissingRequiredAssociationAndClause(association, missingRequiredWhereStatements[0]);
|
|
1574
|
+
}
|
|
1575
|
+
conditionallyApplyDefaultScopesDependentOnAssociation({ join, tableNameOrAlias, association, }) {
|
|
1576
|
+
let scopesQuery = new Query(this.dreamInstance);
|
|
1577
|
+
const associationClass = association.modelCB();
|
|
1578
|
+
const associationScopes = associationClass['scopes'].default;
|
|
1579
|
+
for (const scope of associationScopes) {
|
|
1580
|
+
if (!shouldBypassDefaultScope(scope.method, {
|
|
1581
|
+
bypassAllDefaultScopes: this.query['bypassAllDefaultScopes'],
|
|
1582
|
+
defaultScopesToBypass: [
|
|
1583
|
+
...this.query['defaultScopesToBypass'],
|
|
1584
|
+
...(association.withoutDefaultScopes || []),
|
|
1585
|
+
],
|
|
1586
|
+
})) {
|
|
1587
|
+
const tempQuery = associationClass[scope.method](scopesQuery);
|
|
1588
|
+
// The scope method on a Dream model should return a clone of the Query it receives
|
|
1589
|
+
// (e.g. by returning `scope.where(...)`), but in case the function doesn't return,
|
|
1590
|
+
// or returns the wrong thing, we check before overriding `scopesQuery` with what the
|
|
1591
|
+
// method returned.
|
|
1592
|
+
if (tempQuery && tempQuery.constructor === scopesQuery.constructor)
|
|
1593
|
+
scopesQuery = tempQuery;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
if (scopesQuery['whereStatements'].length) {
|
|
1597
|
+
join = join.on((eb) => eb.and(scopesQuery['whereStatements'].flatMap(whereStatement => this.whereStatementToExpressionWrapper(eb, this.aliasWhereStatement(whereStatement, tableNameOrAlias), { disallowSimilarityOperator: false }))));
|
|
1598
|
+
}
|
|
1599
|
+
return join;
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* @internal
|
|
1603
|
+
*
|
|
1604
|
+
* Polymorphic BelongsTo. Since polymorphic associations may point to multiple tables,
|
|
1605
|
+
* preload by loading each target class separately.
|
|
1606
|
+
*
|
|
1607
|
+
* Used to preload polymorphic belongs to associations
|
|
1608
|
+
*/
|
|
1609
|
+
async preloadPolymorphicBelongsTo(association, dreams) {
|
|
1610
|
+
if (!association.polymorphic)
|
|
1611
|
+
throw new Error(`Association ${association.as} points to an array of models but is not designated polymorphic`);
|
|
1612
|
+
if (association.type !== 'BelongsTo')
|
|
1613
|
+
throw new Error(`Polymorphic association ${association.as} points to an array of models but is ${association.type}. Only BelongsTo associations may point to an array of models.`);
|
|
1614
|
+
const associatedDreams = [];
|
|
1615
|
+
for (const associatedModel of association.modelCB()) {
|
|
1616
|
+
await this.preloadPolymorphicAssociationModel(dreams, association, associatedModel, associatedDreams);
|
|
1617
|
+
}
|
|
1618
|
+
return associatedDreams;
|
|
1619
|
+
}
|
|
1620
|
+
async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass, associatedDreams) {
|
|
1621
|
+
const relevantAssociatedModels = dreams.filter((dream) => {
|
|
1622
|
+
const field = association.foreignKeyTypeField();
|
|
1623
|
+
return dream[field] === associatedDreamClass['stiBaseClassOrOwnClassName'] || dream[field] === null;
|
|
1624
|
+
});
|
|
1625
|
+
if (relevantAssociatedModels.length) {
|
|
1626
|
+
dreams.forEach((dream) => {
|
|
1627
|
+
dream[associationToGetterSetterProp(association)] = null;
|
|
1628
|
+
});
|
|
1629
|
+
// Load all models of type associated that are associated with any of the already loaded Dream models
|
|
1630
|
+
const loadedAssociations = await this.dreamClassQueryWithScopeBypasses(associatedDreamClass, {
|
|
1631
|
+
// The association may remove specific default scopes that would otherwise preclude
|
|
1632
|
+
// certain instances of the associated class from being found.
|
|
1633
|
+
defaultScopesToBypassExceptOnAssociations: association.withoutDefaultScopes,
|
|
1634
|
+
})
|
|
1635
|
+
.where({
|
|
1636
|
+
[associatedDreamClass.primaryKey]: relevantAssociatedModels.map((dream) => dream[association.foreignKey()]),
|
|
1637
|
+
})
|
|
1638
|
+
.all();
|
|
1639
|
+
loadedAssociations.forEach((loadedAssociation) => associatedDreams.push(loadedAssociation));
|
|
1640
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
1641
|
+
// Associate each loaded association with each dream based on primary key and foreign key type
|
|
1642
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
1643
|
+
for (const loadedAssociation of loadedAssociations) {
|
|
1644
|
+
dreams
|
|
1645
|
+
.filter((dream) => {
|
|
1646
|
+
return (dream[association.foreignKeyTypeField()] === loadedAssociation['stiBaseClassOrOwnClassName'] &&
|
|
1647
|
+
dream[association.foreignKey()] === association.primaryKeyValue(loadedAssociation));
|
|
1648
|
+
})
|
|
1649
|
+
.forEach((dream) => {
|
|
1650
|
+
dream[association.as] = loadedAssociation;
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1654
|
+
// end: Associate each loaded association with each dream based on primary key and foreign key type
|
|
1655
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* @internal
|
|
1660
|
+
*
|
|
1661
|
+
* Used for applying preload and load statements
|
|
1662
|
+
*
|
|
1663
|
+
* @returns An associated Query
|
|
1664
|
+
*/
|
|
1665
|
+
dreamClassQueryWithScopeBypasses(dreamClass, { bypassAllDefaultScopesExceptOnAssociations = false, defaultScopesToBypassExceptOnAssociations = [], } = {}) {
|
|
1666
|
+
const associationQuery = dreamClass.query().clone({
|
|
1667
|
+
passthroughOnStatement: this.query['passthroughOnStatement'],
|
|
1668
|
+
bypassAllDefaultScopes: this.query['bypassAllDefaultScopes'],
|
|
1669
|
+
bypassAllDefaultScopesExceptOnAssociations,
|
|
1670
|
+
defaultScopesToBypass: this.query['defaultScopesToBypass'],
|
|
1671
|
+
defaultScopesToBypassExceptOnAssociations,
|
|
1672
|
+
});
|
|
1673
|
+
return (this.dreamTransaction ? associationQuery.txn(this.dreamTransaction) : associationQuery);
|
|
1674
|
+
}
|
|
1675
|
+
conditionallyAttachSimilarityColumnsToSelect(kyselyQuery, { bypassOrder = false } = {}) {
|
|
1676
|
+
const similarityBuilder = this.similarityStatementBuilder();
|
|
1677
|
+
if (similarityBuilder.hasSimilarityClauses) {
|
|
1678
|
+
kyselyQuery = similarityBuilder.select(kyselyQuery, { bypassOrder });
|
|
1679
|
+
}
|
|
1680
|
+
return kyselyQuery;
|
|
1681
|
+
}
|
|
1682
|
+
conditionallyAttachSimilarityColumnsToUpdate(kyselyQuery) {
|
|
1683
|
+
const similarityBuilder = this.similarityStatementBuilder();
|
|
1684
|
+
if (similarityBuilder.hasSimilarityClauses) {
|
|
1685
|
+
kyselyQuery = similarityBuilder.update(kyselyQuery);
|
|
1686
|
+
}
|
|
1687
|
+
return kyselyQuery;
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* @internal
|
|
1691
|
+
*
|
|
1692
|
+
* Applies a preload statement
|
|
1693
|
+
*/
|
|
1694
|
+
async applyOnePreload(associationName, dreams, onStatement = {}) {
|
|
1695
|
+
if (!Array.isArray(dreams))
|
|
1696
|
+
dreams = [dreams];
|
|
1697
|
+
const dream = dreams.find(dream => dream['getAssociationMetadata'](associationName));
|
|
1698
|
+
if (!dream)
|
|
1699
|
+
return;
|
|
1700
|
+
const { name, alias } = extractAssociationMetadataFromAssociationName(associationName);
|
|
1701
|
+
const association = dream['getAssociationMetadata'](name);
|
|
1702
|
+
if (association === undefined)
|
|
1703
|
+
throw new UnexpectedUndefined();
|
|
1704
|
+
const dreamClass = dream.constructor;
|
|
1705
|
+
const dreamClassToHydrate = association.modelCB();
|
|
1706
|
+
if ((association.polymorphic && association.type === 'BelongsTo') || Array.isArray(dreamClassToHydrate))
|
|
1707
|
+
return this.preloadPolymorphicBelongsTo(association, dreams);
|
|
1708
|
+
const dreamClassToHydrateColumns = [...dreamClassToHydrate.columns()];
|
|
1709
|
+
const throughColumnsToHydrate = [];
|
|
1710
|
+
const columnsToPluck = dreamClassToHydrateColumns.map(column => this.namespaceColumn(column.toString(), alias));
|
|
1711
|
+
const asHasAssociation = association;
|
|
1712
|
+
if (asHasAssociation.through && asHasAssociation.preloadThroughColumns) {
|
|
1713
|
+
if (isObject(asHasAssociation.preloadThroughColumns)) {
|
|
1714
|
+
const preloadMap = asHasAssociation.preloadThroughColumns;
|
|
1715
|
+
Object.keys(preloadMap).forEach(preloadThroughColumn => {
|
|
1716
|
+
throughColumnsToHydrate.push(preloadMap[preloadThroughColumn]);
|
|
1717
|
+
columnsToPluck.push(this.namespaceColumn(preloadThroughColumn, asHasAssociation.through));
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
else {
|
|
1721
|
+
const preloadArray = asHasAssociation.preloadThroughColumns;
|
|
1722
|
+
preloadArray.forEach(preloadThroughColumn => {
|
|
1723
|
+
throughColumnsToHydrate.push(preloadThroughColumn);
|
|
1724
|
+
columnsToPluck.push(this.namespaceColumn(preloadThroughColumn, asHasAssociation.through));
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
columnsToPluck.push(this.namespaceColumn(dreamClass.primaryKey, dreamClass.table));
|
|
1729
|
+
const baseClass = dreamClass['stiBaseClassOrOwnClass']['getAssociationMetadata'](associationName)
|
|
1730
|
+
? dreamClass['stiBaseClassOrOwnClass']
|
|
1731
|
+
: dreamClass;
|
|
1732
|
+
const associationDataScope = this.dreamClassQueryWithScopeBypasses(baseClass, {
|
|
1733
|
+
// In order to stay DRY, preloading leverages the association logic built into
|
|
1734
|
+
// `joins` (by using `pluck`, which calls `joins`). However, baseClass may have
|
|
1735
|
+
// default scopes that would preclude finding that instance. We remove all
|
|
1736
|
+
// default scopes on baseClass, but not subsequent associations, so that the
|
|
1737
|
+
// single query will be able to find each row corresponding to a Dream in `dreams`,
|
|
1738
|
+
// regardless of default scopes on that Dream's class.
|
|
1739
|
+
bypassAllDefaultScopesExceptOnAssociations: true,
|
|
1740
|
+
}).where({
|
|
1741
|
+
[dreamClass.primaryKey]: dreams.map(obj => obj.primaryKeyValue),
|
|
1742
|
+
});
|
|
1743
|
+
const hydrationData = await associationDataScope['_connection'](this.connectionOverride)
|
|
1744
|
+
.innerJoin(associationName, (onStatement || {}))
|
|
1745
|
+
.pluck(...columnsToPluck);
|
|
1746
|
+
const preloadedDreamsAndWhatTheyPointTo = hydrationData.map(pluckedData => {
|
|
1747
|
+
const attributes = {};
|
|
1748
|
+
dreamClassToHydrateColumns.forEach((columnName, index) => (attributes[protectAgainstPollutingAssignment(columnName)] = pluckedData[index]));
|
|
1749
|
+
const hydratedDream = this.dbResultToDreamInstance(attributes, dreamClassToHydrate);
|
|
1750
|
+
throughColumnsToHydrate.forEach((throughAssociationColumn, index) => (hydratedDream.preloadedThroughColumns[throughAssociationColumn] =
|
|
1751
|
+
pluckedData[dreamClassToHydrateColumns.length + index]));
|
|
1752
|
+
return {
|
|
1753
|
+
dream: hydratedDream,
|
|
1754
|
+
pointsToPrimaryKey: pluckedData.at(-1),
|
|
1755
|
+
};
|
|
1756
|
+
});
|
|
1757
|
+
this.hydrateAssociation(dreams, association, preloadedDreamsAndWhatTheyPointTo);
|
|
1758
|
+
return preloadedDreamsAndWhatTheyPointTo.map(obj => obj.dream);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
function getSourceAssociation(dream, sourceName) {
|
|
1762
|
+
if (!dream)
|
|
1763
|
+
return;
|
|
1764
|
+
if (!sourceName)
|
|
1765
|
+
return;
|
|
1766
|
+
return (dream['getAssociationMetadata'](sourceName) ||
|
|
1767
|
+
dream['getAssociationMetadata'](pluralize.singular(sourceName)));
|
|
1768
|
+
}
|