@rvoh/dream 2.41.0 → 2.42.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 +155 -13
- package/dist/cjs/src/cli/index.js +39 -5
- package/dist/cjs/src/db/DreamDbConnection.js +19 -1
- package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +34 -2
- package/dist/cjs/src/dream/Query.js +178 -14
- package/dist/cjs/src/dream/QueryDriver/Base.js +80 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +35 -29
- package/dist/cjs/src/dream/QueryDriver/Postgres.js +83 -1
- package/dist/cjs/src/dream/internal/findOrCreateBy.js +6 -1
- package/dist/cjs/src/dream/internal/updateOrCreateBy.js +6 -1
- package/dist/cjs/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +24 -10
- package/dist/cjs/src/errors/CreateOrFindByFailedToCreateAndFind.js +63 -6
- package/dist/cjs/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.js +41 -6
- package/dist/cjs/src/errors/GeneratedFilesAlreadyExist.js +38 -0
- package/dist/cjs/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.js +28 -0
- package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +62 -10
- package/dist/cjs/src/helpers/cli/generateDream.js +109 -18
- package/dist/cjs/src/helpers/cli/generateMigration.js +12 -8
- package/dist/cjs/src/helpers/cli/writeGeneratedFile.js +3 -3
- package/dist/cjs/src/helpers/db/normalizeDataForDb.js +17 -2
- package/dist/esm/src/Dream.js +155 -13
- package/dist/esm/src/cli/index.js +39 -5
- package/dist/esm/src/db/DreamDbConnection.js +19 -1
- package/dist/esm/src/dream/DreamClassTransactionBuilder.js +34 -2
- package/dist/esm/src/dream/Query.js +178 -14
- package/dist/esm/src/dream/QueryDriver/Base.js +80 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +35 -29
- package/dist/esm/src/dream/QueryDriver/Postgres.js +83 -1
- package/dist/esm/src/dream/internal/findOrCreateBy.js +6 -1
- package/dist/esm/src/dream/internal/updateOrCreateBy.js +6 -1
- package/dist/esm/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +24 -10
- package/dist/esm/src/errors/CreateOrFindByFailedToCreateAndFind.js +63 -6
- package/dist/esm/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.js +41 -6
- package/dist/esm/src/errors/GeneratedFilesAlreadyExist.js +38 -0
- package/dist/esm/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.js +28 -0
- package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +62 -10
- package/dist/esm/src/helpers/cli/generateDream.js +109 -18
- package/dist/esm/src/helpers/cli/generateMigration.js +12 -8
- package/dist/esm/src/helpers/cli/writeGeneratedFile.js +3 -3
- package/dist/esm/src/helpers/db/normalizeDataForDb.js +17 -2
- package/dist/types/src/Dream.d.ts +109 -6
- package/dist/types/src/bin/index.d.ts +11 -0
- package/dist/types/src/cli/index.d.ts +27 -0
- package/dist/types/src/db/DreamDbConnection.d.ts +15 -1
- package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +34 -2
- package/dist/types/src/dream/Query.d.ts +101 -6
- package/dist/types/src/dream/QueryDriver/Base.d.ts +59 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +9 -0
- package/dist/types/src/dream/QueryDriver/Postgres.d.ts +29 -1
- package/dist/types/src/dream/internal/executeDatabaseQuery.d.ts +2 -1
- package/dist/types/src/errors/CreateOrFindByFailedToCreateAndFind.d.ts +40 -1
- package/dist/types/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.d.ts +8 -1
- package/dist/types/src/errors/GeneratedFilesAlreadyExist.d.ts +18 -0
- package/dist/types/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.d.ts +12 -0
- package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +23 -1
- package/dist/types/src/helpers/cli/generateDream.d.ts +25 -0
- package/dist/types/src/helpers/cli/generateMigration.d.ts +20 -2
- package/dist/types/src/helpers/cli/syncWithFailureContext.d.ts +7 -2
- package/dist/types/src/helpers/cli/writeGeneratedFile.d.ts +6 -1
- package/dist/types/src/helpers/db/normalizeDataForDb.d.ts +7 -0
- 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 +87 -50
- package/docs/classes/db.PostgresQueryDriver.html +76 -50
- package/docs/classes/db.QueryDriverBase.html +87 -50
- package/docs/classes/errors.CannotSaveMissingDream.html +2 -2
- package/docs/classes/errors.CheckConstraintViolation.html +3 -3
- package/docs/classes/errors.ColumnOverflow.html +3 -3
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +6 -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.SortableScopeDidNotStabilize.html +3 -3
- package/docs/classes/errors.SortableScopeLockWaitTimedOut.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 +203 -133
- package/docs/classes/index.DreamApp.html +13 -13
- package/docs/classes/index.DreamTransaction.html +3 -3
- package/docs/classes/index.Env.html +5 -5
- package/docs/classes/index.Query.html +147 -100
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +7 -3
- 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 +4 -4
- 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 +1 -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 +3 -3
- 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.DecryptCallbacks.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 +6 -6
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.EncryptionLegacyKeyUsedEvent.html +4 -4
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.IntrospectedColumn.html +7 -7
- package/docs/interfaces/types.IntrospectedDatabase.html +2 -2
- package/docs/interfaces/types.IntrospectedEnum.html +2 -2
- package/docs/interfaces/types.IntrospectedTable.html +5 -5
- 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.OpenapiSchemaCombinatorFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
- 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.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.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 +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
- 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.IntrospectedValueType.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 +1 -1
- package/dist/cjs/src/helpers/cli/generateFactory.js +0 -10
- package/dist/cjs/src/helpers/cli/generateSerializer.js +0 -18
- package/dist/cjs/src/helpers/cli/generateUnitSpec.js +0 -10
- package/dist/esm/src/helpers/cli/generateFactory.js +0 -10
- package/dist/esm/src/helpers/cli/generateSerializer.js +0 -18
- package/dist/esm/src/helpers/cli/generateUnitSpec.js +0 -10
- package/dist/types/src/helpers/cli/generateFactory.d.ts +0 -5
- package/dist/types/src/helpers/cli/generateSerializer.d.ts +0 -9
- package/dist/types/src/helpers/cli/generateUnitSpec.d.ts +0 -3
|
@@ -18,14 +18,22 @@ import NoUpdateOnAssociationQuery from '../errors/NoUpdateOnAssociationQuery.js'
|
|
|
18
18
|
import CannotPaginateWithLeftJoinPreload from '../errors/pagination/CannotPaginateWithLeftJoinPreload.js';
|
|
19
19
|
import CannotPaginateWithLimit from '../errors/pagination/CannotPaginateWithLimit.js';
|
|
20
20
|
import CannotPaginateWithOffset from '../errors/pagination/CannotPaginateWithOffset.js';
|
|
21
|
+
import ColumnOverflow from '../errors/db/ColumnOverflow.js';
|
|
22
|
+
import DataTypeColumnTypeMismatch from '../errors/db/DataTypeColumnTypeMismatch.js';
|
|
21
23
|
import RecordNotFound from '../errors/RecordNotFound.js';
|
|
22
24
|
import cloneDeepSafe from '../helpers/cloneDeepSafe.js';
|
|
25
|
+
import cachedTypeForAttribute from '../helpers/db/cachedTypeForAttribute.js';
|
|
26
|
+
import { isMismatchRaisedBeforeQuerying, normalizeDataForDb } from '../helpers/db/normalizeDataForDb.js';
|
|
23
27
|
import isObject from '../helpers/isObject.js';
|
|
24
28
|
import namespaceColumn from '../helpers/namespaceColumn.js';
|
|
25
29
|
import protectAgainstPollutingAssignment from '../helpers/protectAgainstPollutingAssignment.js';
|
|
26
30
|
import { toSafeObject } from '../helpers/toSafeObject.js';
|
|
27
31
|
import uniq from '../helpers/uniq.js';
|
|
28
32
|
import ops from '../ops/index.js';
|
|
33
|
+
import OpsStatement from '../ops/ops-statement.js';
|
|
34
|
+
import BaseClockTime from '../utils/datetime/BaseClockTime.js';
|
|
35
|
+
import CalendarDate from '../utils/datetime/CalendarDate.js';
|
|
36
|
+
import { DateTime } from '../utils/datetime/DateTime.js';
|
|
29
37
|
import assertRecognizedAssociationConditionKeys, { ASSOCIATION_CONDITION_KEYS, } from './internal/associations/assertRecognizedAssociationConditionKeys.js';
|
|
30
38
|
import buildSerializerPreloadPaths from './internal/buildSerializerPreloadPaths.js';
|
|
31
39
|
import computedPaginatePage from './internal/computedPaginatePage.js';
|
|
@@ -359,12 +367,25 @@ export default class Query {
|
|
|
359
367
|
}
|
|
360
368
|
/**
|
|
361
369
|
* Finds a record matching the Query with the
|
|
362
|
-
* specified primary key. Returns null if not found
|
|
370
|
+
* specified primary key. Returns null if not found,
|
|
371
|
+
* including when the primary key is a value its column
|
|
372
|
+
* cannot hold (for example, `'abc'` for an integer
|
|
373
|
+
* primary key).
|
|
374
|
+
*
|
|
375
|
+
* When the database rejects a value elsewhere in the Query (for
|
|
376
|
+
* example, in a `whereNot`, `whereAny` or `leftJoinPreload`
|
|
377
|
+
* clause), that rejection is raised as `DataTypeColumnTypeMismatch`
|
|
378
|
+
* or `ColumnOverflow`, since a record may still match. Inside a
|
|
379
|
+
* transaction, a primary key the database rejects is raised that
|
|
380
|
+
* way too, because the database has then aborted the transaction.
|
|
363
381
|
*
|
|
364
382
|
* ```ts
|
|
365
383
|
* await User.query().find(123)
|
|
366
384
|
* // User{id: 123}
|
|
367
385
|
*
|
|
386
|
+
* await User.query().find('abc')
|
|
387
|
+
* // null
|
|
388
|
+
*
|
|
368
389
|
* await User.query().find(null)
|
|
369
390
|
* // null
|
|
370
391
|
*
|
|
@@ -378,14 +399,22 @@ export default class Query {
|
|
|
378
399
|
async find(primaryKey) {
|
|
379
400
|
if (!primaryKey)
|
|
380
401
|
return null;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}).first();
|
|
402
|
+
const lookup = { [this.dreamInstance['_primaryKey']]: primaryKey };
|
|
403
|
+
return await this.where(lookup).firstForLookup(lookup);
|
|
384
404
|
}
|
|
385
405
|
/**
|
|
386
406
|
* Finds a record matching the Query with the
|
|
387
|
-
* specified primary key. If not found,
|
|
388
|
-
* is raised
|
|
407
|
+
* specified primary key. If not found, `RecordNotFound`
|
|
408
|
+
* is raised, including when the primary key is a value
|
|
409
|
+
* its column cannot hold (for example, `'abc'` for an
|
|
410
|
+
* integer primary key).
|
|
411
|
+
*
|
|
412
|
+
* When the database rejects a value elsewhere in the Query (for
|
|
413
|
+
* example, in a `whereNot`, `whereAny` or `leftJoinPreload`
|
|
414
|
+
* clause), that rejection is raised as `DataTypeColumnTypeMismatch`
|
|
415
|
+
* or `ColumnOverflow`, since a record may still match. Inside a
|
|
416
|
+
* transaction, a primary key the database rejects is raised that
|
|
417
|
+
* way too, because the database has then aborted the transaction.
|
|
389
418
|
*
|
|
390
419
|
* ```ts
|
|
391
420
|
* await User.query().findOrFail(123)
|
|
@@ -394,6 +423,7 @@ export default class Query {
|
|
|
394
423
|
*
|
|
395
424
|
* @param primaryKey - The primary key of the record to look up
|
|
396
425
|
* @returns The found record
|
|
426
|
+
* @throws RecordNotFound if no record is found
|
|
397
427
|
*/
|
|
398
428
|
async findOrFail(primaryKey) {
|
|
399
429
|
const record = await this.find(primaryKey);
|
|
@@ -404,7 +434,22 @@ export default class Query {
|
|
|
404
434
|
/**
|
|
405
435
|
* Finds a record matching the Query and the
|
|
406
436
|
* specified where statement. If not found, null
|
|
407
|
-
* is returned
|
|
437
|
+
* is returned, including when a value the where
|
|
438
|
+
* statement compares for equality (a plain value or
|
|
439
|
+
* `ops.equal`) is one its column cannot hold: for example,
|
|
440
|
+
* `'abc'` for a uuid column, or a date string Dream cannot
|
|
441
|
+
* parse for a date column.
|
|
442
|
+
*
|
|
443
|
+
* A value the database rejects anywhere else (in a list of values,
|
|
444
|
+
* under another operator such as `ops.not.equal` or `ops.lessThan`,
|
|
445
|
+
* or elsewhere in the Query, such as a `whereNot` or `whereAny`
|
|
446
|
+
* clause) is raised as `DataTypeColumnTypeMismatch` or
|
|
447
|
+
* `ColumnOverflow`, since a record may still match; so is a date,
|
|
448
|
+
* datetime or time string there that Dream cannot parse. Inside a
|
|
449
|
+
* transaction, an equality value the database rejects is raised that
|
|
450
|
+
* way too, because the database has then aborted the transaction; a
|
|
451
|
+
* date, datetime or time string Dream cannot parse never reaches the
|
|
452
|
+
* database, so it is treated as not found there as well.
|
|
408
453
|
*
|
|
409
454
|
* ```ts
|
|
410
455
|
* await User.query().findBy({ email: 'how@yadoin' })
|
|
@@ -415,12 +460,27 @@ export default class Query {
|
|
|
415
460
|
* @returns Either the first record found matching the attributes, or else null
|
|
416
461
|
*/
|
|
417
462
|
async findBy(whereStatement) {
|
|
418
|
-
return await this._where(whereStatement, 'where').
|
|
463
|
+
return await this._where(whereStatement, 'where').firstForLookup(whereStatement);
|
|
419
464
|
}
|
|
420
465
|
/**
|
|
421
466
|
* Finds a record matching the Query and the
|
|
422
|
-
* specified where statement. If not found,
|
|
423
|
-
* is raised
|
|
467
|
+
* specified where statement. If not found, `RecordNotFound`
|
|
468
|
+
* is raised, including when a value the where
|
|
469
|
+
* statement compares for equality (a plain value or
|
|
470
|
+
* `ops.equal`) is one its column cannot hold: for example,
|
|
471
|
+
* `'abc'` for a uuid column, or a date string Dream cannot
|
|
472
|
+
* parse for a date column.
|
|
473
|
+
*
|
|
474
|
+
* A value the database rejects anywhere else (in a list of values,
|
|
475
|
+
* under another operator such as `ops.not.equal` or `ops.lessThan`,
|
|
476
|
+
* or elsewhere in the Query, such as a `whereNot` or `whereAny`
|
|
477
|
+
* clause) is raised as `DataTypeColumnTypeMismatch` or
|
|
478
|
+
* `ColumnOverflow`, since a record may still match; so is a date,
|
|
479
|
+
* datetime or time string there that Dream cannot parse. Inside a
|
|
480
|
+
* transaction, an equality value the database rejects is raised that
|
|
481
|
+
* way too, because the database has then aborted the transaction; a
|
|
482
|
+
* date, datetime or time string Dream cannot parse never reaches the
|
|
483
|
+
* database, so it is treated as not found there as well.
|
|
424
484
|
*
|
|
425
485
|
* ```ts
|
|
426
486
|
* await User.query().findOrFailBy({ email: 'how@yadoin' })
|
|
@@ -429,6 +489,7 @@ export default class Query {
|
|
|
429
489
|
*
|
|
430
490
|
* @param whereStatement - The where statement used to locate the record
|
|
431
491
|
* @returns The first record found matching the attributes
|
|
492
|
+
* @throws RecordNotFound if no record is found
|
|
432
493
|
*/
|
|
433
494
|
async findOrFailBy(whereStatement) {
|
|
434
495
|
const record = await this.findBy(whereStatement);
|
|
@@ -436,6 +497,98 @@ export default class Query {
|
|
|
436
497
|
throw new RecordNotFound(this.dreamInstance['sanitizedConstructorName']);
|
|
437
498
|
return record;
|
|
438
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* @internal
|
|
502
|
+
*
|
|
503
|
+
* The first record of this Query, for `find` and `findBy`, whose `lookup`
|
|
504
|
+
* (the primary key or where statement they were given) is already applied.
|
|
505
|
+
* Returns null when the lookup's own values tell that no record can match
|
|
506
|
+
* (see `lookupValueCannotBeHeld`); every other error is raised. Preloads
|
|
507
|
+
* run after the lookup's statement, so a rejected value in a preload clause
|
|
508
|
+
* is raised.
|
|
509
|
+
*/
|
|
510
|
+
async firstForLookup(lookup) {
|
|
511
|
+
const hasPreloads = Object.keys(this.preloadStatements).length > 0;
|
|
512
|
+
const lookupQuery = hasPreloads ? this.clone({ preloadStatements: {}, preloadOnStatements: {} }) : this;
|
|
513
|
+
let record;
|
|
514
|
+
try {
|
|
515
|
+
record = await lookupQuery.first();
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
if (await this.lookupValueCannotBeHeld(error, lookup))
|
|
519
|
+
return null;
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
if (record && hasPreloads)
|
|
523
|
+
await this.dbDriverInstance().hydratePreload(record);
|
|
524
|
+
return record;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* @internal
|
|
528
|
+
*
|
|
529
|
+
* Whether `error`, raised by a `find` or `findBy` statement, is a value of
|
|
530
|
+
* the lookup's own equality comparisons (see `lookupEqualityValues`) that
|
|
531
|
+
* its column cannot hold. Every matching record must equal each of those
|
|
532
|
+
* values, so when one cannot be held, no record can match. A rejected value
|
|
533
|
+
* anywhere else (a negated or OR'd clause, a join's `and` clause, a list of
|
|
534
|
+
* values, a negated or ordering operator) does not tell that, so it is
|
|
535
|
+
* raised.
|
|
536
|
+
*
|
|
537
|
+
* When Dream itself could not convert a value (a date, datetime or time
|
|
538
|
+
* string it cannot parse), nothing reached the database, and Dream's
|
|
539
|
+
* conversion of the lookup's values alone tells whether one of them was
|
|
540
|
+
* the value. When the database rejected the statement, the driver is asked
|
|
541
|
+
* whether it rejects one of the lookup's values on its own; inside a
|
|
542
|
+
* transaction it cannot be asked, since the database has aborted the
|
|
543
|
+
* transaction, so the error is raised.
|
|
544
|
+
*/
|
|
545
|
+
async lookupValueCannotBeHeld(error, lookup) {
|
|
546
|
+
if (!(error instanceof DataTypeColumnTypeMismatch || error instanceof ColumnOverflow))
|
|
547
|
+
return false;
|
|
548
|
+
const equalityValues = this.lookupEqualityValues(lookup);
|
|
549
|
+
const convertForDb = ({ column, value }) => normalizeDataForDb({ val: value, dreamClass: this.dreamClass, column });
|
|
550
|
+
if (isMismatchRaisedBeforeQuerying(error))
|
|
551
|
+
return equalityValues.some(equalityValue => {
|
|
552
|
+
try {
|
|
553
|
+
convertForDb(equalityValue);
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
catch (conversionError) {
|
|
557
|
+
if (conversionError instanceof DataTypeColumnTypeMismatch)
|
|
558
|
+
return true;
|
|
559
|
+
throw conversionError;
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
if (this.dreamTransaction)
|
|
563
|
+
return false;
|
|
564
|
+
return await Query.dbDriverClass(this.connectionName).rejectsAnyValueForColumnType(this.connectionName, this.connectionOverride ?? 'primary', equalityValues.map(equalityValue => ({
|
|
565
|
+
value: convertForDb(equalityValue),
|
|
566
|
+
dbType: cachedTypeForAttribute(this.dreamClass, equalityValue.column),
|
|
567
|
+
})));
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* @internal
|
|
571
|
+
*
|
|
572
|
+
* The values a `find` or `findBy` lookup compares a column of this model
|
|
573
|
+
* with `=`, as a plain value or `ops.equal`: the primary key for `find`.
|
|
574
|
+
* Lists of values, other operators, ranges, associations, subqueries and
|
|
575
|
+
* nulls are left out, since a value there that its column cannot hold does
|
|
576
|
+
* not stop a record from matching the lookup.
|
|
577
|
+
*/
|
|
578
|
+
lookupEqualityValues(lookup) {
|
|
579
|
+
return Object.entries(lookup).flatMap(([column, comparison]) => {
|
|
580
|
+
if (!cachedTypeForAttribute(this.dreamClass, column))
|
|
581
|
+
return [];
|
|
582
|
+
if (comparison instanceof OpsStatement) {
|
|
583
|
+
if (comparison.operator !== '=')
|
|
584
|
+
return [];
|
|
585
|
+
const value = comparison.value;
|
|
586
|
+
// an array compared with `=` is a whole value of an array column
|
|
587
|
+
return Array.isArray(value) || isScalarWhereValue(value) ? [{ column, value }] : [];
|
|
588
|
+
}
|
|
589
|
+
return isScalarWhereValue(comparison) ? [{ column, value: comparison }] : [];
|
|
590
|
+
});
|
|
591
|
+
}
|
|
439
592
|
/**
|
|
440
593
|
* Finds all records matching the Query in batches,
|
|
441
594
|
* and then calls the provided callback for each found record.
|
|
@@ -1970,10 +2123,7 @@ export default class Query {
|
|
|
1970
2123
|
* @returns boolean - true if any records match the query, false otherwise
|
|
1971
2124
|
*/
|
|
1972
2125
|
async exists() {
|
|
1973
|
-
|
|
1974
|
-
// because it avoids the step of finding the first. Just find any, and return
|
|
1975
|
-
// that one.
|
|
1976
|
-
return (await this.limit(1).all()).length > 0;
|
|
2126
|
+
return await this.dbDriverInstance().exists();
|
|
1977
2127
|
}
|
|
1978
2128
|
/**
|
|
1979
2129
|
* Returns the first record in the database
|
|
@@ -2749,3 +2899,17 @@ function isCursorPrimaryKey(value) {
|
|
|
2749
2899
|
function isCursorRowKey(value) {
|
|
2750
2900
|
return value === null || isCursorPrimaryKey(value);
|
|
2751
2901
|
}
|
|
2902
|
+
/**
|
|
2903
|
+
* Whether a where-clause value is compared with a column as one value: a
|
|
2904
|
+
* string, number, bigint or boolean, or a date or time object, which Dream
|
|
2905
|
+
* converts to a string before sending it.
|
|
2906
|
+
*/
|
|
2907
|
+
function isScalarWhereValue(value) {
|
|
2908
|
+
return (typeof value === 'string' ||
|
|
2909
|
+
typeof value === 'number' ||
|
|
2910
|
+
typeof value === 'bigint' ||
|
|
2911
|
+
typeof value === 'boolean' ||
|
|
2912
|
+
value instanceof DateTime ||
|
|
2913
|
+
value instanceof CalendarDate ||
|
|
2914
|
+
value instanceof BaseClockTime);
|
|
2915
|
+
}
|
|
@@ -226,6 +226,31 @@ export default class QueryDriverBase {
|
|
|
226
226
|
static isUniqueConstraintViolation(error) {
|
|
227
227
|
return false;
|
|
228
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* @internal
|
|
231
|
+
*
|
|
232
|
+
* The columns of the unique index whose violation `error` reports, in index
|
|
233
|
+
* order, when that index is on `table` and is a plain one: every key is a
|
|
234
|
+
* column and it has no predicate. `createOrFindBy` and `createOrUpdateBy`
|
|
235
|
+
* ask this after a failed insert to tell which of their attributes
|
|
236
|
+
* conflicted. Returns null when the driver cannot tell, when the index is
|
|
237
|
+
* on another table, or when it is an expression or partial index; the
|
|
238
|
+
* default returns null.
|
|
239
|
+
*
|
|
240
|
+
* @param connectionName - the connection the insert ran on
|
|
241
|
+
* @param error - the error the insert raised
|
|
242
|
+
* @param table - the table the insert wrote to
|
|
243
|
+
*/
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
245
|
+
static async violatedUniqueIndexColumns(
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
247
|
+
connectionName,
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
249
|
+
error,
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
251
|
+
table) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
229
254
|
/**
|
|
230
255
|
* @internal
|
|
231
256
|
*
|
|
@@ -238,6 +263,48 @@ export default class QueryDriverBase {
|
|
|
238
263
|
static isDeadlock(error) {
|
|
239
264
|
return false;
|
|
240
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* @internal
|
|
268
|
+
*
|
|
269
|
+
* The Dream error to raise in place of `error` when `error` is the database
|
|
270
|
+
* rejecting a value for the column it is written to or compared with: a value
|
|
271
|
+
* the column's type cannot represent, a null in a NOT NULL column, or a CHECK
|
|
272
|
+
* constraint violation. Saves and reads (selects, aggregates, plucks and
|
|
273
|
+
* preloads) raise the returned error; bulk `Query#update` and `Query#delete`
|
|
274
|
+
* do not consult this. A driver that does not recognize the error returns
|
|
275
|
+
* null, and the original error propagates unchanged.
|
|
276
|
+
*/
|
|
277
|
+
static dataIncompatibleWithDatabaseFieldError(
|
|
278
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
279
|
+
error) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* @internal
|
|
284
|
+
*
|
|
285
|
+
* Whether the database rejects any of `values` as input for its `dbType`
|
|
286
|
+
* (the column type in the model's schema), judged by the database's own
|
|
287
|
+
* input rules. `find` and `findBy` ask this, outside a transaction, after
|
|
288
|
+
* the database rejected their statement, passing the values their lookup
|
|
289
|
+
* compares for equality: when one of those is rejected, no record can match
|
|
290
|
+
* the lookup, which then returns null; otherwise the rejected value was
|
|
291
|
+
* elsewhere in the Query, and the statement's error is raised. A driver that
|
|
292
|
+
* cannot tell returns false; the default does.
|
|
293
|
+
*
|
|
294
|
+
* @param connectionName - the connection the lookup ran on
|
|
295
|
+
* @param dbConnectionType - the connection type to ask on
|
|
296
|
+
* @param values - each value as sent to the database, with its column's type
|
|
297
|
+
*/
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
299
|
+
static async rejectsAnyValueForColumnType(
|
|
300
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
301
|
+
connectionName,
|
|
302
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
303
|
+
dbConnectionType,
|
|
304
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
305
|
+
values) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
241
308
|
/**
|
|
242
309
|
* @internal
|
|
243
310
|
*
|
|
@@ -517,6 +584,19 @@ export default class QueryDriverBase {
|
|
|
517
584
|
async avgBy(groupColumn, aggregatedColumn) {
|
|
518
585
|
throw new Error('implement avgBy in child class');
|
|
519
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Returns true if a record matches the Query, false otherwise
|
|
589
|
+
*
|
|
590
|
+
* ```ts
|
|
591
|
+
* await User.where({ email: 'how@yadoin' }).exists()
|
|
592
|
+
* ```
|
|
593
|
+
*
|
|
594
|
+
* @returns true if any records match the Query, false otherwise
|
|
595
|
+
*/
|
|
596
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
597
|
+
async exists() {
|
|
598
|
+
throw new Error('implement exists in child class');
|
|
599
|
+
}
|
|
520
600
|
/**
|
|
521
601
|
* Retrieves the number of records in the database
|
|
522
602
|
*
|
|
@@ -11,7 +11,6 @@ import { NoResultError, PostgresDialect, sql, } from 'kysely';
|
|
|
11
11
|
import pluralize from 'pluralize-esm';
|
|
12
12
|
import { CliFileWriter } from '../../cli/CliFileWriter.js';
|
|
13
13
|
import DreamCLI from '../../cli/index.js';
|
|
14
|
-
import { CHECK_VIOLATION, COLUMN_OVERFLOW, INVALID_INPUT_SYNTAX, NOT_NULL_VIOLATION, pgErrorType, } from '../../db/errors.js';
|
|
15
14
|
import { default as _db } from '../../db/index.js';
|
|
16
15
|
import validateColumn from '../../db/validators/validateColumn.js';
|
|
17
16
|
import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
|
|
@@ -33,10 +32,6 @@ import CannotSaveMissingDream from '../../errors/CannotSaveMissingDream.js';
|
|
|
33
32
|
import ArrayEqualityRequiresArrayColumn from '../../errors/ops/ArrayEqualityRequiresArrayColumn.js';
|
|
34
33
|
import RowLockIncompatibleWithDistinct from '../../errors/RowLockIncompatibleWithDistinct.js';
|
|
35
34
|
import RowLockIncompatibleWithJoinLoad from '../../errors/RowLockIncompatibleWithJoinLoad.js';
|
|
36
|
-
import CheckConstraintViolation from '../../errors/db/CheckConstraintViolation.js';
|
|
37
|
-
import ColumnOverflow from '../../errors/db/ColumnOverflow.js';
|
|
38
|
-
import DataTypeColumnTypeMismatch from '../../errors/db/DataTypeColumnTypeMismatch.js';
|
|
39
|
-
import NotNullViolation from '../../errors/db/NotNullViolation.js';
|
|
40
35
|
import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
|
|
41
36
|
import generateMigration from '../../helpers/cli/generateMigration.js';
|
|
42
37
|
import compact from '../../helpers/compact.js';
|
|
@@ -407,7 +402,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
407
402
|
return (await new driverClass(query)['executeJoinLoad']())[0] || null;
|
|
408
403
|
}
|
|
409
404
|
const kyselyQuery = new this.constructor(this.query.limit(1)).buildSelect();
|
|
410
|
-
const results = await
|
|
405
|
+
const results = await this.executeReadQuery(kyselyQuery, 'executeTakeFirst');
|
|
411
406
|
if (results) {
|
|
412
407
|
const theFirst = this.dbResultToDreamInstance(results, this.dreamClass);
|
|
413
408
|
if (theFirst)
|
|
@@ -449,7 +444,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
449
444
|
// row locking is adapter-specific, so it goes through the query driver seam
|
|
450
445
|
if (options.lock)
|
|
451
446
|
kyselyQuery = this.applyRowLock(kyselyQuery, this.query['baseSqlAlias']);
|
|
452
|
-
const results = await
|
|
447
|
+
const results = await this.executeReadQuery(kyselyQuery, 'execute');
|
|
453
448
|
const theAll = results.map(r => this.dbResultToDreamInstance(r, this.dreamClass));
|
|
454
449
|
await this.applyPreload(this.query['preloadStatements'], this.query['preloadOnStatements'], theAll);
|
|
455
450
|
return theAll;
|
|
@@ -475,7 +470,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
475
470
|
bypassOrder: true,
|
|
476
471
|
});
|
|
477
472
|
kyselyQuery = kyselyQuery.select(max(this.namespaceColumn(columnName)));
|
|
478
|
-
const data = await
|
|
473
|
+
const data = await this.executeReadQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
479
474
|
return data.max;
|
|
480
475
|
}
|
|
481
476
|
/**
|
|
@@ -516,7 +511,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
516
511
|
bypassOrder: true,
|
|
517
512
|
});
|
|
518
513
|
kyselyQuery = kyselyQuery.select(min(this.namespaceColumn(columnName)));
|
|
519
|
-
const data = await
|
|
514
|
+
const data = await this.executeReadQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
520
515
|
return data.min;
|
|
521
516
|
}
|
|
522
517
|
/**
|
|
@@ -557,7 +552,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
557
552
|
bypassOrder: true,
|
|
558
553
|
});
|
|
559
554
|
kyselyQuery = kyselyQuery.select(sum(this.namespaceColumn(columnName)));
|
|
560
|
-
const data = await
|
|
555
|
+
const data = await this.executeReadQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
561
556
|
return data.sum === null ? null : parseFloat(data.sum);
|
|
562
557
|
}
|
|
563
558
|
/**
|
|
@@ -598,7 +593,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
598
593
|
bypassOrder: true,
|
|
599
594
|
});
|
|
600
595
|
kyselyQuery = kyselyQuery.select(avg(this.namespaceColumn(columnName)));
|
|
601
|
-
const data = await
|
|
596
|
+
const data = await this.executeReadQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
602
597
|
return data.avg;
|
|
603
598
|
}
|
|
604
599
|
/**
|
|
@@ -619,6 +614,12 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
619
614
|
const { avg } = this.dbFor('select').fn;
|
|
620
615
|
return this.groupedAggregate(groupColumn, avg(this.namespaceColumn(aggregatedColumn)));
|
|
621
616
|
}
|
|
617
|
+
async exists() {
|
|
618
|
+
const kyselyQuery = new this.constructor(this.query.limit(1))
|
|
619
|
+
.buildSelect({ bypassSelectAll: true, bypassOrder: true })
|
|
620
|
+
.select(sql `1`.as('present'));
|
|
621
|
+
return (await this.executeReadQuery(kyselyQuery, 'executeTakeFirst')) !== undefined;
|
|
622
|
+
}
|
|
622
623
|
/**
|
|
623
624
|
* Retrieves the number of records in the database
|
|
624
625
|
*
|
|
@@ -641,7 +642,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
641
642
|
? count(sql `DISTINCT ${sql.ref(distinctColumn)}`)
|
|
642
643
|
: count(query['namespaceColumn'](query.dreamInstance['_primaryKey']));
|
|
643
644
|
kyselyQuery = kyselyQuery.select(countClause.as('tablecount'));
|
|
644
|
-
const data = await
|
|
645
|
+
const data = await this.executeReadQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
645
646
|
return parseInt(data.tablecount.toString());
|
|
646
647
|
}
|
|
647
648
|
/**
|
|
@@ -690,7 +691,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
690
691
|
.select(`${groupColumnRef} as groupvalue`)
|
|
691
692
|
.select(aggregateExpression.as('aggregatevalue'))
|
|
692
693
|
.groupBy(groupColumnRef);
|
|
693
|
-
const rows = await
|
|
694
|
+
const rows = await this.executeReadQuery(kyselyQuery, 'execute');
|
|
694
695
|
const result = new Map();
|
|
695
696
|
for (const row of rows) {
|
|
696
697
|
result.set(row.groupvalue, coerceValue ? coerceValue(row.aggregatevalue) : row.aggregatevalue);
|
|
@@ -722,7 +723,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
722
723
|
// which the pg package then returns in an object with a single `id` key)
|
|
723
724
|
kyselyQuery = kyselyQuery.select(`${this.namespaceColumn(field)} as ${shortAlias}`);
|
|
724
725
|
});
|
|
725
|
-
return (await
|
|
726
|
+
return (await this.executeReadQuery(kyselyQuery, 'execute')).map(singleResult => shortAliases.map(alias => singleResult[alias]));
|
|
726
727
|
}
|
|
727
728
|
/**
|
|
728
729
|
* Returns a new Kysely SelectQueryBuilder instance to be used
|
|
@@ -815,17 +816,22 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
815
816
|
catch (error) {
|
|
816
817
|
if (dream.isPersisted && error instanceof NoResultError)
|
|
817
818
|
throw new CannotSaveMissingDream(dream);
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
819
|
+
throw this.dataIncompatibleWithDatabaseFieldError(error) ?? error;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* @internal
|
|
824
|
+
*
|
|
825
|
+
* Runs a read statement (a select, aggregate, pluck or preload), raising the
|
|
826
|
+
* driver's Dream error for a value the database rejects in place of the
|
|
827
|
+
* database's own error (see `dataIncompatibleWithDatabaseFieldError`).
|
|
828
|
+
*/
|
|
829
|
+
async executeReadQuery(kyselyQuery, command) {
|
|
830
|
+
try {
|
|
831
|
+
return await executeDatabaseQuery(kyselyQuery, command);
|
|
832
|
+
}
|
|
833
|
+
catch (error) {
|
|
834
|
+
throw (this.constructor.dataIncompatibleWithDatabaseFieldError(error) ?? error);
|
|
829
835
|
}
|
|
830
836
|
}
|
|
831
837
|
dbConnectionType(sqlCommandType) {
|
|
@@ -1047,7 +1053,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1047
1053
|
});
|
|
1048
1054
|
if (this.query['limitStatement'])
|
|
1049
1055
|
pageSelect = pageSelect.limit(this.query['limitStatement']);
|
|
1050
|
-
return (await
|
|
1056
|
+
return (await this.executeReadQuery(pageSelect, 'execute')).map(row => pageColumns.map(({ alias }) => row[alias]));
|
|
1051
1057
|
}
|
|
1052
1058
|
/**
|
|
1053
1059
|
* @internal
|
|
@@ -1080,7 +1086,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1080
1086
|
keyColumns.forEach(({ alias }) => {
|
|
1081
1087
|
keySelect = keySelect.orderBy(`${baseSqlAlias}.${alias}`, this.orderByDirection('asc'));
|
|
1082
1088
|
});
|
|
1083
|
-
return (await
|
|
1089
|
+
return (await this.executeReadQuery(keySelect, 'execute')).map(row => keyColumns.map(({ alias }) => row[alias]));
|
|
1084
1090
|
}
|
|
1085
1091
|
orderByDirection(direction) {
|
|
1086
1092
|
return orderByDirection(direction);
|
|
@@ -1225,7 +1231,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1225
1231
|
columnAliasMap[column] = columnAlias;
|
|
1226
1232
|
});
|
|
1227
1233
|
});
|
|
1228
|
-
const queryResults = await
|
|
1234
|
+
const queryResults = await this.executeReadQuery(kyselyQuery, 'execute');
|
|
1229
1235
|
const aliasToDreamIdMap = queryResults.reduce((aliasToDreamIdMap, singleSqlResult) => {
|
|
1230
1236
|
this.fleshOutJoinLoadExecutionResults({
|
|
1231
1237
|
currentAlias: this.query['baseSqlAlias'],
|
|
@@ -2728,7 +2734,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
2728
2734
|
.buildSelect({ bypassSelectAll: true })
|
|
2729
2735
|
.selectAll(alias)
|
|
2730
2736
|
.select(`${this.namespaceColumn(dreamClass.primaryKey, dreamClass.table)} as ${basePrimaryKeyAlias}`);
|
|
2731
|
-
const rows = await
|
|
2737
|
+
const rows = await this.executeReadQuery(kyselyQuery, 'execute');
|
|
2732
2738
|
const preloadedDreamsAndWhatTheyPointTo = rows.map((row) => {
|
|
2733
2739
|
const hydratedDream = this.dbResultToDreamInstance(row, dreamClassToHydrate);
|
|
2734
2740
|
return {
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
import pg from 'pg';
|
|
10
10
|
import { sql } from 'kysely';
|
|
11
11
|
import DreamCLI from '../../cli/index.js';
|
|
12
|
-
import { DEADLOCK_DETECTED, LOCK_NOT_AVAILABLE, pgErrorType, UNIQUE_VIOLATION } from '../../db/errors.js';
|
|
12
|
+
import { CHECK_VIOLATION, COLUMN_OVERFLOW, DEADLOCK_DETECTED, INVALID_INPUT_SYNTAX, LOCK_NOT_AVAILABLE, NOT_NULL_VIOLATION, pgErrorType, UNIQUE_VIOLATION, } from '../../db/errors.js';
|
|
13
13
|
import DreamApp from '../../dream-app/index.js';
|
|
14
|
+
import CheckConstraintViolation from '../../errors/db/CheckConstraintViolation.js';
|
|
15
|
+
import ColumnOverflow from '../../errors/db/ColumnOverflow.js';
|
|
16
|
+
import DataTypeColumnTypeMismatch from '../../errors/db/DataTypeColumnTypeMismatch.js';
|
|
17
|
+
import NotNullViolation from '../../errors/db/NotNullViolation.js';
|
|
14
18
|
import SortableScopeLockWaitTimedOut from '../../errors/SortableScopeLockWaitTimedOut.js';
|
|
15
19
|
import { findCitextArrayOid, findCorrespondingArrayOid, findEnumArrayOids, parsePostgresBigint, parsePostgresDate, parsePostgresDatetime, parsePostgresDatetimeTz, parsePostgresDecimal, parsePostgresTime, parsePostgresTimeTz, } from '../../helpers/customPgParsers.js';
|
|
16
20
|
import { testDatabasePoolSize } from '../../db/testDatabasePool.js';
|
|
@@ -281,9 +285,87 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
|
|
|
281
285
|
static isUniqueConstraintViolation(error) {
|
|
282
286
|
return pgErrorType(error) === UNIQUE_VIOLATION;
|
|
283
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* @internal
|
|
290
|
+
*
|
|
291
|
+
* Postgres implementation of {@link QueryDriverBase.violatedUniqueIndexColumns}.
|
|
292
|
+
* A unique violation names the index it hit in the error's `constraint`
|
|
293
|
+
* field (a unique constraint's index shares its name), and one catalog query
|
|
294
|
+
* reads that index's key columns. `INCLUDE` columns are not keys and are
|
|
295
|
+
* left out.
|
|
296
|
+
*/
|
|
297
|
+
static async violatedUniqueIndexColumns(connectionName, error, table) {
|
|
298
|
+
if (!(error instanceof pg.DatabaseError) || pgErrorType(error) !== UNIQUE_VIOLATION)
|
|
299
|
+
return null;
|
|
300
|
+
if (!error.constraint || !error.schema || !error.table)
|
|
301
|
+
return null;
|
|
302
|
+
const tableParts = table.split('.');
|
|
303
|
+
const tableName = tableParts.at(-1);
|
|
304
|
+
const tableSchema = tableParts.length > 1 ? tableParts[0] : undefined;
|
|
305
|
+
if (error.table !== tableName || (tableSchema !== undefined && error.schema !== tableSchema))
|
|
306
|
+
return null;
|
|
307
|
+
// the returned columns are aliased to single words: result keys come back camelized
|
|
308
|
+
const result = await sql `
|
|
309
|
+
SELECT
|
|
310
|
+
(idx.indexprs IS NULL AND idx.indpred IS NULL) AS plain,
|
|
311
|
+
ARRAY(
|
|
312
|
+
SELECT att.attname::text
|
|
313
|
+
FROM unnest(idx.indkey::int2[]) WITH ORDINALITY AS k(attnum, ord)
|
|
314
|
+
JOIN pg_catalog.pg_attribute AS att ON att.attrelid = idx.indrelid AND att.attnum = k.attnum
|
|
315
|
+
WHERE k.ord <= idx.indnkeyatts
|
|
316
|
+
ORDER BY k.ord
|
|
317
|
+
) AS columns
|
|
318
|
+
FROM pg_catalog.pg_index AS idx
|
|
319
|
+
JOIN pg_catalog.pg_class AS cls ON cls.oid = idx.indexrelid
|
|
320
|
+
JOIN pg_catalog.pg_namespace AS ns ON ns.oid = cls.relnamespace
|
|
321
|
+
WHERE ns.nspname = ${error.schema} AND cls.relname = ${error.constraint} AND idx.indisunique
|
|
322
|
+
`.execute(this.dbFor(connectionName, 'primary'));
|
|
323
|
+
const index = result.rows[0];
|
|
324
|
+
if (!index?.plain || !index.columns.length)
|
|
325
|
+
return null;
|
|
326
|
+
return index.columns;
|
|
327
|
+
}
|
|
284
328
|
static isDeadlock(error) {
|
|
285
329
|
return pgErrorType(error) === DEADLOCK_DETECTED;
|
|
286
330
|
}
|
|
331
|
+
static dataIncompatibleWithDatabaseFieldError(error) {
|
|
332
|
+
// pgErrorType recognizes only a pg DatabaseError, which is an Error
|
|
333
|
+
const databaseError = error;
|
|
334
|
+
switch (pgErrorType(error)) {
|
|
335
|
+
case COLUMN_OVERFLOW:
|
|
336
|
+
return new ColumnOverflow(databaseError);
|
|
337
|
+
case INVALID_INPUT_SYNTAX:
|
|
338
|
+
return new DataTypeColumnTypeMismatch(databaseError);
|
|
339
|
+
case NOT_NULL_VIOLATION:
|
|
340
|
+
return new NotNullViolation(databaseError);
|
|
341
|
+
case CHECK_VIOLATION:
|
|
342
|
+
return new CheckConstraintViolation(databaseError);
|
|
343
|
+
default:
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* @internal
|
|
349
|
+
*
|
|
350
|
+
* Postgres implementation of {@link QueryDriverBase.rejectsAnyValueForColumnType}.
|
|
351
|
+
* One statement casts every value to its type, so Postgres's own input
|
|
352
|
+
* function for each type is the judge: a cast it rejects raises `22P02` or
|
|
353
|
+
* `22003`. Callers never ask inside a transaction, which a rejected cast
|
|
354
|
+
* would abort.
|
|
355
|
+
*/
|
|
356
|
+
static async rejectsAnyValueForColumnType(connectionName, dbConnectionType, values) {
|
|
357
|
+
if (!values.length)
|
|
358
|
+
return false;
|
|
359
|
+
const casts = values.map(({ value, dbType }) => sql `CAST(${value} AS ${sql.raw(dbType)})`);
|
|
360
|
+
try {
|
|
361
|
+
await sql `SELECT ${sql.join(casts)}`.execute(this.dbFor(connectionName, dbConnectionType));
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
const errorType = pgErrorType(error);
|
|
366
|
+
return errorType === INVALID_INPUT_SYNTAX || errorType === COLUMN_OVERFLOW;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
287
369
|
/**
|
|
288
370
|
* @internal
|
|
289
371
|
*
|