@rvoh/dream 2.18.1 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/Dream.js +186 -0
- package/dist/cjs/src/db/DreamDbConnection.js +6 -0
- package/dist/cjs/src/decorators/class/SoftDelete.js +12 -0
- package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +113 -0
- package/dist/cjs/src/dream/Query.js +148 -0
- package/dist/cjs/src/dream/QueryDriver/Base.js +84 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +420 -81
- package/dist/cjs/src/dream/internal/filterRowToKnownColumns.js +41 -0
- package/dist/cjs/src/dream/internal/saveDream.js +6 -1
- package/dist/cjs/src/dream/internal/sqlResultToDreamInstance.js +9 -2
- package/dist/cjs/src/errors/CannotNamespaceAssociationFilterToAnotherTable.js +27 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreAssociationColumn.js +38 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreEncryptedColumn.js +20 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnorePrimaryKey.js +18 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.js +21 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.js +20 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.js +24 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreStiTypeColumn.js +17 -0
- package/dist/cjs/src/errors/schema-builder/ConflictingIgnoredColumns.js +27 -0
- package/dist/cjs/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.js +21 -0
- package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +38 -1
- package/dist/cjs/src/helpers/cli/ASTKyselyCodegenEnhancer.js +60 -0
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +21 -13
- package/dist/cjs/src/helpers/cli/resolveIgnoredColumns.js +198 -0
- package/dist/esm/src/Dream.js +186 -0
- package/dist/esm/src/db/DreamDbConnection.js +6 -0
- package/dist/esm/src/decorators/class/SoftDelete.js +12 -0
- package/dist/esm/src/dream/DreamClassTransactionBuilder.js +113 -0
- package/dist/esm/src/dream/Query.js +148 -0
- package/dist/esm/src/dream/QueryDriver/Base.js +84 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +420 -81
- package/dist/esm/src/dream/internal/filterRowToKnownColumns.js +41 -0
- package/dist/esm/src/dream/internal/saveDream.js +6 -1
- package/dist/esm/src/dream/internal/sqlResultToDreamInstance.js +9 -2
- package/dist/esm/src/errors/CannotNamespaceAssociationFilterToAnotherTable.js +27 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreAssociationColumn.js +38 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreEncryptedColumn.js +20 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnorePrimaryKey.js +18 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.js +21 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.js +20 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.js +24 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreStiTypeColumn.js +17 -0
- package/dist/esm/src/errors/schema-builder/ConflictingIgnoredColumns.js +27 -0
- package/dist/esm/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.js +21 -0
- package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +38 -1
- package/dist/esm/src/helpers/cli/ASTKyselyCodegenEnhancer.js +60 -0
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +21 -13
- package/dist/esm/src/helpers/cli/resolveIgnoredColumns.js +198 -0
- package/dist/types/src/Dream.d.ts +178 -4
- package/dist/types/src/decorators/class/SoftDelete.d.ts +12 -0
- package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +103 -0
- package/dist/types/src/dream/Query.d.ts +138 -0
- package/dist/types/src/dream/QueryDriver/Base.d.ts +69 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +187 -8
- package/dist/types/src/dream/internal/filterRowToKnownColumns.d.ts +30 -0
- package/dist/types/src/errors/CannotNamespaceAssociationFilterToAnotherTable.d.ts +8 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreAssociationColumn.d.ts +12 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreEncryptedColumn.d.ts +7 -0
- package/dist/types/src/errors/schema-builder/CannotIgnorePrimaryKey.d.ts +6 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.d.ts +7 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.d.ts +7 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.d.ts +8 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreStiTypeColumn.d.ts +6 -0
- package/dist/types/src/errors/schema-builder/ConflictingIgnoredColumns.d.ts +7 -0
- package/dist/types/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.d.ts +7 -0
- package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +20 -0
- package/dist/types/src/helpers/cli/ASTKyselyCodegenEnhancer.d.ts +13 -0
- package/dist/types/src/helpers/cli/resolveIgnoredColumns.d.ts +41 -0
- package/dist/types/src/types/associations/shared.d.ts +4 -1
- package/dist/types/src/types/dream.d.ts +17 -0
- package/dist/types/src/types/types/associations/shared.ts +10 -1
- package/dist/types/src/types/types/dream.ts +53 -0
- package/dist/types/src/types/types/variadic.ts +179 -140
- package/dist/types/src/types/variadic.d.ts +15 -10
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +96 -34
- package/docs/classes/db.PostgresQueryDriver.html +97 -35
- package/docs/classes/db.QueryDriverBase.html +77 -33
- package/docs/classes/errors.CheckConstraintViolation.html +3 -3
- package/docs/classes/errors.ColumnOverflow.html +3 -3
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
- package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
- package/docs/classes/errors.DecryptionError.html +2 -2
- package/docs/classes/errors.DecryptionParseError.html +2 -2
- package/docs/classes/errors.DecryptionRotationError.html +3 -3
- package/docs/classes/errors.GlobalNameNotSet.html +3 -3
- package/docs/classes/errors.InvalidCalendarDate.html +2 -2
- package/docs/classes/errors.InvalidClockTime.html +2 -2
- package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
- package/docs/classes/errors.InvalidDateTime.html +2 -2
- package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
- package/docs/classes/errors.NonLoadedAssociation.html +3 -3
- package/docs/classes/errors.NotNullViolation.html +3 -3
- package/docs/classes/errors.RecordNotFound.html +3 -3
- package/docs/classes/errors.ValidationError.html +3 -3
- package/docs/classes/index.CalendarDate.html +33 -33
- package/docs/classes/index.ClockTime.html +32 -32
- package/docs/classes/index.ClockTimeTz.html +35 -35
- package/docs/classes/index.DateTime.html +86 -86
- package/docs/classes/index.Decorators.html +19 -19
- package/docs/classes/index.Dream.html +247 -119
- package/docs/classes/index.DreamApp.html +10 -10
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +155 -57
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +7 -7
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +11 -11
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/system.PathHelpers.html +3 -3
- package/docs/classes/utils.Encrypt.html +3 -3
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +12 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +1 -1
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +1 -1
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/hierarchy.html +1 -1
- package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
- package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/types.BelongsToStatement.html +2 -2
- package/docs/interfaces/types.DecoratorContext.html +2 -2
- package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
- package/docs/interfaces/types.DreamAppOpts.html +2 -2
- package/docs/interfaces/types.DreamDbConfig.html +5 -5
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
- package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/openapi.OpenapiAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiFormats.html +1 -1
- package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- package/package.json +3 -3
- package/dist/cjs/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.js +0 -11
- package/dist/cjs/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js +0 -17
- package/dist/esm/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.js +0 -11
- package/dist/esm/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js +0 -17
- package/dist/types/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.d.ts +0 -13
- package/dist/types/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.d.ts +0 -12
|
@@ -26,8 +26,8 @@ import MissingRequiredAssociationAndClause from '../../errors/associations/Missi
|
|
|
26
26
|
import MissingRequiredPassthroughForAssociationAndClause from '../../errors/associations/MissingRequiredPassthroughForAssociationAndClause.js';
|
|
27
27
|
import MissingThroughAssociation from '../../errors/associations/MissingThroughAssociation.js';
|
|
28
28
|
import MissingThroughAssociationSource from '../../errors/associations/MissingThroughAssociationSource.js';
|
|
29
|
-
import ThroughAssociationConditionsIncompatibleWithThroughAssociationSource from '../../errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js';
|
|
30
29
|
import CannotNegateSimilarityClause from '../../errors/CannotNegateSimilarityClause.js';
|
|
30
|
+
import CannotNamespaceAssociationFilterToAnotherTable from '../../errors/CannotNamespaceAssociationFilterToAnotherTable.js';
|
|
31
31
|
import CannotPassUndefinedAsAValueToAWhereClause from '../../errors/CannotPassUndefinedAsAValueToAWhereClause.js';
|
|
32
32
|
import CheckConstraintViolation from '../../errors/db/CheckConstraintViolation.js';
|
|
33
33
|
import ColumnOverflow from '../../errors/db/ColumnOverflow.js';
|
|
@@ -55,7 +55,6 @@ import CurriedOpsStatement from '../../ops/curried-ops-statement.js';
|
|
|
55
55
|
import OpsStatement from '../../ops/ops-statement.js';
|
|
56
56
|
import { DreamConst, primaryKeyTypes } from '../constants.js';
|
|
57
57
|
import DreamTransaction from '../DreamTransaction.js';
|
|
58
|
-
import throughAssociationHasOptionsBesidesThroughAndSource from '../internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.js';
|
|
59
58
|
import associationStringToNameAndAlias from '../internal/associationStringToNameAndAlias.js';
|
|
60
59
|
import executeDatabaseQuery from '../internal/executeDatabaseQuery.js';
|
|
61
60
|
import extractAssignableAssociationAttributes from '../internal/extractAssignableAssociationAttributes.js';
|
|
@@ -213,6 +212,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
213
212
|
await DreamCLI.logger.logProgress('[dream] sync failed, reverting file contents...', async () => {
|
|
214
213
|
await CliFileWriter.revert();
|
|
215
214
|
});
|
|
215
|
+
// rethrow after reverting so callers (e.g. the `psy sync` and
|
|
216
|
+
// `db:migrate` CLI commands) exit nonzero instead of reporting success
|
|
217
|
+
// with stale generated types
|
|
218
|
+
throw error;
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
221
|
static get syncDialect() {
|
|
@@ -441,6 +444,24 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
441
444
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
442
445
|
return data.max;
|
|
443
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Retrieves the max value of the specified column within each group,
|
|
449
|
+
* keyed by the value of the provided group column.
|
|
450
|
+
*
|
|
451
|
+
* ```ts
|
|
452
|
+
* await CompositionAsset.query().maxBy('name', 'score')
|
|
453
|
+
* // Map(2) { 'primary' => 9, 'secondary' => 4 }
|
|
454
|
+
* ```
|
|
455
|
+
*
|
|
456
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
457
|
+
* @param aggregatedColumn - the column to take the max of within each group
|
|
458
|
+
* @returns A Map from each present group value to the max of the aggregated column in that group
|
|
459
|
+
*/
|
|
460
|
+
async maxBy(groupColumn, aggregatedColumn) {
|
|
461
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
462
|
+
const { max } = this.dbFor('select').fn;
|
|
463
|
+
return this.groupedAggregate(groupColumn, max(aggregatedColumn));
|
|
464
|
+
}
|
|
444
465
|
/**
|
|
445
466
|
* Retrieves the min value of the specified column
|
|
446
467
|
* for this Query
|
|
@@ -464,6 +485,24 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
464
485
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
465
486
|
return data.min;
|
|
466
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Retrieves the min value of the specified column within each group,
|
|
490
|
+
* keyed by the value of the provided group column.
|
|
491
|
+
*
|
|
492
|
+
* ```ts
|
|
493
|
+
* await CompositionAsset.query().minBy('name', 'score')
|
|
494
|
+
* // Map(2) { 'primary' => 1, 'secondary' => 4 }
|
|
495
|
+
* ```
|
|
496
|
+
*
|
|
497
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
498
|
+
* @param aggregatedColumn - the column to take the min of within each group
|
|
499
|
+
* @returns A Map from each present group value to the min of the aggregated column in that group
|
|
500
|
+
*/
|
|
501
|
+
async minBy(groupColumn, aggregatedColumn) {
|
|
502
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
503
|
+
const { min } = this.dbFor('select').fn;
|
|
504
|
+
return this.groupedAggregate(groupColumn, min(aggregatedColumn));
|
|
505
|
+
}
|
|
467
506
|
/**
|
|
468
507
|
* Retrieves the sum value of the specified column
|
|
469
508
|
* for this Query
|
|
@@ -487,6 +526,24 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
487
526
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
488
527
|
return data.sum === null ? null : parseFloat(data.sum);
|
|
489
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Retrieves the sum of the specified column within each group,
|
|
531
|
+
* keyed by the value of the provided group column.
|
|
532
|
+
*
|
|
533
|
+
* ```ts
|
|
534
|
+
* await CompositionAsset.query().sumBy('name', 'score')
|
|
535
|
+
* // Map(2) { 'primary' => 10, 'secondary' => 4 }
|
|
536
|
+
* ```
|
|
537
|
+
*
|
|
538
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
539
|
+
* @param aggregatedColumn - the column to sum within each group
|
|
540
|
+
* @returns A Map from each present group value to the sum of the aggregated column in that group
|
|
541
|
+
*/
|
|
542
|
+
async sumBy(groupColumn, aggregatedColumn) {
|
|
543
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
544
|
+
const { sum } = this.dbFor('select').fn;
|
|
545
|
+
return this.groupedAggregate(groupColumn, sum(aggregatedColumn), rawSum => rawSum === null ? null : parseFloat(rawSum));
|
|
546
|
+
}
|
|
490
547
|
/**
|
|
491
548
|
* Retrieves the average value of the specified column
|
|
492
549
|
* for this Query
|
|
@@ -510,6 +567,24 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
510
567
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
511
568
|
return data.avg;
|
|
512
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* Retrieves the average of the specified column within each group,
|
|
572
|
+
* keyed by the value of the provided group column.
|
|
573
|
+
*
|
|
574
|
+
* ```ts
|
|
575
|
+
* await CompositionAsset.query().avgBy('name', 'score')
|
|
576
|
+
* // Map(2) { 'primary' => 5, 'secondary' => 4 }
|
|
577
|
+
* ```
|
|
578
|
+
*
|
|
579
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
580
|
+
* @param aggregatedColumn - the column to average within each group
|
|
581
|
+
* @returns A Map from each present group value to the average of the aggregated column in that group
|
|
582
|
+
*/
|
|
583
|
+
async avgBy(groupColumn, aggregatedColumn) {
|
|
584
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
585
|
+
const { avg } = this.dbFor('select').fn;
|
|
586
|
+
return this.groupedAggregate(groupColumn, avg(aggregatedColumn));
|
|
587
|
+
}
|
|
513
588
|
/**
|
|
514
589
|
* Retrieves the number of records in the database
|
|
515
590
|
*
|
|
@@ -535,6 +610,59 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
535
610
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
536
611
|
return parseInt(data.tablecount.toString());
|
|
537
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* Retrieves the number of records in each group, keyed by the
|
|
615
|
+
* value of the provided group column.
|
|
616
|
+
*
|
|
617
|
+
* ```ts
|
|
618
|
+
* await User.query().countBy('name')
|
|
619
|
+
* // Map(2) { 'fred' => 2, 'zed' => 1 }
|
|
620
|
+
* ```
|
|
621
|
+
*
|
|
622
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
623
|
+
* @returns A Map from each present group value to the number of records in that group
|
|
624
|
+
*/
|
|
625
|
+
async countBy(groupColumn) {
|
|
626
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
627
|
+
const { count } = this.dbFor('select').fn;
|
|
628
|
+
const primaryKeyRef = this.namespaceColumn(this.query.dreamInstance['_primaryKey']);
|
|
629
|
+
return this.groupedAggregate(groupColumn, count(primaryKeyRef), rawCount => parseInt(rawCount.toString()));
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* @internal
|
|
633
|
+
*
|
|
634
|
+
* Shared plumbing for grouped aggregates (`countBy` and the value-aggregate
|
|
635
|
+
* siblings `minBy` / `maxBy` / `sumBy` / `avgBy`). Selects the group column
|
|
636
|
+
* alongside a single aggregate expression, groups by the group column, executes,
|
|
637
|
+
* and folds the resulting rows into a Map keyed by the group value.
|
|
638
|
+
*
|
|
639
|
+
* The group column and the aggregate are selected under stable, underscore-free
|
|
640
|
+
* aliases so they can be read back off each row without being affected by
|
|
641
|
+
* Kysely's CamelCasePlugin (which would otherwise mangle a namespaced alias),
|
|
642
|
+
* mirroring the short-alias strategy used by `pluck`.
|
|
643
|
+
*
|
|
644
|
+
* @param groupColumn - the column to GROUP BY (base or joined-association-namespaced)
|
|
645
|
+
* @param aggregateExpression - the Kysely aggregate expression to select per group (e.g. `count(pk)`)
|
|
646
|
+
* @param coerceValue - maps each raw aggregate value to the value stored in the returned Map
|
|
647
|
+
* @returns A Map from each present group value to its coerced aggregate value
|
|
648
|
+
*/
|
|
649
|
+
async groupedAggregate(groupColumn, aggregateExpression, coerceValue) {
|
|
650
|
+
const groupColumnRef = this.namespaceColumn(groupColumn);
|
|
651
|
+
let kyselyQuery = new this.constructor(this.query).buildSelect({
|
|
652
|
+
bypassSelectAll: true,
|
|
653
|
+
bypassOrder: true,
|
|
654
|
+
});
|
|
655
|
+
kyselyQuery = kyselyQuery
|
|
656
|
+
.select(`${groupColumnRef} as groupvalue`)
|
|
657
|
+
.select(aggregateExpression.as('aggregatevalue'))
|
|
658
|
+
.groupBy(groupColumnRef);
|
|
659
|
+
const rows = await executeDatabaseQuery(kyselyQuery, 'execute');
|
|
660
|
+
const result = new Map();
|
|
661
|
+
for (const row of rows) {
|
|
662
|
+
result.set(row.groupvalue, coerceValue ? coerceValue(row.aggregatevalue) : row.aggregatevalue);
|
|
663
|
+
}
|
|
664
|
+
return result;
|
|
665
|
+
}
|
|
538
666
|
/**
|
|
539
667
|
* @internal
|
|
540
668
|
*
|
|
@@ -606,6 +734,27 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
606
734
|
* is provided as a second argument, it will use that transaction
|
|
607
735
|
* to encapsulate the persisting of the dream, as well as any
|
|
608
736
|
* subsequent model hooks that are fired.
|
|
737
|
+
*
|
|
738
|
+
* `RETURNING *` (rather than enumerating the compiled column list) keeps
|
|
739
|
+
* writes working under schema/image skew: during a rolling deploy, a
|
|
740
|
+
* container built before a drop-column migration would otherwise name the
|
|
741
|
+
* dropped column in `RETURNING` and fail with `42703 column does not
|
|
742
|
+
* exist` on every write, even writes that never touch that column. The
|
|
743
|
+
* `SET`/`VALUES` half still names only dirty attributes, so a write that
|
|
744
|
+
* actually sets a dropped column still fails loudly. The returned row is
|
|
745
|
+
* filtered to the compiled column list before hydration (see
|
|
746
|
+
* internal/saveDream.ts), so a column the image doesn't know about never
|
|
747
|
+
* reaches `setAttributes`.
|
|
748
|
+
*
|
|
749
|
+
* KNOWN CONSTRAINT: star-selects are only safe because nothing in this
|
|
750
|
+
* stack uses named prepared statements — node-postgres prepares a
|
|
751
|
+
* statement only when given an explicit `name`, and Kysely never names
|
|
752
|
+
* them, so every query is re-planned. With named prepared statements, a
|
|
753
|
+
* concurrent `ADD COLUMN` changes a cached plan's result shape and
|
|
754
|
+
* Postgres raises `cached plan must not change result type` (the reason
|
|
755
|
+
* Rails added `enumerate_columns_in_select_statements`). If a future
|
|
756
|
+
* driver or pooling layer enables named prepared statements, revisit
|
|
757
|
+
* every `RETURNING *` / `select *` in this driver.
|
|
609
758
|
*/
|
|
610
759
|
static async saveDream(dream, txn = null) {
|
|
611
760
|
const connectionName = dream.connectionName || 'default';
|
|
@@ -617,13 +766,13 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
617
766
|
.updateTable(dream.table)
|
|
618
767
|
.set(sqlifiedAttributes)
|
|
619
768
|
.where(namespaceColumn(dream['_primaryKey'], dream.table), '=', dream.primaryKeyValue());
|
|
620
|
-
return await executeDatabaseQuery(query.
|
|
769
|
+
return await executeDatabaseQuery(query.returningAll(), 'executeTakeFirstOrThrow');
|
|
621
770
|
}
|
|
622
771
|
else {
|
|
623
772
|
const query = db
|
|
624
773
|
.insertInto(dream.table)
|
|
625
774
|
.values(sqlifiedAttributes)
|
|
626
|
-
.
|
|
775
|
+
.returningAll();
|
|
627
776
|
return await executeDatabaseQuery(query, 'executeTakeFirstOrThrow');
|
|
628
777
|
}
|
|
629
778
|
}
|
|
@@ -999,6 +1148,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
999
1148
|
}, {}), {
|
|
1000
1149
|
negate,
|
|
1001
1150
|
disallowSimilarityOperator,
|
|
1151
|
+
expectedAlias: rootTableOrAssociationAlias,
|
|
1002
1152
|
});
|
|
1003
1153
|
}
|
|
1004
1154
|
buildCommon(kyselyQuery) {
|
|
@@ -1041,20 +1191,25 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1041
1191
|
kyselyQuery = kyselyQuery.where((eb) => eb.and([
|
|
1042
1192
|
...this.aliasWhereStatements(query['whereStatements'], query['baseSqlAlias']).map(whereStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereStatement, {
|
|
1043
1193
|
disallowSimilarityOperator: false,
|
|
1194
|
+
expectedAlias: query['baseSqlAlias'],
|
|
1044
1195
|
})),
|
|
1045
1196
|
...this.aliasWhereStatements(query['whereNotStatements'], query['baseSqlAlias']).map(whereNotStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereNotStatement, {
|
|
1046
1197
|
negate: true,
|
|
1198
|
+
expectedAlias: query['baseSqlAlias'],
|
|
1047
1199
|
})),
|
|
1048
|
-
...query['whereAnyStatements'].map(whereAnyStatements => eb.or(this.aliasWhereStatements(whereAnyStatements, query['baseSqlAlias']).map(whereAnyStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereAnyStatement
|
|
1200
|
+
...query['whereAnyStatements'].map(whereAnyStatements => eb.or(this.aliasWhereStatements(whereAnyStatements, query['baseSqlAlias']).map(whereAnyStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereAnyStatement, {
|
|
1201
|
+
expectedAlias: query['baseSqlAlias'],
|
|
1202
|
+
})))),
|
|
1049
1203
|
]));
|
|
1050
1204
|
}
|
|
1051
1205
|
return kyselyQuery;
|
|
1052
1206
|
}
|
|
1053
|
-
whereStatementToExpressionWrapper(dreamClass, eb, whereStatement, { negate = false, disallowSimilarityOperator = true, } = {}) {
|
|
1207
|
+
whereStatementToExpressionWrapper(dreamClass, eb, whereStatement, { negate = false, disallowSimilarityOperator = true, expectedAlias, } = {}) {
|
|
1054
1208
|
const clauses = compact(Object.keys(whereStatement)
|
|
1055
1209
|
.filter(key => whereStatement[key] !== DreamConst.required)
|
|
1056
1210
|
.map(attr => {
|
|
1057
1211
|
const val = whereStatement[attr];
|
|
1212
|
+
this.validateAssociationFilterAlias(attr, val, expectedAlias);
|
|
1058
1213
|
if (val?.isOpsStatement &&
|
|
1059
1214
|
val.shouldBypassWhereStatement) {
|
|
1060
1215
|
if (disallowSimilarityOperator)
|
|
@@ -1063,6 +1218,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1063
1218
|
// and should be ommited from the where clause directly
|
|
1064
1219
|
return;
|
|
1065
1220
|
}
|
|
1221
|
+
if (this.isAssociationInstanceArray(dreamClass, attr, val))
|
|
1222
|
+
return this.associationInstanceArrayToExpressionWrapper(dreamClass, eb, attr, val, {
|
|
1223
|
+
negate,
|
|
1224
|
+
});
|
|
1066
1225
|
const { a, b, c, a2, b2, c2 } = this.dreamWhereStatementToExpressionBuilderParts(dreamClass, attr, val);
|
|
1067
1226
|
// postgres is unable to handle WHERE IN statements with blank arrays, such as in
|
|
1068
1227
|
// "WHERE id IN ()", meaning that:
|
|
@@ -1106,7 +1265,13 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1106
1265
|
//
|
|
1107
1266
|
}
|
|
1108
1267
|
else if (b === '=' && negate) {
|
|
1109
|
-
|
|
1268
|
+
const conditions = [eb(a, '=', c), eb(a, 'is not', null)];
|
|
1269
|
+
// when a second condition is present (e.g. the type field of a polymorphic
|
|
1270
|
+
// association), it must be included so that the caller negates the full
|
|
1271
|
+
// conjunction rather than the first condition alone
|
|
1272
|
+
if (b2)
|
|
1273
|
+
conditions.push(eb(a2, b2, c2), eb(a2, 'is not', null));
|
|
1274
|
+
return eb.and(conditions);
|
|
1110
1275
|
//
|
|
1111
1276
|
}
|
|
1112
1277
|
else if (b === '!=' && c !== null) {
|
|
@@ -1147,6 +1312,95 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1147
1312
|
return eb.and([eb(a, 'not in', compactedC), isNotNullStatement]);
|
|
1148
1313
|
return isNotNullStatement;
|
|
1149
1314
|
}
|
|
1315
|
+
/**
|
|
1316
|
+
* @internal
|
|
1317
|
+
*
|
|
1318
|
+
* A Dream instance (or array of Dream instances) as a where-clause value is
|
|
1319
|
+
* resolved as an association of the dreamClass whose statement is being
|
|
1320
|
+
* compiled, so a key namespaced to a different table (e.g.
|
|
1321
|
+
* `where({ 'c.user': user })` after `innerJoin('composition as c')`) would
|
|
1322
|
+
* silently resolve the association against the wrong class, emitting invalid
|
|
1323
|
+
* SQL or SQL that filters the wrong table. Such keys are already rejected at
|
|
1324
|
+
* the type level; this guard rejects them at runtime. Un-namespaced keys and
|
|
1325
|
+
* keys namespaced to the alias the statement applies to are unaffected, as
|
|
1326
|
+
* are callers that do not declare an expected alias.
|
|
1327
|
+
*/
|
|
1328
|
+
validateAssociationFilterAlias(attr, val, expectedAlias) {
|
|
1329
|
+
if (expectedAlias === undefined)
|
|
1330
|
+
return;
|
|
1331
|
+
const isolatedColumn = maybeNamespacedColumnNameToColumnName(attr);
|
|
1332
|
+
if (isolatedColumn === attr)
|
|
1333
|
+
return;
|
|
1334
|
+
const namespace = attr.slice(0, attr.length - isolatedColumn.length - 1);
|
|
1335
|
+
if (namespace === expectedAlias)
|
|
1336
|
+
return;
|
|
1337
|
+
if (val instanceof Dream ||
|
|
1338
|
+
(Array.isArray(val) && val.length > 0 && val.every(element => element instanceof Dream)))
|
|
1339
|
+
throw new CannotNamespaceAssociationFilterToAnotherTable(this.dreamClass, attr, expectedAlias);
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* @internal
|
|
1343
|
+
*
|
|
1344
|
+
* An array under an association name can only be an array of Dream instances
|
|
1345
|
+
* (association names are not columns). The every-element check simply avoids
|
|
1346
|
+
* changing the handling of invalid runtime input (e.g. an array of ids under
|
|
1347
|
+
* an association name), which the type system already rejects.
|
|
1348
|
+
*/
|
|
1349
|
+
isAssociationInstanceArray(dreamClass, attr, val) {
|
|
1350
|
+
return (Array.isArray(val) &&
|
|
1351
|
+
dreamClass.associationNames.includes(maybeNamespacedColumnNameToColumnName(attr)) &&
|
|
1352
|
+
val.every(element => element instanceof Dream));
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* @internal
|
|
1356
|
+
*
|
|
1357
|
+
* Expands an array of Dream instances under a BelongsTo association name into
|
|
1358
|
+
* foreign key filtering:
|
|
1359
|
+
* - non-polymorphic: `foreignKey IN (...)`
|
|
1360
|
+
* - polymorphic: instances are grouped by their reference type (STI children
|
|
1361
|
+
* collapse into their base class), each group becoming
|
|
1362
|
+
* `(foreignKey IN (...) AND foreignKeyTypeField = 'TheType')`, with multiple
|
|
1363
|
+
* groups ORed together
|
|
1364
|
+
* - empty array: matches nothing (FALSE); when the caller negates, NOT(FALSE)
|
|
1365
|
+
* matches everything, mirroring empty `in`/`not in` array semantics
|
|
1366
|
+
*
|
|
1367
|
+
* When `negate` is set, IS NOT NULL conditions are included so that the
|
|
1368
|
+
* caller's negation also matches records in which the foreign key (or type
|
|
1369
|
+
* field) is null, consistent with negation of single Dream instances and of
|
|
1370
|
+
* `in` arrays.
|
|
1371
|
+
*/
|
|
1372
|
+
associationInstanceArrayToExpressionWrapper(dreamClass, eb, attr, instances, { negate }) {
|
|
1373
|
+
if (instances.length === 0)
|
|
1374
|
+
return sql `FALSE`;
|
|
1375
|
+
const isolatedColumn = maybeNamespacedColumnNameToColumnName(attr);
|
|
1376
|
+
// preserve any table alias prefix from the attribute (e.g. `posts.user` -> `posts.`)
|
|
1377
|
+
// on the columns we compare against
|
|
1378
|
+
const namespacePrefix = attr.slice(0, attr.length - isolatedColumn.length);
|
|
1379
|
+
const association = dreamClass['associationMetadataMap']()[isolatedColumn];
|
|
1380
|
+
const foreignKeyColumn = `${namespacePrefix}${association.foreignKey()}`;
|
|
1381
|
+
const primaryKeyValuesFor = (instances) => instances.map(instance => {
|
|
1382
|
+
const primaryKeyValue = association.primaryKeyValue(instance);
|
|
1383
|
+
if (primaryKeyValue === undefined)
|
|
1384
|
+
throw new CannotPassUndefinedAsAValueToAWhereClause(this.dreamClass, foreignKeyColumn);
|
|
1385
|
+
return primaryKeyValue;
|
|
1386
|
+
});
|
|
1387
|
+
if (!association.polymorphic) {
|
|
1388
|
+
const inClause = eb(foreignKeyColumn, 'in', primaryKeyValuesFor(instances));
|
|
1389
|
+
return negate ? eb.and([inClause, eb(foreignKeyColumn, 'is not', null)]) : inClause;
|
|
1390
|
+
}
|
|
1391
|
+
const typeColumn = `${namespacePrefix}${association.foreignKeyTypeField()}`;
|
|
1392
|
+
const instancesByType = groupBy(instances, instance => instance.referenceTypeString);
|
|
1393
|
+
const typeClauses = Object.keys(instancesByType).map(referenceTypeString => {
|
|
1394
|
+
const conditions = [
|
|
1395
|
+
eb(foreignKeyColumn, 'in', primaryKeyValuesFor(instancesByType[referenceTypeString])),
|
|
1396
|
+
eb(typeColumn, '=', referenceTypeString),
|
|
1397
|
+
];
|
|
1398
|
+
if (negate)
|
|
1399
|
+
conditions.push(eb(foreignKeyColumn, 'is not', null), eb(typeColumn, 'is not', null));
|
|
1400
|
+
return eb.and(conditions);
|
|
1401
|
+
});
|
|
1402
|
+
return eb.or(typeClauses);
|
|
1403
|
+
}
|
|
1150
1404
|
dreamWhereStatementToExpressionBuilderParts(dreamClass, attr, val) {
|
|
1151
1405
|
let a;
|
|
1152
1406
|
let b;
|
|
@@ -1260,7 +1514,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1260
1514
|
explicitAlias: alias,
|
|
1261
1515
|
joinAndStatement,
|
|
1262
1516
|
joinType,
|
|
1263
|
-
|
|
1517
|
+
previousThroughAssociations: [],
|
|
1264
1518
|
dreamClassThroughAssociationWantsToHydrate: undefined,
|
|
1265
1519
|
});
|
|
1266
1520
|
query = results.query;
|
|
@@ -1563,20 +1817,35 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1563
1817
|
* public b
|
|
1564
1818
|
* ```
|
|
1565
1819
|
*
|
|
1820
|
+
* Options declared on a through association (`and`/`andAny`/`andNot`/`selfAnd`/
|
|
1821
|
+
* `selfAndNot`/`order`/`distinct`) are applied at the join of the table where the
|
|
1822
|
+
* through association's target model materializes. When a source is itself a
|
|
1823
|
+
* through association, that join is only reached after bridging further through
|
|
1824
|
+
* associations, so each through association is pushed onto the
|
|
1825
|
+
* `previousThroughAssociations` stack (along with the alias `selfAnd`/`selfAndNot`
|
|
1826
|
+
* clauses reference) before the terminal `applyOneJoin` call, and the stack is
|
|
1827
|
+
* threaded through every recursion until `addAssociationJoinStatementToQuery`
|
|
1828
|
+
* reaches a concrete (non-through) association, where every stacked entry is
|
|
1829
|
+
* applied to that join.
|
|
1830
|
+
*
|
|
1566
1831
|
* Then `MyModel.leftJoinPreload('myB')` is processed as follows:
|
|
1567
1832
|
* - `applyOneJoin` is called with the `myB` association
|
|
1568
1833
|
* - `joinsBridgeThroughAssociations` is called with the `myB` association
|
|
1569
1834
|
* - `joinsBridgeThroughAssociations` is called with the `myA` association
|
|
1570
1835
|
* - `addAssociationJoinStatementToQuery` is called with the `otherModel` association
|
|
1571
|
-
* - `applyOneJoin` is called with the `a` association from OtherModel
|
|
1572
|
-
*
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1836
|
+
* - `applyOneJoin` is called with the `a` association from OtherModel, with `myA` pushed
|
|
1837
|
+
* onto the previousThroughAssociations stack
|
|
1838
|
+
* - `joinsBridgeThroughAssociations` is called with the `a` association from OtherModel,
|
|
1839
|
+
* inheriting the stack
|
|
1575
1840
|
* - `addAssociationJoinStatementToQuery` is called with the `aToOtherModelJoinModel` association
|
|
1576
|
-
* - `applyOneJoin` is called with the `a` association from AToOtherModelJoinModel with
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1841
|
+
* - `applyOneJoin` is called with the `a` association from AToOtherModelJoinModel, with the
|
|
1842
|
+
* `a` association from OtherModel pushed onto the stack
|
|
1843
|
+
* - `addAssociationJoinStatementToQuery` is called with the `a` association from
|
|
1844
|
+
* AToOtherModelJoinModel, applying the options (if present) of every stacked through
|
|
1845
|
+
* association (`myA` defined on MyModel, `a` defined on OtherModel) to the `through_as` join
|
|
1846
|
+
* - `applyOneJoin` is called with the `b` association from A, with `myB` pushed onto the stack
|
|
1847
|
+
* - `addAssociationJoinStatementToQuery` is called with the `b` association from A, applying
|
|
1848
|
+
* the options (if present) of `myB` defined on MyModel to the `through_bs` join
|
|
1580
1849
|
*/
|
|
1581
1850
|
joinsBridgeThroughAssociations({ query, dreamClassTheAssociationIsDefinedOn, throughAssociation,
|
|
1582
1851
|
/**
|
|
@@ -1598,7 +1867,13 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1598
1867
|
/**
|
|
1599
1868
|
* previousTableAlias is always set
|
|
1600
1869
|
*/
|
|
1601
|
-
previousTableAlias, joinAndStatement,
|
|
1870
|
+
previousTableAlias, joinAndStatement,
|
|
1871
|
+
/**
|
|
1872
|
+
* Through associations from further out in the association chain whose
|
|
1873
|
+
* options are waiting to be applied at the terminal concrete join of this
|
|
1874
|
+
* chain (see the doc comment above this method).
|
|
1875
|
+
*/
|
|
1876
|
+
previousThroughAssociations, joinType, }) {
|
|
1602
1877
|
/**
|
|
1603
1878
|
* `through` associations always point to other associations on the same model
|
|
1604
1879
|
* they are defined on. So when we want to find an association referenced by a
|
|
@@ -1632,6 +1907,14 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1632
1907
|
explicitAlias: undefined,
|
|
1633
1908
|
previousTableAlias,
|
|
1634
1909
|
joinAndStatement: {},
|
|
1910
|
+
/**
|
|
1911
|
+
* The through associations pending in previousThroughAssociations are
|
|
1912
|
+
* applied at the terminal join of _this_ association's chain (their
|
|
1913
|
+
* target model is this through association's target model), not at the
|
|
1914
|
+
* terminal join of the nested through association being bridged here,
|
|
1915
|
+
* so the nested chain starts with an empty stack.
|
|
1916
|
+
*/
|
|
1917
|
+
previousThroughAssociations: [],
|
|
1635
1918
|
joinType,
|
|
1636
1919
|
});
|
|
1637
1920
|
//
|
|
@@ -1658,7 +1941,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1658
1941
|
* The joinAndStatement is reserved for the final association, not intermediary join tables
|
|
1659
1942
|
*/
|
|
1660
1943
|
joinAndStatement: {},
|
|
1661
|
-
|
|
1944
|
+
previousThroughAssociations: [],
|
|
1662
1945
|
joinType,
|
|
1663
1946
|
dreamClassThroughAssociationWantsToHydrate: undefined,
|
|
1664
1947
|
});
|
|
@@ -1698,15 +1981,22 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1698
1981
|
// recursively and therefore may have added other through associations and their sources prior to reaching this point)
|
|
1699
1982
|
explicitAlias,
|
|
1700
1983
|
previousTableAlias: recursiveResult.association.as,
|
|
1701
|
-
selfTableAlias:
|
|
1702
|
-
? previousTableAlias
|
|
1703
|
-
: recursiveResult.association.as,
|
|
1984
|
+
selfTableAlias: recursiveResult.association.as,
|
|
1704
1985
|
// since joinsBridgeThroughAssociations passes {} to joinAndStatement recursively, we know this is only set on the
|
|
1705
1986
|
// first call to joinsBridgeThroughAssociations, which corresponds to the outermoset through association and therefore
|
|
1706
1987
|
// the last source association to be added to the statement (because joinsBridgeThroughAssociations was called
|
|
1707
1988
|
// recursively and therefore may have added other through associations and their sources prior to reaching this point)
|
|
1708
1989
|
joinAndStatement,
|
|
1709
|
-
|
|
1990
|
+
previousThroughAssociations: [
|
|
1991
|
+
...previousThroughAssociations,
|
|
1992
|
+
/**
|
|
1993
|
+
* This through association's options are applied at the terminal
|
|
1994
|
+
* concrete join of this chain. `selfAnd`/`selfAndNot` clauses on a
|
|
1995
|
+
* through association reference the model the association is defined
|
|
1996
|
+
* on, whose table is aliased with this method's previousTableAlias.
|
|
1997
|
+
*/
|
|
1998
|
+
{ association: throughAssociation, selfTableAlias: previousTableAlias },
|
|
1999
|
+
],
|
|
1710
2000
|
joinType,
|
|
1711
2001
|
dreamClassThroughAssociationWantsToHydrate,
|
|
1712
2002
|
});
|
|
@@ -1731,14 +2021,8 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1731
2021
|
/**
|
|
1732
2022
|
* previousTableAlias is always set
|
|
1733
2023
|
*/
|
|
1734
|
-
previousTableAlias, selfTableAlias = previousTableAlias, joinAndStatement = {},
|
|
2024
|
+
previousTableAlias, selfTableAlias = previousTableAlias, joinAndStatement = {}, previousThroughAssociations, joinType, dreamClassThroughAssociationWantsToHydrate, }) {
|
|
1735
2025
|
if (association.type !== 'BelongsTo' && association.through) {
|
|
1736
|
-
if (throughAssociationHasOptionsBesidesThroughAndSource(previousThroughAssociation)) {
|
|
1737
|
-
throw new ThroughAssociationConditionsIncompatibleWithThroughAssociationSource({
|
|
1738
|
-
dreamClass,
|
|
1739
|
-
association,
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
2026
|
return this.joinsBridgeThroughAssociations({
|
|
1743
2027
|
query,
|
|
1744
2028
|
dreamClassTheAssociationIsDefinedOn: dreamClass,
|
|
@@ -1746,6 +2030,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1746
2030
|
explicitAlias,
|
|
1747
2031
|
previousTableAlias,
|
|
1748
2032
|
joinAndStatement,
|
|
2033
|
+
previousThroughAssociations,
|
|
1749
2034
|
joinType,
|
|
1750
2035
|
});
|
|
1751
2036
|
}
|
|
@@ -1757,7 +2042,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1757
2042
|
previousTableAlias,
|
|
1758
2043
|
selfTableAlias,
|
|
1759
2044
|
joinAndStatement,
|
|
1760
|
-
|
|
2045
|
+
previousThroughAssociations,
|
|
1761
2046
|
joinType,
|
|
1762
2047
|
dreamClassThroughAssociationWantsToHydrate,
|
|
1763
2048
|
});
|
|
@@ -1782,24 +2067,32 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1782
2067
|
/**
|
|
1783
2068
|
* previousTableAlias is always set
|
|
1784
2069
|
*/
|
|
1785
|
-
previousTableAlias, selfTableAlias, joinAndStatement,
|
|
2070
|
+
previousTableAlias, selfTableAlias, joinAndStatement, previousThroughAssociations, joinType, dreamClassThroughAssociationWantsToHydrate, }) {
|
|
1786
2071
|
const currentTableAlias = explicitAlias ?? association.as;
|
|
1787
2072
|
const _associatedDreamClass = association.modelCB();
|
|
1788
2073
|
const associatedDreamClass = Array.isArray(_associatedDreamClass)
|
|
1789
2074
|
? _associatedDreamClass[0]
|
|
1790
2075
|
: _associatedDreamClass;
|
|
1791
|
-
|
|
1792
|
-
|
|
2076
|
+
/**
|
|
2077
|
+
* Stacked order/distinct clauses are applied in join order: the options of
|
|
2078
|
+
* the through association whose bridging joins were added to the query
|
|
2079
|
+
* first are applied first, and the terminal association's own
|
|
2080
|
+
* order/distinct (applied after the join, below) come last.
|
|
2081
|
+
*/
|
|
2082
|
+
for (const { association: throughAssociation } of previousThroughAssociations) {
|
|
2083
|
+
if (throughAssociation.type !== 'HasMany')
|
|
2084
|
+
continue;
|
|
2085
|
+
if (query?.distinctOn && throughAssociation.distinct) {
|
|
1793
2086
|
query = query.distinctOn(this.distinctColumnNameForAssociation({
|
|
1794
|
-
association:
|
|
2087
|
+
association: throughAssociation,
|
|
1795
2088
|
tableNameOrAlias: currentTableAlias,
|
|
1796
|
-
foreignKey:
|
|
2089
|
+
foreignKey: throughAssociation.primaryKey(),
|
|
1797
2090
|
}));
|
|
1798
2091
|
}
|
|
1799
|
-
if (
|
|
2092
|
+
if (throughAssociation.order) {
|
|
1800
2093
|
query = this.applyOrderStatementForAssociation({
|
|
1801
2094
|
query,
|
|
1802
|
-
association:
|
|
2095
|
+
association: throughAssociation,
|
|
1803
2096
|
tableNameOrAlias: currentTableAlias,
|
|
1804
2097
|
});
|
|
1805
2098
|
}
|
|
@@ -1818,21 +2111,18 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1818
2111
|
join = join.onRef(this.namespaceColumn(association.foreignKey(), previousTableAlias), '=', this.namespaceColumn(association.primaryKey(undefined, {
|
|
1819
2112
|
associatedClassOverride: dreamClassThroughAssociationWantsToHydrate,
|
|
1820
2113
|
}), currentTableAlias));
|
|
1821
|
-
if (
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
}, previousThroughAssociation.through)));
|
|
1826
|
-
}
|
|
1827
|
-
join = this.applyAssociationAndStatementsToJoinStatement({
|
|
1828
|
-
dreamClass,
|
|
1829
|
-
join,
|
|
1830
|
-
association: previousThroughAssociation,
|
|
1831
|
-
currentTableAlias,
|
|
1832
|
-
selfTableAlias,
|
|
1833
|
-
joinAndStatement,
|
|
1834
|
-
});
|
|
2114
|
+
if (dreamClassThroughAssociationWantsToHydrate) {
|
|
2115
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement({
|
|
2116
|
+
[association.foreignKeyTypeField()]: dreamClassThroughAssociationWantsToHydrate.sanitizedName,
|
|
2117
|
+
}, previousTableAlias)));
|
|
1835
2118
|
}
|
|
2119
|
+
join = this.applyPreviousThroughAssociationAndStatementsToJoinStatement({
|
|
2120
|
+
dreamClass,
|
|
2121
|
+
join,
|
|
2122
|
+
previousThroughAssociations,
|
|
2123
|
+
currentTableAlias,
|
|
2124
|
+
joinAndStatement,
|
|
2125
|
+
});
|
|
1836
2126
|
join = this.conditionallyApplyDefaultScopesDependentOnAssociation({
|
|
1837
2127
|
dreamClass,
|
|
1838
2128
|
join,
|
|
@@ -1856,16 +2146,13 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1856
2146
|
: dreamClass.referenceTypeString,
|
|
1857
2147
|
}, currentTableAlias)));
|
|
1858
2148
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
joinAndStatement,
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
2149
|
+
join = this.applyPreviousThroughAssociationAndStatementsToJoinStatement({
|
|
2150
|
+
dreamClass,
|
|
2151
|
+
join,
|
|
2152
|
+
previousThroughAssociations,
|
|
2153
|
+
currentTableAlias,
|
|
2154
|
+
joinAndStatement,
|
|
2155
|
+
});
|
|
1869
2156
|
join = this.applyAssociationAndStatementsToJoinStatement({
|
|
1870
2157
|
dreamClass,
|
|
1871
2158
|
join,
|
|
@@ -1929,27 +2216,52 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1929
2216
|
return this.namespaceColumn(foreignKey, tableNameOrAlias);
|
|
1930
2217
|
return this.namespaceColumn(association.distinct, tableNameOrAlias);
|
|
1931
2218
|
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Applies the and-family clauses (`and`/`andAny`/`andNot`/`selfAnd`/`selfAndNot`)
|
|
2221
|
+
* of every pending through association to the terminal concrete join of a
|
|
2222
|
+
* through association chain.
|
|
2223
|
+
*
|
|
2224
|
+
* The first entry in the stack is the association the developer named in the
|
|
2225
|
+
* join/preload/associationQuery statement, so it is the association the
|
|
2226
|
+
* developer-supplied joinAndStatement corresponds to (used to satisfy
|
|
2227
|
+
* `DreamConst.required` clauses); `DreamConst.required` on any other stacked
|
|
2228
|
+
* through association cannot be satisfied and will throw
|
|
2229
|
+
* `MissingRequiredAssociationAndClause`.
|
|
2230
|
+
*/
|
|
2231
|
+
applyPreviousThroughAssociationAndStatementsToJoinStatement({ dreamClass, join, previousThroughAssociations, currentTableAlias, joinAndStatement, }) {
|
|
2232
|
+
previousThroughAssociations.forEach(({ association, selfTableAlias }, index) => {
|
|
2233
|
+
join = this.applyAssociationAndStatementsToJoinStatement({
|
|
2234
|
+
dreamClass,
|
|
2235
|
+
join,
|
|
2236
|
+
association,
|
|
2237
|
+
currentTableAlias,
|
|
2238
|
+
selfTableAlias,
|
|
2239
|
+
joinAndStatement: index === 0 ? joinAndStatement : {},
|
|
2240
|
+
});
|
|
2241
|
+
});
|
|
2242
|
+
return join;
|
|
2243
|
+
}
|
|
1932
2244
|
applyAssociationAndStatementsToJoinStatement({ dreamClass, join, currentTableAlias, selfTableAlias, association, joinAndStatement, }) {
|
|
1933
2245
|
if (association.and) {
|
|
1934
2246
|
this.throwUnlessAllRequiredWhereClausesProvided(association, currentTableAlias, joinAndStatement);
|
|
1935
|
-
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(association.and, currentTableAlias), { disallowSimilarityOperator: false }));
|
|
2247
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(association.and, currentTableAlias), { disallowSimilarityOperator: false, expectedAlias: currentTableAlias }));
|
|
1936
2248
|
}
|
|
1937
2249
|
if (association.andNot) {
|
|
1938
|
-
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(association.andNot, currentTableAlias), { negate: true }));
|
|
2250
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(association.andNot, currentTableAlias), { negate: true, expectedAlias: currentTableAlias }));
|
|
1939
2251
|
}
|
|
1940
2252
|
if (association.andAny) {
|
|
1941
|
-
join = join.on((eb) => eb.or(association.andAny.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, currentTableAlias), { disallowSimilarityOperator: false }))));
|
|
2253
|
+
join = join.on((eb) => eb.or(association.andAny.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, currentTableAlias), { disallowSimilarityOperator: false, expectedAlias: currentTableAlias }))));
|
|
1942
2254
|
}
|
|
1943
2255
|
if (association.selfAnd) {
|
|
1944
2256
|
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.rawifiedSelfOnClause({
|
|
1945
|
-
associationAlias:
|
|
2257
|
+
associationAlias: currentTableAlias,
|
|
1946
2258
|
selfAlias: selfTableAlias,
|
|
1947
2259
|
selfAndClause: association.selfAnd,
|
|
1948
2260
|
})));
|
|
1949
2261
|
}
|
|
1950
2262
|
if (association.selfAndNot) {
|
|
1951
2263
|
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.rawifiedSelfOnClause({
|
|
1952
|
-
associationAlias:
|
|
2264
|
+
associationAlias: currentTableAlias,
|
|
1953
2265
|
selfAlias: selfTableAlias,
|
|
1954
2266
|
selfAndClause: association.selfAndNot,
|
|
1955
2267
|
}), { negate: true }));
|
|
@@ -1997,9 +2309,9 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1997
2309
|
// branches below, such default scopes are silently dropped on association loads, leaking
|
|
1998
2310
|
// rows the app intended to hide.
|
|
1999
2311
|
join = join.on((eb) => eb.and([
|
|
2000
|
-
...scopesQuery['whereStatements'].map(whereStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereStatement, tableNameOrAlias), { disallowSimilarityOperator: false })),
|
|
2001
|
-
...scopesQuery['whereNotStatements'].map(whereNotStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereNotStatement, tableNameOrAlias), { negate: true })),
|
|
2002
|
-
...scopesQuery['whereAnyStatements'].map(whereAnyStatements => eb.or(whereAnyStatements.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, tableNameOrAlias))))),
|
|
2312
|
+
...scopesQuery['whereStatements'].map(whereStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereStatement, tableNameOrAlias), { disallowSimilarityOperator: false, expectedAlias: tableNameOrAlias })),
|
|
2313
|
+
...scopesQuery['whereNotStatements'].map(whereNotStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereNotStatement, tableNameOrAlias), { negate: true, expectedAlias: tableNameOrAlias })),
|
|
2314
|
+
...scopesQuery['whereAnyStatements'].map(whereAnyStatements => eb.or(whereAnyStatements.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, tableNameOrAlias), { expectedAlias: tableNameOrAlias })))),
|
|
2003
2315
|
]));
|
|
2004
2316
|
}
|
|
2005
2317
|
return join;
|
|
@@ -2110,15 +2422,29 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
2110
2422
|
const preloadedPolymorphicBelongsTos = await this.preloadPolymorphicBelongsTo(association, dreamClassToHydrate, dreams);
|
|
2111
2423
|
return preloadedPolymorphicBelongsTos;
|
|
2112
2424
|
}
|
|
2113
|
-
const dreamClassToHydrateColumns =
|
|
2114
|
-
|
|
2115
|
-
|
|
2425
|
+
const dreamClassToHydrateColumns = dreamClassToHydrate.columns();
|
|
2426
|
+
// The base-model primary key rides along under a short alias so each
|
|
2427
|
+
// associated row can be matched back to the dream it belongs to. The
|
|
2428
|
+
// alias is lowercase without underscores so Kysely's CamelCasePlugin
|
|
2429
|
+
// passes it through untouched (the same strategy as `pluck0` /
|
|
2430
|
+
// `groupvalue`), and is extended until it cannot collide with a real
|
|
2431
|
+
// column of the associated table. That collision loop can only consult
|
|
2432
|
+
// the compiled column set, so under add-column skew a live column
|
|
2433
|
+
// literally named like the alias would produce two identically-named
|
|
2434
|
+
// result fields. The aliased base PK is deliberately selected last,
|
|
2435
|
+
// and node-postgres resolves duplicate field names last-wins, so the
|
|
2436
|
+
// base PK still wins even in that pathological case — and the
|
|
2437
|
+
// hydration filter strips the colliding key from the instance's
|
|
2438
|
+
// attributes.
|
|
2439
|
+
let basePrimaryKeyAlias = 'preloadbasepk';
|
|
2440
|
+
while (dreamClassToHydrateColumns.has(basePrimaryKeyAlias))
|
|
2441
|
+
basePrimaryKeyAlias += '0';
|
|
2116
2442
|
const baseClass = dreamClass['stiBaseClassOrOwnClass']['getAssociationMetadata'](associationName)
|
|
2117
2443
|
? dreamClass['stiBaseClassOrOwnClass']
|
|
2118
2444
|
: dreamClass;
|
|
2119
2445
|
const associationDataScope = this.dreamClassQueryWithScopeBypasses(baseClass, {
|
|
2120
2446
|
// In order to stay DRY, preloading leverages the association logic built into
|
|
2121
|
-
// `joins
|
|
2447
|
+
// `joins`. However, baseClass may have
|
|
2122
2448
|
// default scopes that would preclude finding that instance. We remove all
|
|
2123
2449
|
// default scopes on baseClass, but not subsequent associations, so that the
|
|
2124
2450
|
// single query will be able to find each row corresponding to a Dream in `dreams`,
|
|
@@ -2127,16 +2453,29 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
2127
2453
|
}).where({
|
|
2128
2454
|
[dreamClass.primaryKey]: dreams.map(obj => obj.primaryKeyValue()),
|
|
2129
2455
|
});
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2456
|
+
const joinedQuery = associationDataScope['_connection'](this.connectionOverride).innerJoin(associationName, (onStatement || {}));
|
|
2457
|
+
// Select the association's row wholesale (`"alias".*`) rather than
|
|
2458
|
+
// enumerating the compiled column list. Enumerating made every preload
|
|
2459
|
+
// assert the compiled schema against the live database: under
|
|
2460
|
+
// schema/image skew (rolling deploy or rollback around a drop-column
|
|
2461
|
+
// migration), naming a dropped column fails the whole preload with
|
|
2462
|
+
// `42703 column does not exist`. The raw row is passed straight to
|
|
2463
|
+
// hydration, where sqlResultToDreamInstance — the single filtering
|
|
2464
|
+
// point — intersects it with the compiled column list (also stripping
|
|
2465
|
+
// the base-PK alias key), so a column the image doesn't know about
|
|
2466
|
+
// (the add-column direction of skew) never reaches the instance. See
|
|
2467
|
+
// the prepared-statements note on `saveDream` before changing this
|
|
2468
|
+
// back to an enumerated select.
|
|
2469
|
+
const kyselyQuery = new this.constructor(joinedQuery)
|
|
2470
|
+
.buildSelect({ bypassSelectAll: true })
|
|
2471
|
+
.selectAll(alias)
|
|
2472
|
+
.select(`${this.namespaceColumn(dreamClass.primaryKey, dreamClass.table)} as ${basePrimaryKeyAlias}`);
|
|
2473
|
+
const rows = await executeDatabaseQuery(kyselyQuery, 'execute');
|
|
2474
|
+
const preloadedDreamsAndWhatTheyPointTo = rows.map((row) => {
|
|
2475
|
+
const hydratedDream = this.dbResultToDreamInstance(row, dreamClassToHydrate);
|
|
2137
2476
|
return {
|
|
2138
2477
|
dream: hydratedDream,
|
|
2139
|
-
pointsToPrimaryKey:
|
|
2478
|
+
pointsToPrimaryKey: row[basePrimaryKeyAlias],
|
|
2140
2479
|
};
|
|
2141
2480
|
});
|
|
2142
2481
|
this.hydrateAssociation(dreams, association, preloadedDreamsAndWhatTheyPointTo);
|