@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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class CannotNamespaceAssociationFilterToAnotherTable extends Error {
|
|
2
|
+
dreamClass;
|
|
3
|
+
key;
|
|
4
|
+
expectedAlias;
|
|
5
|
+
constructor(dreamClass, key, expectedAlias) {
|
|
6
|
+
super();
|
|
7
|
+
this.dreamClass = dreamClass;
|
|
8
|
+
this.key = key;
|
|
9
|
+
this.expectedAlias = expectedAlias;
|
|
10
|
+
}
|
|
11
|
+
get message() {
|
|
12
|
+
return `
|
|
13
|
+
Cannot filter on an association via a where-clause key namespaced to another table.
|
|
14
|
+
|
|
15
|
+
dream class: ${this.dreamClass.sanitizedName}
|
|
16
|
+
where-clause key: ${this.key}
|
|
17
|
+
table alias this where clause applies to: ${this.expectedAlias}
|
|
18
|
+
|
|
19
|
+
A Dream instance (or array of Dream instances) as a where-clause value resolves
|
|
20
|
+
an association on the model the where clause applies to, so the key may not be
|
|
21
|
+
namespaced to a different table. To filter a joined table by one of its
|
|
22
|
+
associations, pass the filter in the join's on-clause instead, e.g.:
|
|
23
|
+
|
|
24
|
+
.innerJoin('association', { and: { myBelongsToAssociation: instances } })
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -8,8 +8,13 @@ import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFu
|
|
|
8
8
|
import snakeify from '../snakeify.js';
|
|
9
9
|
import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
|
|
10
10
|
const STI_TYPE_COLUMN_NAME = 'type';
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// deleted_at is deliberately NOT in this list: the SoftDelete default scope's
|
|
12
|
+
// `WHERE deleted_at IS NULL` is unselective on healthy tables, Dream internals
|
|
13
|
+
// never issue a query a deleted_at index could serve, and the useful partial
|
|
14
|
+
// index alternatives are dialect-specific SQL the generator must not emit by
|
|
15
|
+
// default. See spec/unit/cli/generateMigrationContent.spec.ts
|
|
16
|
+
// ("deleted_at is deliberately NOT indexed") and the CHANGELOG.
|
|
17
|
+
const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME];
|
|
13
18
|
export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, softDelete = false, } = {}) {
|
|
14
19
|
const altering = createOrAlter === 'alter';
|
|
15
20
|
let requireCitextExtension = false;
|
|
@@ -43,13 +48,18 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
43
48
|
return acc;
|
|
44
49
|
/**
|
|
45
50
|
* Automatically set email columns to citext since different casings of
|
|
46
|
-
* email address are the same email address
|
|
51
|
+
* email address are the same email address. Skip this when the user
|
|
52
|
+
* explicitly asked for an encrypted column, since encrypted columns
|
|
53
|
+
* are always stored as encrypted text and must not be overridden by
|
|
54
|
+
* the name-based heuristic.
|
|
47
55
|
*/
|
|
48
|
-
const attributeType =
|
|
49
|
-
?
|
|
50
|
-
: /
|
|
51
|
-
? '
|
|
52
|
-
:
|
|
56
|
+
const attributeType = _attributeType === 'encrypted' || _attributeType === 'encrypted[]'
|
|
57
|
+
? _attributeType
|
|
58
|
+
: /email$/.test(nonStandardAttributeName)
|
|
59
|
+
? 'citext'
|
|
60
|
+
: /uuid$/.test(nonStandardAttributeName)
|
|
61
|
+
? 'uuid'
|
|
62
|
+
: _attributeType;
|
|
53
63
|
const processedAttrType = camelize(attributeType)?.toLowerCase();
|
|
54
64
|
const userWantsThisOptional = optionalFromDescriptors(descriptors);
|
|
55
65
|
// when creating a migration for an STI child, we don't want to include notNull;
|
|
@@ -115,6 +125,7 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
115
125
|
attributeName = `encrypted_${attributeName}`;
|
|
116
126
|
columnDefs.push(generateColumnStr(attributeName, 'text', descriptors, {
|
|
117
127
|
omitInlineNonNull,
|
|
128
|
+
skipUniqueHeuristic: true,
|
|
118
129
|
}));
|
|
119
130
|
break;
|
|
120
131
|
// TODO: determine if we need to support encrypted[] in the future
|
|
@@ -145,9 +156,6 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
145
156
|
}
|
|
146
157
|
return acc;
|
|
147
158
|
}, { columnDefs: [], columnDrops: [], indexDefs: [] });
|
|
148
|
-
if (emitDeletedAtColumn) {
|
|
149
|
-
indexDefs.push(columnIndexStatement(table, DELETED_AT_COLUMN_NAME));
|
|
150
|
-
}
|
|
151
159
|
if (!table) {
|
|
152
160
|
return `\
|
|
153
161
|
import { Kysely, sql } from 'kysely'
|
|
@@ -274,12 +282,12 @@ function generateDecimalStr(attributeName, { descriptors, omitInlineNonNull: opt
|
|
|
274
282
|
: `'decimal(${scale}, ${precision})'`;
|
|
275
283
|
return `.addColumn('${attributeName}', ${decimalStatement}${optional ? '' : `, col => ${columnModifiers}`})`;
|
|
276
284
|
}
|
|
277
|
-
function generateColumnStr(attributeName, attributeType, descriptors, { omitInlineNonNull: optional }) {
|
|
285
|
+
function generateColumnStr(attributeName, attributeType, descriptors, { omitInlineNonNull: optional, skipUniqueHeuristic = false, }) {
|
|
278
286
|
let returnStr = `.addColumn('${attributeName}', ${attributeTypeString(attributeType)}`;
|
|
279
287
|
const providedDefaultArg = descriptors.find(d => /^default\(/.test(d));
|
|
280
288
|
const providedDefault = providedDefaultArg?.replace(/^default\(/, '')?.replace(/\)$/, '');
|
|
281
289
|
const notNull = !optional;
|
|
282
|
-
const isUnique = /(email|token|uuid)$/.test(attributeName);
|
|
290
|
+
const isUnique = !skipUniqueHeuristic && /(email|token|uuid)$/.test(attributeName);
|
|
283
291
|
const hasExtraValues = providedDefault || notNull || isUnique;
|
|
284
292
|
const isArray = /\[\]$/.test(attributeType);
|
|
285
293
|
const needsJsonDefault = notNull && !isArray && (attributeType === 'jsonb' || attributeType === 'json');
|
package/dist/esm/src/Dream.js
CHANGED
|
@@ -976,6 +976,25 @@ export default class Dream {
|
|
|
976
976
|
static async count() {
|
|
977
977
|
return await this.query().count();
|
|
978
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* Retrieves the number of records in each group, keyed by the
|
|
981
|
+
* value of the provided group column (a SQL `GROUP BY`).
|
|
982
|
+
*
|
|
983
|
+
* ```ts
|
|
984
|
+
* await User.countBy('name')
|
|
985
|
+
* // Map(2) { 'fred' => 2, 'zed' => 1 }
|
|
986
|
+
* ```
|
|
987
|
+
*
|
|
988
|
+
* Only groups with at least one matching row appear in the Map; seed absent
|
|
989
|
+
* groups yourself with `map.get(key) ?? 0`. When the group column is nullable,
|
|
990
|
+
* records with a `null` value are grouped under a real `null` key.
|
|
991
|
+
*
|
|
992
|
+
* @param groupColumn - the column to group by
|
|
993
|
+
* @returns A Map from each present group value to the number of records in that group
|
|
994
|
+
*/
|
|
995
|
+
static async countBy(groupColumn) {
|
|
996
|
+
return await this.query().countBy(groupColumn);
|
|
997
|
+
}
|
|
979
998
|
/**
|
|
980
999
|
* Retrieves the max value of the specified column
|
|
981
1000
|
* for this model's records.
|
|
@@ -991,6 +1010,27 @@ export default class Dream {
|
|
|
991
1010
|
static async max(columnName) {
|
|
992
1011
|
return await this.query().max(columnName);
|
|
993
1012
|
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Retrieves the max value of the specified column within each group, keyed by
|
|
1015
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
1016
|
+
*
|
|
1017
|
+
* ```ts
|
|
1018
|
+
* await CompositionAsset.maxBy('name', 'score')
|
|
1019
|
+
* // Map(2) { 'primary' => 9, 'secondary' => 4 }
|
|
1020
|
+
* ```
|
|
1021
|
+
*
|
|
1022
|
+
* Only groups with at least one matching row appear in the Map. When the group
|
|
1023
|
+
* column is nullable, records with a `null` value are grouped under a real
|
|
1024
|
+
* `null` key; a group whose aggregated values are all `null` yields a `null`
|
|
1025
|
+
* value.
|
|
1026
|
+
*
|
|
1027
|
+
* @param groupColumn - the column to group by
|
|
1028
|
+
* @param aggregatedColumn - the column to take the max of within each group
|
|
1029
|
+
* @returns A Map from each present group value to the max of the aggregated column in that group
|
|
1030
|
+
*/
|
|
1031
|
+
static async maxBy(groupColumn, aggregatedColumn) {
|
|
1032
|
+
return await this.query().maxBy(groupColumn, aggregatedColumn);
|
|
1033
|
+
}
|
|
994
1034
|
/**
|
|
995
1035
|
* Retrieves the min value of the specified column
|
|
996
1036
|
* for this model's records.
|
|
@@ -1007,6 +1047,27 @@ export default class Dream {
|
|
|
1007
1047
|
static async min(columnName) {
|
|
1008
1048
|
return await this.query().min(columnName);
|
|
1009
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Retrieves the min value of the specified column within each group, keyed by
|
|
1052
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
1053
|
+
*
|
|
1054
|
+
* ```ts
|
|
1055
|
+
* await CompositionAsset.minBy('name', 'score')
|
|
1056
|
+
* // Map(2) { 'primary' => 1, 'secondary' => 4 }
|
|
1057
|
+
* ```
|
|
1058
|
+
*
|
|
1059
|
+
* Only groups with at least one matching row appear in the Map. When the group
|
|
1060
|
+
* column is nullable, records with a `null` value are grouped under a real
|
|
1061
|
+
* `null` key; a group whose aggregated values are all `null` yields a `null`
|
|
1062
|
+
* value.
|
|
1063
|
+
*
|
|
1064
|
+
* @param groupColumn - the column to group by
|
|
1065
|
+
* @param aggregatedColumn - the column to take the min of within each group
|
|
1066
|
+
* @returns A Map from each present group value to the min of the aggregated column in that group
|
|
1067
|
+
*/
|
|
1068
|
+
static async minBy(groupColumn, aggregatedColumn) {
|
|
1069
|
+
return await this.query().minBy(groupColumn, aggregatedColumn);
|
|
1070
|
+
}
|
|
1010
1071
|
/**
|
|
1011
1072
|
* Retrieves the sum for all values of the specified column
|
|
1012
1073
|
* for this model's records.
|
|
@@ -1023,6 +1084,27 @@ export default class Dream {
|
|
|
1023
1084
|
static async sum(columnName) {
|
|
1024
1085
|
return await this.query().sum(columnName);
|
|
1025
1086
|
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Retrieves the sum of the specified column within each group, keyed by
|
|
1089
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
1090
|
+
*
|
|
1091
|
+
* ```ts
|
|
1092
|
+
* await CompositionAsset.sumBy('name', 'score')
|
|
1093
|
+
* // Map(2) { 'primary' => 10, 'secondary' => 4 }
|
|
1094
|
+
* ```
|
|
1095
|
+
*
|
|
1096
|
+
* Only groups with at least one matching row appear in the Map. When the group
|
|
1097
|
+
* column is nullable, records with a `null` value are grouped under a real
|
|
1098
|
+
* `null` key; a group whose aggregated values are all `null` yields a `null`
|
|
1099
|
+
* value.
|
|
1100
|
+
*
|
|
1101
|
+
* @param groupColumn - the column to group by
|
|
1102
|
+
* @param aggregatedColumn - the column to sum within each group
|
|
1103
|
+
* @returns A Map from each present group value to the sum of the aggregated column in that group
|
|
1104
|
+
*/
|
|
1105
|
+
static async sumBy(groupColumn, aggregatedColumn) {
|
|
1106
|
+
return await this.query().sumBy(groupColumn, aggregatedColumn);
|
|
1107
|
+
}
|
|
1026
1108
|
/**
|
|
1027
1109
|
* Retrieves the average for all values of the specified column
|
|
1028
1110
|
* for this model's records.
|
|
@@ -1039,6 +1121,27 @@ export default class Dream {
|
|
|
1039
1121
|
static async avg(columnName) {
|
|
1040
1122
|
return await this.query().avg(columnName);
|
|
1041
1123
|
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Retrieves the average of the specified column within each group, keyed by
|
|
1126
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
1127
|
+
*
|
|
1128
|
+
* ```ts
|
|
1129
|
+
* await CompositionAsset.avgBy('name', 'score')
|
|
1130
|
+
* // Map(2) { 'primary' => 5, 'secondary' => 4 }
|
|
1131
|
+
* ```
|
|
1132
|
+
*
|
|
1133
|
+
* Only groups with at least one matching row appear in the Map. When the group
|
|
1134
|
+
* column is nullable, records with a `null` value are grouped under a real
|
|
1135
|
+
* `null` key; a group whose aggregated values are all `null` yields a `null`
|
|
1136
|
+
* value.
|
|
1137
|
+
*
|
|
1138
|
+
* @param groupColumn - the column to group by
|
|
1139
|
+
* @param aggregatedColumn - the column to average within each group
|
|
1140
|
+
* @returns A Map from each present group value to the average of the aggregated column in that group
|
|
1141
|
+
*/
|
|
1142
|
+
static async avgBy(groupColumn, aggregatedColumn) {
|
|
1143
|
+
return await this.query().avgBy(groupColumn, aggregatedColumn);
|
|
1144
|
+
}
|
|
1042
1145
|
/**
|
|
1043
1146
|
* Persists a new record, setting the provided attributes.
|
|
1044
1147
|
* Automatically sets createdAt and updatedAt timestamps.
|
|
@@ -103,6 +103,12 @@ async function destroyConnectionWithinTimeout(conn, label) {
|
|
|
103
103
|
reportLeakedDbConnections(label);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
// teardown is best-effort (the registry entry has already been removed,
|
|
108
|
+
// so a subsequent getConnection() builds a fresh pool), but a rejected
|
|
109
|
+
// destroy() must not vanish silently
|
|
110
|
+
DreamApp.logWithLevel('error', `[dream] failed to close db connection "${label}"`, error);
|
|
111
|
+
}
|
|
106
112
|
finally {
|
|
107
113
|
if (timer)
|
|
108
114
|
clearTimeout(timer);
|
|
@@ -35,6 +35,18 @@ export const SOFT_DELETE_SCOPE_NAME = 'dream:SoftDelete';
|
|
|
35
35
|
* return 'customDatetimeField' as const
|
|
36
36
|
* }
|
|
37
37
|
* }
|
|
38
|
+
*
|
|
39
|
+
* Note on indexing: Dream deliberately does not index `deleted_at`.
|
|
40
|
+
* The default scope's `WHERE deleted_at IS NULL` matches nearly every
|
|
41
|
+
* row on a healthy table, so a plain b-tree on the column is rarely
|
|
42
|
+
* chosen by the planner while still costing index size and write
|
|
43
|
+
* amplification, and Dream itself never issues a query such an index
|
|
44
|
+
* could serve. If your app needs one, add it yourself based on your
|
|
45
|
+
* own access patterns — on Postgres, the two useful shapes are a
|
|
46
|
+
* composite partial index on your hot lookup columns with
|
|
47
|
+
* `WHERE deleted_at IS NULL` (fast scoped reads), or a partial index
|
|
48
|
+
* `WHERE deleted_at IS NOT NULL` (purge/GC sweeps over soft-deleted
|
|
49
|
+
* rows). Both are Postgres-specific syntax.
|
|
38
50
|
*/
|
|
39
51
|
export default function SoftDelete() {
|
|
40
52
|
return function (target) {
|
|
@@ -58,6 +58,27 @@ export default class DreamClassTransactionBuilder {
|
|
|
58
58
|
async count() {
|
|
59
59
|
return this.queryInstance().count();
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Retrieves the number of records in each group, keyed by the
|
|
63
|
+
* value of the provided group column (a SQL `GROUP BY`).
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* await ApplicationModel.transaction(async txn => {
|
|
67
|
+
* await User.txn(txn).countBy('name')
|
|
68
|
+
* // Map(2) { 'fred' => 2, 'zed' => 1 }
|
|
69
|
+
* })
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* Only groups with at least one matching row appear in the Map; seed absent
|
|
73
|
+
* groups yourself with `map.get(key) ?? 0`. When the group column is nullable,
|
|
74
|
+
* records with a `null` value are grouped under a real `null` key.
|
|
75
|
+
*
|
|
76
|
+
* @param groupColumn - the column to group by
|
|
77
|
+
* @returns A Map from each present group value to the number of records in that group
|
|
78
|
+
*/
|
|
79
|
+
async countBy(groupColumn) {
|
|
80
|
+
return this.queryInstance().countBy(groupColumn);
|
|
81
|
+
}
|
|
61
82
|
/**
|
|
62
83
|
* Returns a new Query instance, specifying a limit
|
|
63
84
|
*
|
|
@@ -107,6 +128,29 @@ export default class DreamClassTransactionBuilder {
|
|
|
107
128
|
async max(columnName) {
|
|
108
129
|
return this.queryInstance().max(columnName);
|
|
109
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Retrieves the max value of the specified column within each group, keyed by
|
|
133
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* await ApplicationModel.transaction(async txn => {
|
|
137
|
+
* await CompositionAsset.txn(txn).maxBy('name', 'score')
|
|
138
|
+
* // Map(2) { 'primary' => 9, 'secondary' => 4 }
|
|
139
|
+
* })
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* Only groups with at least one matching row appear in the Map. When the group
|
|
143
|
+
* column is nullable, records with a `null` value are grouped under a real
|
|
144
|
+
* `null` key; a group whose aggregated values are all `null` yields a `null`
|
|
145
|
+
* value.
|
|
146
|
+
*
|
|
147
|
+
* @param groupColumn - the column to group by
|
|
148
|
+
* @param aggregatedColumn - the column to take the max of within each group
|
|
149
|
+
* @returns A Map from each present group value to the max of the aggregated column in that group
|
|
150
|
+
*/
|
|
151
|
+
async maxBy(groupColumn, aggregatedColumn) {
|
|
152
|
+
return this.queryInstance().maxBy(groupColumn, aggregatedColumn);
|
|
153
|
+
}
|
|
110
154
|
/**
|
|
111
155
|
* Retrieves the min value of the specified column
|
|
112
156
|
* for this model's records.
|
|
@@ -124,6 +168,29 @@ export default class DreamClassTransactionBuilder {
|
|
|
124
168
|
async min(columnName) {
|
|
125
169
|
return this.queryInstance().min(columnName);
|
|
126
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Retrieves the min value of the specified column within each group, keyed by
|
|
173
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
174
|
+
*
|
|
175
|
+
* ```ts
|
|
176
|
+
* await ApplicationModel.transaction(async txn => {
|
|
177
|
+
* await CompositionAsset.txn(txn).minBy('name', 'score')
|
|
178
|
+
* // Map(2) { 'primary' => 1, 'secondary' => 4 }
|
|
179
|
+
* })
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* Only groups with at least one matching row appear in the Map. When the group
|
|
183
|
+
* column is nullable, records with a `null` value are grouped under a real
|
|
184
|
+
* `null` key; a group whose aggregated values are all `null` yields a `null`
|
|
185
|
+
* value.
|
|
186
|
+
*
|
|
187
|
+
* @param groupColumn - the column to group by
|
|
188
|
+
* @param aggregatedColumn - the column to take the min of within each group
|
|
189
|
+
* @returns A Map from each present group value to the min of the aggregated column in that group
|
|
190
|
+
*/
|
|
191
|
+
async minBy(groupColumn, aggregatedColumn) {
|
|
192
|
+
return this.queryInstance().minBy(groupColumn, aggregatedColumn);
|
|
193
|
+
}
|
|
127
194
|
/**
|
|
128
195
|
* Retrieves the sum value of the specified column
|
|
129
196
|
* for this Query
|
|
@@ -139,6 +206,29 @@ export default class DreamClassTransactionBuilder {
|
|
|
139
206
|
async sum(columnName) {
|
|
140
207
|
return this.queryInstance().sum(columnName);
|
|
141
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Retrieves the sum of the specified column within each group, keyed by
|
|
211
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
212
|
+
*
|
|
213
|
+
* ```ts
|
|
214
|
+
* await ApplicationModel.transaction(async txn => {
|
|
215
|
+
* await CompositionAsset.txn(txn).sumBy('name', 'score')
|
|
216
|
+
* // Map(2) { 'primary' => 10, 'secondary' => 4 }
|
|
217
|
+
* })
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* Only groups with at least one matching row appear in the Map. When the group
|
|
221
|
+
* column is nullable, records with a `null` value are grouped under a real
|
|
222
|
+
* `null` key; a group whose aggregated values are all `null` yields a `null`
|
|
223
|
+
* value.
|
|
224
|
+
*
|
|
225
|
+
* @param groupColumn - the column to group by
|
|
226
|
+
* @param aggregatedColumn - the column to sum within each group
|
|
227
|
+
* @returns A Map from each present group value to the sum of the aggregated column in that group
|
|
228
|
+
*/
|
|
229
|
+
async sumBy(groupColumn, aggregatedColumn) {
|
|
230
|
+
return this.queryInstance().sumBy(groupColumn, aggregatedColumn);
|
|
231
|
+
}
|
|
142
232
|
/**
|
|
143
233
|
* Retrieves the average value of the specified column
|
|
144
234
|
* for this Query
|
|
@@ -154,6 +244,29 @@ export default class DreamClassTransactionBuilder {
|
|
|
154
244
|
async avg(columnName) {
|
|
155
245
|
return this.queryInstance().avg(columnName);
|
|
156
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Retrieves the average of the specified column within each group, keyed by
|
|
249
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
250
|
+
*
|
|
251
|
+
* ```ts
|
|
252
|
+
* await ApplicationModel.transaction(async txn => {
|
|
253
|
+
* await CompositionAsset.txn(txn).avgBy('name', 'score')
|
|
254
|
+
* // Map(2) { 'primary' => 5, 'secondary' => 4 }
|
|
255
|
+
* })
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* Only groups with at least one matching row appear in the Map. When the group
|
|
259
|
+
* column is nullable, records with a `null` value are grouped under a real
|
|
260
|
+
* `null` key; a group whose aggregated values are all `null` yields a `null`
|
|
261
|
+
* value.
|
|
262
|
+
*
|
|
263
|
+
* @param groupColumn - the column to group by
|
|
264
|
+
* @param aggregatedColumn - the column to average within each group
|
|
265
|
+
* @returns A Map from each present group value to the average of the aggregated column in that group
|
|
266
|
+
*/
|
|
267
|
+
async avgBy(groupColumn, aggregatedColumn) {
|
|
268
|
+
return this.queryInstance().avgBy(groupColumn, aggregatedColumn);
|
|
269
|
+
}
|
|
157
270
|
/**
|
|
158
271
|
* Persists a new record, setting the provided attributes.
|
|
159
272
|
* Automatically sets createdAt and updatedAt timestamps.
|
|
@@ -980,6 +980,39 @@ export default class Query {
|
|
|
980
980
|
async count() {
|
|
981
981
|
return await this.dbDriverInstance().count();
|
|
982
982
|
}
|
|
983
|
+
/**
|
|
984
|
+
* Retrieves the number of records in each group, keyed by the
|
|
985
|
+
* value of the provided group column (a SQL `GROUP BY`).
|
|
986
|
+
*
|
|
987
|
+
* ```ts
|
|
988
|
+
* await User.query().countBy('name')
|
|
989
|
+
* // Map(2) { 'fred' => 2, 'zed' => 1 }
|
|
990
|
+
*
|
|
991
|
+
* await User.where({ email: ops.ilike('%gmail.com') }).countBy('name')
|
|
992
|
+
* // Map(1) { 'fred' => 3 }
|
|
993
|
+
* ```
|
|
994
|
+
*
|
|
995
|
+
* Any `where` clauses, base scopes (soft-delete / STI), and joined-association
|
|
996
|
+
* `and` clauses on the Query carry through automatically. A joined-association
|
|
997
|
+
* column may be grouped on by passing its namespaced name (e.g.
|
|
998
|
+
* `'compositionAssets.name'`).
|
|
999
|
+
*
|
|
1000
|
+
* Only groups with at least one matching row appear in the Map (inherent to
|
|
1001
|
+
* `GROUP BY`); seed absent groups yourself with `map.get(key) ?? 0`. When the
|
|
1002
|
+
* group column is nullable, records with a `null` value are grouped under a real
|
|
1003
|
+
* `null` key.
|
|
1004
|
+
*
|
|
1005
|
+
* ```ts
|
|
1006
|
+
* await Composition.query().innerJoin('compositionAssets').countBy('compositionAssets.name')
|
|
1007
|
+
* // Map(2) { 'primary' => 3, null => 1 }
|
|
1008
|
+
* ```
|
|
1009
|
+
*
|
|
1010
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
1011
|
+
* @returns A Map from each present group value to the number of records in that group
|
|
1012
|
+
*/
|
|
1013
|
+
async countBy(groupColumn) {
|
|
1014
|
+
return await this.dbDriverInstance().countBy(groupColumn);
|
|
1015
|
+
}
|
|
983
1016
|
/**
|
|
984
1017
|
* Returns new Query with distinct clause applied.
|
|
985
1018
|
* If no column is specified, applies distinct to the primary key.
|
|
@@ -1038,6 +1071,32 @@ export default class Query {
|
|
|
1038
1071
|
async max(columnName) {
|
|
1039
1072
|
return await this.dbDriverInstance().max(columnName);
|
|
1040
1073
|
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Retrieves the max value of the specified column within each group, keyed by
|
|
1076
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
1077
|
+
*
|
|
1078
|
+
* ```ts
|
|
1079
|
+
* await CompositionAsset.query().maxBy('name', 'score')
|
|
1080
|
+
* // Map(2) { 'primary' => 9, 'secondary' => 4 }
|
|
1081
|
+
* ```
|
|
1082
|
+
*
|
|
1083
|
+
* Any `where` clauses, base scopes (soft-delete / STI), and joined-association
|
|
1084
|
+
* `and` clauses on the Query carry through automatically. A joined-association
|
|
1085
|
+
* column may be grouped on or aggregated by passing its namespaced name (e.g.
|
|
1086
|
+
* `'compositionAssets.name'`).
|
|
1087
|
+
*
|
|
1088
|
+
* Only groups with at least one matching row appear in the Map (inherent to
|
|
1089
|
+
* `GROUP BY`). When the group column is nullable, records with a `null` value
|
|
1090
|
+
* are grouped under a real `null` key; a group whose aggregated values are all
|
|
1091
|
+
* `null` yields a `null` value.
|
|
1092
|
+
*
|
|
1093
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
1094
|
+
* @param aggregatedColumn - the column to take the max of within each group
|
|
1095
|
+
* @returns A Map from each present group value to the max of the aggregated column in that group
|
|
1096
|
+
*/
|
|
1097
|
+
async maxBy(groupColumn, aggregatedColumn) {
|
|
1098
|
+
return await this.dbDriverInstance().maxBy(groupColumn, aggregatedColumn);
|
|
1099
|
+
}
|
|
1041
1100
|
/**
|
|
1042
1101
|
* Retrieves the min value of the specified column
|
|
1043
1102
|
* for this Query
|
|
@@ -1053,6 +1112,32 @@ export default class Query {
|
|
|
1053
1112
|
async min(columnName) {
|
|
1054
1113
|
return await this.dbDriverInstance().min(columnName);
|
|
1055
1114
|
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Retrieves the min value of the specified column within each group, keyed by
|
|
1117
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
1118
|
+
*
|
|
1119
|
+
* ```ts
|
|
1120
|
+
* await CompositionAsset.query().minBy('name', 'score')
|
|
1121
|
+
* // Map(2) { 'primary' => 1, 'secondary' => 4 }
|
|
1122
|
+
* ```
|
|
1123
|
+
*
|
|
1124
|
+
* Any `where` clauses, base scopes (soft-delete / STI), and joined-association
|
|
1125
|
+
* `and` clauses on the Query carry through automatically. A joined-association
|
|
1126
|
+
* column may be grouped on or aggregated by passing its namespaced name (e.g.
|
|
1127
|
+
* `'compositionAssets.name'`).
|
|
1128
|
+
*
|
|
1129
|
+
* Only groups with at least one matching row appear in the Map (inherent to
|
|
1130
|
+
* `GROUP BY`). When the group column is nullable, records with a `null` value
|
|
1131
|
+
* are grouped under a real `null` key; a group whose aggregated values are all
|
|
1132
|
+
* `null` yields a `null` value.
|
|
1133
|
+
*
|
|
1134
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
1135
|
+
* @param aggregatedColumn - the column to take the min of within each group
|
|
1136
|
+
* @returns A Map from each present group value to the min of the aggregated column in that group
|
|
1137
|
+
*/
|
|
1138
|
+
async minBy(groupColumn, aggregatedColumn) {
|
|
1139
|
+
return await this.dbDriverInstance().minBy(groupColumn, aggregatedColumn);
|
|
1140
|
+
}
|
|
1056
1141
|
/**
|
|
1057
1142
|
* Retrieves the sum value of the specified column
|
|
1058
1143
|
* for this Query
|
|
@@ -1068,6 +1153,32 @@ export default class Query {
|
|
|
1068
1153
|
async sum(columnName) {
|
|
1069
1154
|
return await this.dbDriverInstance().sum(columnName);
|
|
1070
1155
|
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Retrieves the sum of the specified column within each group, keyed by
|
|
1158
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
1159
|
+
*
|
|
1160
|
+
* ```ts
|
|
1161
|
+
* await CompositionAsset.query().sumBy('name', 'score')
|
|
1162
|
+
* // Map(2) { 'primary' => 10, 'secondary' => 4 }
|
|
1163
|
+
* ```
|
|
1164
|
+
*
|
|
1165
|
+
* Any `where` clauses, base scopes (soft-delete / STI), and joined-association
|
|
1166
|
+
* `and` clauses on the Query carry through automatically. A joined-association
|
|
1167
|
+
* column may be grouped on or aggregated by passing its namespaced name (e.g.
|
|
1168
|
+
* `'compositionAssets.name'`).
|
|
1169
|
+
*
|
|
1170
|
+
* Only groups with at least one matching row appear in the Map (inherent to
|
|
1171
|
+
* `GROUP BY`). When the group column is nullable, records with a `null` value
|
|
1172
|
+
* are grouped under a real `null` key; a group whose aggregated values are all
|
|
1173
|
+
* `null` yields a `null` value.
|
|
1174
|
+
*
|
|
1175
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
1176
|
+
* @param aggregatedColumn - the column to sum within each group
|
|
1177
|
+
* @returns A Map from each present group value to the sum of the aggregated column in that group
|
|
1178
|
+
*/
|
|
1179
|
+
async sumBy(groupColumn, aggregatedColumn) {
|
|
1180
|
+
return await this.dbDriverInstance().sumBy(groupColumn, aggregatedColumn);
|
|
1181
|
+
}
|
|
1071
1182
|
/**
|
|
1072
1183
|
* Retrieves the average value of the specified column
|
|
1073
1184
|
* for this Query
|
|
@@ -1083,6 +1194,32 @@ export default class Query {
|
|
|
1083
1194
|
async avg(columnName) {
|
|
1084
1195
|
return await this.dbDriverInstance().avg(columnName);
|
|
1085
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Retrieves the average of the specified column within each group, keyed by
|
|
1199
|
+
* the value of the provided group column (a SQL `GROUP BY`).
|
|
1200
|
+
*
|
|
1201
|
+
* ```ts
|
|
1202
|
+
* await CompositionAsset.query().avgBy('name', 'score')
|
|
1203
|
+
* // Map(2) { 'primary' => 5, 'secondary' => 4 }
|
|
1204
|
+
* ```
|
|
1205
|
+
*
|
|
1206
|
+
* Any `where` clauses, base scopes (soft-delete / STI), and joined-association
|
|
1207
|
+
* `and` clauses on the Query carry through automatically. A joined-association
|
|
1208
|
+
* column may be grouped on or aggregated by passing its namespaced name (e.g.
|
|
1209
|
+
* `'compositionAssets.name'`).
|
|
1210
|
+
*
|
|
1211
|
+
* Only groups with at least one matching row appear in the Map (inherent to
|
|
1212
|
+
* `GROUP BY`). When the group column is nullable, records with a `null` value
|
|
1213
|
+
* are grouped under a real `null` key; a group whose aggregated values are all
|
|
1214
|
+
* `null` yields a `null` value.
|
|
1215
|
+
*
|
|
1216
|
+
* @param groupColumn - the column to group by (base or joined-association-namespaced)
|
|
1217
|
+
* @param aggregatedColumn - the column to average within each group
|
|
1218
|
+
* @returns A Map from each present group value to the average of the aggregated column in that group
|
|
1219
|
+
*/
|
|
1220
|
+
async avgBy(groupColumn, aggregatedColumn) {
|
|
1221
|
+
return await this.dbDriverInstance().avgBy(groupColumn, aggregatedColumn);
|
|
1222
|
+
}
|
|
1086
1223
|
/**
|
|
1087
1224
|
* Plucks the provided fields from the given dream class table
|
|
1088
1225
|
*
|