@rvoh/dream 2.18.1 → 2.19.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 +103 -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 +137 -0
- package/dist/cjs/src/dream/QueryDriver/Base.js +84 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +358 -67
- package/dist/cjs/src/errors/CannotNamespaceAssociationFilterToAnotherTable.js +27 -0
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +21 -13
- package/dist/esm/src/Dream.js +103 -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 +137 -0
- package/dist/esm/src/dream/QueryDriver/Base.js +84 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +358 -67
- package/dist/esm/src/errors/CannotNamespaceAssociationFilterToAnotherTable.js +27 -0
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +21 -13
- package/dist/types/src/Dream.d.ts +97 -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 +127 -0
- package/dist/types/src/dream/QueryDriver/Base.d.ts +69 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +166 -8
- package/dist/types/src/errors/CannotNamespaceAssociationFilterToAnotherTable.d.ts +8 -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 +78 -34
- package/docs/classes/db.PostgresQueryDriver.html +79 -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 +182 -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 +144 -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
|
*
|
|
@@ -999,6 +1127,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
999
1127
|
}, {}), {
|
|
1000
1128
|
negate,
|
|
1001
1129
|
disallowSimilarityOperator,
|
|
1130
|
+
expectedAlias: rootTableOrAssociationAlias,
|
|
1002
1131
|
});
|
|
1003
1132
|
}
|
|
1004
1133
|
buildCommon(kyselyQuery) {
|
|
@@ -1041,20 +1170,25 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1041
1170
|
kyselyQuery = kyselyQuery.where((eb) => eb.and([
|
|
1042
1171
|
...this.aliasWhereStatements(query['whereStatements'], query['baseSqlAlias']).map(whereStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereStatement, {
|
|
1043
1172
|
disallowSimilarityOperator: false,
|
|
1173
|
+
expectedAlias: query['baseSqlAlias'],
|
|
1044
1174
|
})),
|
|
1045
1175
|
...this.aliasWhereStatements(query['whereNotStatements'], query['baseSqlAlias']).map(whereNotStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereNotStatement, {
|
|
1046
1176
|
negate: true,
|
|
1177
|
+
expectedAlias: query['baseSqlAlias'],
|
|
1047
1178
|
})),
|
|
1048
|
-
...query['whereAnyStatements'].map(whereAnyStatements => eb.or(this.aliasWhereStatements(whereAnyStatements, query['baseSqlAlias']).map(whereAnyStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereAnyStatement
|
|
1179
|
+
...query['whereAnyStatements'].map(whereAnyStatements => eb.or(this.aliasWhereStatements(whereAnyStatements, query['baseSqlAlias']).map(whereAnyStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereAnyStatement, {
|
|
1180
|
+
expectedAlias: query['baseSqlAlias'],
|
|
1181
|
+
})))),
|
|
1049
1182
|
]));
|
|
1050
1183
|
}
|
|
1051
1184
|
return kyselyQuery;
|
|
1052
1185
|
}
|
|
1053
|
-
whereStatementToExpressionWrapper(dreamClass, eb, whereStatement, { negate = false, disallowSimilarityOperator = true, } = {}) {
|
|
1186
|
+
whereStatementToExpressionWrapper(dreamClass, eb, whereStatement, { negate = false, disallowSimilarityOperator = true, expectedAlias, } = {}) {
|
|
1054
1187
|
const clauses = compact(Object.keys(whereStatement)
|
|
1055
1188
|
.filter(key => whereStatement[key] !== DreamConst.required)
|
|
1056
1189
|
.map(attr => {
|
|
1057
1190
|
const val = whereStatement[attr];
|
|
1191
|
+
this.validateAssociationFilterAlias(attr, val, expectedAlias);
|
|
1058
1192
|
if (val?.isOpsStatement &&
|
|
1059
1193
|
val.shouldBypassWhereStatement) {
|
|
1060
1194
|
if (disallowSimilarityOperator)
|
|
@@ -1063,6 +1197,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1063
1197
|
// and should be ommited from the where clause directly
|
|
1064
1198
|
return;
|
|
1065
1199
|
}
|
|
1200
|
+
if (this.isAssociationInstanceArray(dreamClass, attr, val))
|
|
1201
|
+
return this.associationInstanceArrayToExpressionWrapper(dreamClass, eb, attr, val, {
|
|
1202
|
+
negate,
|
|
1203
|
+
});
|
|
1066
1204
|
const { a, b, c, a2, b2, c2 } = this.dreamWhereStatementToExpressionBuilderParts(dreamClass, attr, val);
|
|
1067
1205
|
// postgres is unable to handle WHERE IN statements with blank arrays, such as in
|
|
1068
1206
|
// "WHERE id IN ()", meaning that:
|
|
@@ -1106,7 +1244,13 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1106
1244
|
//
|
|
1107
1245
|
}
|
|
1108
1246
|
else if (b === '=' && negate) {
|
|
1109
|
-
|
|
1247
|
+
const conditions = [eb(a, '=', c), eb(a, 'is not', null)];
|
|
1248
|
+
// when a second condition is present (e.g. the type field of a polymorphic
|
|
1249
|
+
// association), it must be included so that the caller negates the full
|
|
1250
|
+
// conjunction rather than the first condition alone
|
|
1251
|
+
if (b2)
|
|
1252
|
+
conditions.push(eb(a2, b2, c2), eb(a2, 'is not', null));
|
|
1253
|
+
return eb.and(conditions);
|
|
1110
1254
|
//
|
|
1111
1255
|
}
|
|
1112
1256
|
else if (b === '!=' && c !== null) {
|
|
@@ -1147,6 +1291,95 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1147
1291
|
return eb.and([eb(a, 'not in', compactedC), isNotNullStatement]);
|
|
1148
1292
|
return isNotNullStatement;
|
|
1149
1293
|
}
|
|
1294
|
+
/**
|
|
1295
|
+
* @internal
|
|
1296
|
+
*
|
|
1297
|
+
* A Dream instance (or array of Dream instances) as a where-clause value is
|
|
1298
|
+
* resolved as an association of the dreamClass whose statement is being
|
|
1299
|
+
* compiled, so a key namespaced to a different table (e.g.
|
|
1300
|
+
* `where({ 'c.user': user })` after `innerJoin('composition as c')`) would
|
|
1301
|
+
* silently resolve the association against the wrong class, emitting invalid
|
|
1302
|
+
* SQL or SQL that filters the wrong table. Such keys are already rejected at
|
|
1303
|
+
* the type level; this guard rejects them at runtime. Un-namespaced keys and
|
|
1304
|
+
* keys namespaced to the alias the statement applies to are unaffected, as
|
|
1305
|
+
* are callers that do not declare an expected alias.
|
|
1306
|
+
*/
|
|
1307
|
+
validateAssociationFilterAlias(attr, val, expectedAlias) {
|
|
1308
|
+
if (expectedAlias === undefined)
|
|
1309
|
+
return;
|
|
1310
|
+
const isolatedColumn = maybeNamespacedColumnNameToColumnName(attr);
|
|
1311
|
+
if (isolatedColumn === attr)
|
|
1312
|
+
return;
|
|
1313
|
+
const namespace = attr.slice(0, attr.length - isolatedColumn.length - 1);
|
|
1314
|
+
if (namespace === expectedAlias)
|
|
1315
|
+
return;
|
|
1316
|
+
if (val instanceof Dream ||
|
|
1317
|
+
(Array.isArray(val) && val.length > 0 && val.every(element => element instanceof Dream)))
|
|
1318
|
+
throw new CannotNamespaceAssociationFilterToAnotherTable(this.dreamClass, attr, expectedAlias);
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* @internal
|
|
1322
|
+
*
|
|
1323
|
+
* An array under an association name can only be an array of Dream instances
|
|
1324
|
+
* (association names are not columns). The every-element check simply avoids
|
|
1325
|
+
* changing the handling of invalid runtime input (e.g. an array of ids under
|
|
1326
|
+
* an association name), which the type system already rejects.
|
|
1327
|
+
*/
|
|
1328
|
+
isAssociationInstanceArray(dreamClass, attr, val) {
|
|
1329
|
+
return (Array.isArray(val) &&
|
|
1330
|
+
dreamClass.associationNames.includes(maybeNamespacedColumnNameToColumnName(attr)) &&
|
|
1331
|
+
val.every(element => element instanceof Dream));
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* @internal
|
|
1335
|
+
*
|
|
1336
|
+
* Expands an array of Dream instances under a BelongsTo association name into
|
|
1337
|
+
* foreign key filtering:
|
|
1338
|
+
* - non-polymorphic: `foreignKey IN (...)`
|
|
1339
|
+
* - polymorphic: instances are grouped by their reference type (STI children
|
|
1340
|
+
* collapse into their base class), each group becoming
|
|
1341
|
+
* `(foreignKey IN (...) AND foreignKeyTypeField = 'TheType')`, with multiple
|
|
1342
|
+
* groups ORed together
|
|
1343
|
+
* - empty array: matches nothing (FALSE); when the caller negates, NOT(FALSE)
|
|
1344
|
+
* matches everything, mirroring empty `in`/`not in` array semantics
|
|
1345
|
+
*
|
|
1346
|
+
* When `negate` is set, IS NOT NULL conditions are included so that the
|
|
1347
|
+
* caller's negation also matches records in which the foreign key (or type
|
|
1348
|
+
* field) is null, consistent with negation of single Dream instances and of
|
|
1349
|
+
* `in` arrays.
|
|
1350
|
+
*/
|
|
1351
|
+
associationInstanceArrayToExpressionWrapper(dreamClass, eb, attr, instances, { negate }) {
|
|
1352
|
+
if (instances.length === 0)
|
|
1353
|
+
return sql `FALSE`;
|
|
1354
|
+
const isolatedColumn = maybeNamespacedColumnNameToColumnName(attr);
|
|
1355
|
+
// preserve any table alias prefix from the attribute (e.g. `posts.user` -> `posts.`)
|
|
1356
|
+
// on the columns we compare against
|
|
1357
|
+
const namespacePrefix = attr.slice(0, attr.length - isolatedColumn.length);
|
|
1358
|
+
const association = dreamClass['associationMetadataMap']()[isolatedColumn];
|
|
1359
|
+
const foreignKeyColumn = `${namespacePrefix}${association.foreignKey()}`;
|
|
1360
|
+
const primaryKeyValuesFor = (instances) => instances.map(instance => {
|
|
1361
|
+
const primaryKeyValue = association.primaryKeyValue(instance);
|
|
1362
|
+
if (primaryKeyValue === undefined)
|
|
1363
|
+
throw new CannotPassUndefinedAsAValueToAWhereClause(this.dreamClass, foreignKeyColumn);
|
|
1364
|
+
return primaryKeyValue;
|
|
1365
|
+
});
|
|
1366
|
+
if (!association.polymorphic) {
|
|
1367
|
+
const inClause = eb(foreignKeyColumn, 'in', primaryKeyValuesFor(instances));
|
|
1368
|
+
return negate ? eb.and([inClause, eb(foreignKeyColumn, 'is not', null)]) : inClause;
|
|
1369
|
+
}
|
|
1370
|
+
const typeColumn = `${namespacePrefix}${association.foreignKeyTypeField()}`;
|
|
1371
|
+
const instancesByType = groupBy(instances, instance => instance.referenceTypeString);
|
|
1372
|
+
const typeClauses = Object.keys(instancesByType).map(referenceTypeString => {
|
|
1373
|
+
const conditions = [
|
|
1374
|
+
eb(foreignKeyColumn, 'in', primaryKeyValuesFor(instancesByType[referenceTypeString])),
|
|
1375
|
+
eb(typeColumn, '=', referenceTypeString),
|
|
1376
|
+
];
|
|
1377
|
+
if (negate)
|
|
1378
|
+
conditions.push(eb(foreignKeyColumn, 'is not', null), eb(typeColumn, 'is not', null));
|
|
1379
|
+
return eb.and(conditions);
|
|
1380
|
+
});
|
|
1381
|
+
return eb.or(typeClauses);
|
|
1382
|
+
}
|
|
1150
1383
|
dreamWhereStatementToExpressionBuilderParts(dreamClass, attr, val) {
|
|
1151
1384
|
let a;
|
|
1152
1385
|
let b;
|
|
@@ -1260,7 +1493,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1260
1493
|
explicitAlias: alias,
|
|
1261
1494
|
joinAndStatement,
|
|
1262
1495
|
joinType,
|
|
1263
|
-
|
|
1496
|
+
previousThroughAssociations: [],
|
|
1264
1497
|
dreamClassThroughAssociationWantsToHydrate: undefined,
|
|
1265
1498
|
});
|
|
1266
1499
|
query = results.query;
|
|
@@ -1563,20 +1796,35 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1563
1796
|
* public b
|
|
1564
1797
|
* ```
|
|
1565
1798
|
*
|
|
1799
|
+
* Options declared on a through association (`and`/`andAny`/`andNot`/`selfAnd`/
|
|
1800
|
+
* `selfAndNot`/`order`/`distinct`) are applied at the join of the table where the
|
|
1801
|
+
* through association's target model materializes. When a source is itself a
|
|
1802
|
+
* through association, that join is only reached after bridging further through
|
|
1803
|
+
* associations, so each through association is pushed onto the
|
|
1804
|
+
* `previousThroughAssociations` stack (along with the alias `selfAnd`/`selfAndNot`
|
|
1805
|
+
* clauses reference) before the terminal `applyOneJoin` call, and the stack is
|
|
1806
|
+
* threaded through every recursion until `addAssociationJoinStatementToQuery`
|
|
1807
|
+
* reaches a concrete (non-through) association, where every stacked entry is
|
|
1808
|
+
* applied to that join.
|
|
1809
|
+
*
|
|
1566
1810
|
* Then `MyModel.leftJoinPreload('myB')` is processed as follows:
|
|
1567
1811
|
* - `applyOneJoin` is called with the `myB` association
|
|
1568
1812
|
* - `joinsBridgeThroughAssociations` is called with the `myB` association
|
|
1569
1813
|
* - `joinsBridgeThroughAssociations` is called with the `myA` association
|
|
1570
1814
|
* - `addAssociationJoinStatementToQuery` is called with the `otherModel` association
|
|
1571
|
-
* - `applyOneJoin` is called with the `a` association from OtherModel
|
|
1572
|
-
*
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1815
|
+
* - `applyOneJoin` is called with the `a` association from OtherModel, with `myA` pushed
|
|
1816
|
+
* onto the previousThroughAssociations stack
|
|
1817
|
+
* - `joinsBridgeThroughAssociations` is called with the `a` association from OtherModel,
|
|
1818
|
+
* inheriting the stack
|
|
1575
1819
|
* - `addAssociationJoinStatementToQuery` is called with the `aToOtherModelJoinModel` association
|
|
1576
|
-
* - `applyOneJoin` is called with the `a` association from AToOtherModelJoinModel with
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1820
|
+
* - `applyOneJoin` is called with the `a` association from AToOtherModelJoinModel, with the
|
|
1821
|
+
* `a` association from OtherModel pushed onto the stack
|
|
1822
|
+
* - `addAssociationJoinStatementToQuery` is called with the `a` association from
|
|
1823
|
+
* AToOtherModelJoinModel, applying the options (if present) of every stacked through
|
|
1824
|
+
* association (`myA` defined on MyModel, `a` defined on OtherModel) to the `through_as` join
|
|
1825
|
+
* - `applyOneJoin` is called with the `b` association from A, with `myB` pushed onto the stack
|
|
1826
|
+
* - `addAssociationJoinStatementToQuery` is called with the `b` association from A, applying
|
|
1827
|
+
* the options (if present) of `myB` defined on MyModel to the `through_bs` join
|
|
1580
1828
|
*/
|
|
1581
1829
|
joinsBridgeThroughAssociations({ query, dreamClassTheAssociationIsDefinedOn, throughAssociation,
|
|
1582
1830
|
/**
|
|
@@ -1598,7 +1846,13 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1598
1846
|
/**
|
|
1599
1847
|
* previousTableAlias is always set
|
|
1600
1848
|
*/
|
|
1601
|
-
previousTableAlias, joinAndStatement,
|
|
1849
|
+
previousTableAlias, joinAndStatement,
|
|
1850
|
+
/**
|
|
1851
|
+
* Through associations from further out in the association chain whose
|
|
1852
|
+
* options are waiting to be applied at the terminal concrete join of this
|
|
1853
|
+
* chain (see the doc comment above this method).
|
|
1854
|
+
*/
|
|
1855
|
+
previousThroughAssociations, joinType, }) {
|
|
1602
1856
|
/**
|
|
1603
1857
|
* `through` associations always point to other associations on the same model
|
|
1604
1858
|
* they are defined on. So when we want to find an association referenced by a
|
|
@@ -1632,6 +1886,14 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1632
1886
|
explicitAlias: undefined,
|
|
1633
1887
|
previousTableAlias,
|
|
1634
1888
|
joinAndStatement: {},
|
|
1889
|
+
/**
|
|
1890
|
+
* The through associations pending in previousThroughAssociations are
|
|
1891
|
+
* applied at the terminal join of _this_ association's chain (their
|
|
1892
|
+
* target model is this through association's target model), not at the
|
|
1893
|
+
* terminal join of the nested through association being bridged here,
|
|
1894
|
+
* so the nested chain starts with an empty stack.
|
|
1895
|
+
*/
|
|
1896
|
+
previousThroughAssociations: [],
|
|
1635
1897
|
joinType,
|
|
1636
1898
|
});
|
|
1637
1899
|
//
|
|
@@ -1658,7 +1920,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1658
1920
|
* The joinAndStatement is reserved for the final association, not intermediary join tables
|
|
1659
1921
|
*/
|
|
1660
1922
|
joinAndStatement: {},
|
|
1661
|
-
|
|
1923
|
+
previousThroughAssociations: [],
|
|
1662
1924
|
joinType,
|
|
1663
1925
|
dreamClassThroughAssociationWantsToHydrate: undefined,
|
|
1664
1926
|
});
|
|
@@ -1698,15 +1960,22 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1698
1960
|
// recursively and therefore may have added other through associations and their sources prior to reaching this point)
|
|
1699
1961
|
explicitAlias,
|
|
1700
1962
|
previousTableAlias: recursiveResult.association.as,
|
|
1701
|
-
selfTableAlias:
|
|
1702
|
-
? previousTableAlias
|
|
1703
|
-
: recursiveResult.association.as,
|
|
1963
|
+
selfTableAlias: recursiveResult.association.as,
|
|
1704
1964
|
// since joinsBridgeThroughAssociations passes {} to joinAndStatement recursively, we know this is only set on the
|
|
1705
1965
|
// first call to joinsBridgeThroughAssociations, which corresponds to the outermoset through association and therefore
|
|
1706
1966
|
// the last source association to be added to the statement (because joinsBridgeThroughAssociations was called
|
|
1707
1967
|
// recursively and therefore may have added other through associations and their sources prior to reaching this point)
|
|
1708
1968
|
joinAndStatement,
|
|
1709
|
-
|
|
1969
|
+
previousThroughAssociations: [
|
|
1970
|
+
...previousThroughAssociations,
|
|
1971
|
+
/**
|
|
1972
|
+
* This through association's options are applied at the terminal
|
|
1973
|
+
* concrete join of this chain. `selfAnd`/`selfAndNot` clauses on a
|
|
1974
|
+
* through association reference the model the association is defined
|
|
1975
|
+
* on, whose table is aliased with this method's previousTableAlias.
|
|
1976
|
+
*/
|
|
1977
|
+
{ association: throughAssociation, selfTableAlias: previousTableAlias },
|
|
1978
|
+
],
|
|
1710
1979
|
joinType,
|
|
1711
1980
|
dreamClassThroughAssociationWantsToHydrate,
|
|
1712
1981
|
});
|
|
@@ -1731,14 +2000,8 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1731
2000
|
/**
|
|
1732
2001
|
* previousTableAlias is always set
|
|
1733
2002
|
*/
|
|
1734
|
-
previousTableAlias, selfTableAlias = previousTableAlias, joinAndStatement = {},
|
|
2003
|
+
previousTableAlias, selfTableAlias = previousTableAlias, joinAndStatement = {}, previousThroughAssociations, joinType, dreamClassThroughAssociationWantsToHydrate, }) {
|
|
1735
2004
|
if (association.type !== 'BelongsTo' && association.through) {
|
|
1736
|
-
if (throughAssociationHasOptionsBesidesThroughAndSource(previousThroughAssociation)) {
|
|
1737
|
-
throw new ThroughAssociationConditionsIncompatibleWithThroughAssociationSource({
|
|
1738
|
-
dreamClass,
|
|
1739
|
-
association,
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
2005
|
return this.joinsBridgeThroughAssociations({
|
|
1743
2006
|
query,
|
|
1744
2007
|
dreamClassTheAssociationIsDefinedOn: dreamClass,
|
|
@@ -1746,6 +2009,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1746
2009
|
explicitAlias,
|
|
1747
2010
|
previousTableAlias,
|
|
1748
2011
|
joinAndStatement,
|
|
2012
|
+
previousThroughAssociations,
|
|
1749
2013
|
joinType,
|
|
1750
2014
|
});
|
|
1751
2015
|
}
|
|
@@ -1757,7 +2021,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1757
2021
|
previousTableAlias,
|
|
1758
2022
|
selfTableAlias,
|
|
1759
2023
|
joinAndStatement,
|
|
1760
|
-
|
|
2024
|
+
previousThroughAssociations,
|
|
1761
2025
|
joinType,
|
|
1762
2026
|
dreamClassThroughAssociationWantsToHydrate,
|
|
1763
2027
|
});
|
|
@@ -1782,24 +2046,32 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1782
2046
|
/**
|
|
1783
2047
|
* previousTableAlias is always set
|
|
1784
2048
|
*/
|
|
1785
|
-
previousTableAlias, selfTableAlias, joinAndStatement,
|
|
2049
|
+
previousTableAlias, selfTableAlias, joinAndStatement, previousThroughAssociations, joinType, dreamClassThroughAssociationWantsToHydrate, }) {
|
|
1786
2050
|
const currentTableAlias = explicitAlias ?? association.as;
|
|
1787
2051
|
const _associatedDreamClass = association.modelCB();
|
|
1788
2052
|
const associatedDreamClass = Array.isArray(_associatedDreamClass)
|
|
1789
2053
|
? _associatedDreamClass[0]
|
|
1790
2054
|
: _associatedDreamClass;
|
|
1791
|
-
|
|
1792
|
-
|
|
2055
|
+
/**
|
|
2056
|
+
* Stacked order/distinct clauses are applied in join order: the options of
|
|
2057
|
+
* the through association whose bridging joins were added to the query
|
|
2058
|
+
* first are applied first, and the terminal association's own
|
|
2059
|
+
* order/distinct (applied after the join, below) come last.
|
|
2060
|
+
*/
|
|
2061
|
+
for (const { association: throughAssociation } of previousThroughAssociations) {
|
|
2062
|
+
if (throughAssociation.type !== 'HasMany')
|
|
2063
|
+
continue;
|
|
2064
|
+
if (query?.distinctOn && throughAssociation.distinct) {
|
|
1793
2065
|
query = query.distinctOn(this.distinctColumnNameForAssociation({
|
|
1794
|
-
association:
|
|
2066
|
+
association: throughAssociation,
|
|
1795
2067
|
tableNameOrAlias: currentTableAlias,
|
|
1796
|
-
foreignKey:
|
|
2068
|
+
foreignKey: throughAssociation.primaryKey(),
|
|
1797
2069
|
}));
|
|
1798
2070
|
}
|
|
1799
|
-
if (
|
|
2071
|
+
if (throughAssociation.order) {
|
|
1800
2072
|
query = this.applyOrderStatementForAssociation({
|
|
1801
2073
|
query,
|
|
1802
|
-
association:
|
|
2074
|
+
association: throughAssociation,
|
|
1803
2075
|
tableNameOrAlias: currentTableAlias,
|
|
1804
2076
|
});
|
|
1805
2077
|
}
|
|
@@ -1818,21 +2090,18 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1818
2090
|
join = join.onRef(this.namespaceColumn(association.foreignKey(), previousTableAlias), '=', this.namespaceColumn(association.primaryKey(undefined, {
|
|
1819
2091
|
associatedClassOverride: dreamClassThroughAssociationWantsToHydrate,
|
|
1820
2092
|
}), 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
|
-
});
|
|
2093
|
+
if (dreamClassThroughAssociationWantsToHydrate) {
|
|
2094
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement({
|
|
2095
|
+
[association.foreignKeyTypeField()]: dreamClassThroughAssociationWantsToHydrate.sanitizedName,
|
|
2096
|
+
}, previousTableAlias)));
|
|
1835
2097
|
}
|
|
2098
|
+
join = this.applyPreviousThroughAssociationAndStatementsToJoinStatement({
|
|
2099
|
+
dreamClass,
|
|
2100
|
+
join,
|
|
2101
|
+
previousThroughAssociations,
|
|
2102
|
+
currentTableAlias,
|
|
2103
|
+
joinAndStatement,
|
|
2104
|
+
});
|
|
1836
2105
|
join = this.conditionallyApplyDefaultScopesDependentOnAssociation({
|
|
1837
2106
|
dreamClass,
|
|
1838
2107
|
join,
|
|
@@ -1856,16 +2125,13 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1856
2125
|
: dreamClass.referenceTypeString,
|
|
1857
2126
|
}, currentTableAlias)));
|
|
1858
2127
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
joinAndStatement,
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
2128
|
+
join = this.applyPreviousThroughAssociationAndStatementsToJoinStatement({
|
|
2129
|
+
dreamClass,
|
|
2130
|
+
join,
|
|
2131
|
+
previousThroughAssociations,
|
|
2132
|
+
currentTableAlias,
|
|
2133
|
+
joinAndStatement,
|
|
2134
|
+
});
|
|
1869
2135
|
join = this.applyAssociationAndStatementsToJoinStatement({
|
|
1870
2136
|
dreamClass,
|
|
1871
2137
|
join,
|
|
@@ -1929,27 +2195,52 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1929
2195
|
return this.namespaceColumn(foreignKey, tableNameOrAlias);
|
|
1930
2196
|
return this.namespaceColumn(association.distinct, tableNameOrAlias);
|
|
1931
2197
|
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Applies the and-family clauses (`and`/`andAny`/`andNot`/`selfAnd`/`selfAndNot`)
|
|
2200
|
+
* of every pending through association to the terminal concrete join of a
|
|
2201
|
+
* through association chain.
|
|
2202
|
+
*
|
|
2203
|
+
* The first entry in the stack is the association the developer named in the
|
|
2204
|
+
* join/preload/associationQuery statement, so it is the association the
|
|
2205
|
+
* developer-supplied joinAndStatement corresponds to (used to satisfy
|
|
2206
|
+
* `DreamConst.required` clauses); `DreamConst.required` on any other stacked
|
|
2207
|
+
* through association cannot be satisfied and will throw
|
|
2208
|
+
* `MissingRequiredAssociationAndClause`.
|
|
2209
|
+
*/
|
|
2210
|
+
applyPreviousThroughAssociationAndStatementsToJoinStatement({ dreamClass, join, previousThroughAssociations, currentTableAlias, joinAndStatement, }) {
|
|
2211
|
+
previousThroughAssociations.forEach(({ association, selfTableAlias }, index) => {
|
|
2212
|
+
join = this.applyAssociationAndStatementsToJoinStatement({
|
|
2213
|
+
dreamClass,
|
|
2214
|
+
join,
|
|
2215
|
+
association,
|
|
2216
|
+
currentTableAlias,
|
|
2217
|
+
selfTableAlias,
|
|
2218
|
+
joinAndStatement: index === 0 ? joinAndStatement : {},
|
|
2219
|
+
});
|
|
2220
|
+
});
|
|
2221
|
+
return join;
|
|
2222
|
+
}
|
|
1932
2223
|
applyAssociationAndStatementsToJoinStatement({ dreamClass, join, currentTableAlias, selfTableAlias, association, joinAndStatement, }) {
|
|
1933
2224
|
if (association.and) {
|
|
1934
2225
|
this.throwUnlessAllRequiredWhereClausesProvided(association, currentTableAlias, joinAndStatement);
|
|
1935
|
-
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(association.and, currentTableAlias), { disallowSimilarityOperator: false }));
|
|
2226
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(association.and, currentTableAlias), { disallowSimilarityOperator: false, expectedAlias: currentTableAlias }));
|
|
1936
2227
|
}
|
|
1937
2228
|
if (association.andNot) {
|
|
1938
|
-
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(association.andNot, currentTableAlias), { negate: true }));
|
|
2229
|
+
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(association.andNot, currentTableAlias), { negate: true, expectedAlias: currentTableAlias }));
|
|
1939
2230
|
}
|
|
1940
2231
|
if (association.andAny) {
|
|
1941
|
-
join = join.on((eb) => eb.or(association.andAny.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, currentTableAlias), { disallowSimilarityOperator: false }))));
|
|
2232
|
+
join = join.on((eb) => eb.or(association.andAny.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, currentTableAlias), { disallowSimilarityOperator: false, expectedAlias: currentTableAlias }))));
|
|
1942
2233
|
}
|
|
1943
2234
|
if (association.selfAnd) {
|
|
1944
2235
|
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.rawifiedSelfOnClause({
|
|
1945
|
-
associationAlias:
|
|
2236
|
+
associationAlias: currentTableAlias,
|
|
1946
2237
|
selfAlias: selfTableAlias,
|
|
1947
2238
|
selfAndClause: association.selfAnd,
|
|
1948
2239
|
})));
|
|
1949
2240
|
}
|
|
1950
2241
|
if (association.selfAndNot) {
|
|
1951
2242
|
join = join.on((eb) => this.whereStatementToExpressionWrapper(dreamClass, eb, this.rawifiedSelfOnClause({
|
|
1952
|
-
associationAlias:
|
|
2243
|
+
associationAlias: currentTableAlias,
|
|
1953
2244
|
selfAlias: selfTableAlias,
|
|
1954
2245
|
selfAndClause: association.selfAndNot,
|
|
1955
2246
|
}), { negate: true }));
|
|
@@ -1997,9 +2288,9 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1997
2288
|
// branches below, such default scopes are silently dropped on association loads, leaking
|
|
1998
2289
|
// rows the app intended to hide.
|
|
1999
2290
|
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))))),
|
|
2291
|
+
...scopesQuery['whereStatements'].map(whereStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereStatement, tableNameOrAlias), { disallowSimilarityOperator: false, expectedAlias: tableNameOrAlias })),
|
|
2292
|
+
...scopesQuery['whereNotStatements'].map(whereNotStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereNotStatement, tableNameOrAlias), { negate: true, expectedAlias: tableNameOrAlias })),
|
|
2293
|
+
...scopesQuery['whereAnyStatements'].map(whereAnyStatements => eb.or(whereAnyStatements.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, tableNameOrAlias), { expectedAlias: tableNameOrAlias })))),
|
|
2003
2294
|
]));
|
|
2004
2295
|
}
|
|
2005
2296
|
return join;
|