@rvoh/dream 2.40.2 → 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.
Files changed (269) hide show
  1. package/dist/cjs/src/Dream.js +156 -13
  2. package/dist/cjs/src/cli/index.js +39 -5
  3. package/dist/cjs/src/db/DreamDbConnection.js +19 -1
  4. package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +34 -2
  5. package/dist/cjs/src/dream/Query.js +178 -14
  6. package/dist/cjs/src/dream/QueryDriver/Base.js +80 -0
  7. package/dist/cjs/src/dream/QueryDriver/Kysely.js +35 -29
  8. package/dist/cjs/src/dream/QueryDriver/Postgres.js +83 -1
  9. package/dist/cjs/src/dream/internal/findOrCreateBy.js +5 -1
  10. package/dist/cjs/src/dream/internal/findOrCreateLookupStatement.js +19 -0
  11. package/dist/cjs/src/dream/internal/updateOrCreateBy.js +5 -1
  12. package/dist/cjs/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +24 -10
  13. package/dist/cjs/src/errors/CreateOrFindByFailedToCreateAndFind.js +63 -6
  14. package/dist/cjs/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.js +41 -6
  15. package/dist/cjs/src/errors/GeneratedFilesAlreadyExist.js +38 -0
  16. package/dist/cjs/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.js +28 -0
  17. package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +62 -10
  18. package/dist/cjs/src/helpers/cli/generateDream.js +109 -18
  19. package/dist/cjs/src/helpers/cli/generateMigration.js +12 -8
  20. package/dist/cjs/src/helpers/cli/writeGeneratedFile.js +3 -3
  21. package/dist/cjs/src/helpers/db/normalizeDataForDb.js +33 -4
  22. package/dist/cjs/src/ops/index.js +7 -3
  23. package/dist/esm/src/Dream.js +156 -13
  24. package/dist/esm/src/cli/index.js +39 -5
  25. package/dist/esm/src/db/DreamDbConnection.js +19 -1
  26. package/dist/esm/src/dream/DreamClassTransactionBuilder.js +34 -2
  27. package/dist/esm/src/dream/Query.js +178 -14
  28. package/dist/esm/src/dream/QueryDriver/Base.js +80 -0
  29. package/dist/esm/src/dream/QueryDriver/Kysely.js +35 -29
  30. package/dist/esm/src/dream/QueryDriver/Postgres.js +83 -1
  31. package/dist/esm/src/dream/internal/findOrCreateBy.js +5 -1
  32. package/dist/esm/src/dream/internal/findOrCreateLookupStatement.js +19 -0
  33. package/dist/esm/src/dream/internal/updateOrCreateBy.js +5 -1
  34. package/dist/esm/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +24 -10
  35. package/dist/esm/src/errors/CreateOrFindByFailedToCreateAndFind.js +63 -6
  36. package/dist/esm/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.js +41 -6
  37. package/dist/esm/src/errors/GeneratedFilesAlreadyExist.js +38 -0
  38. package/dist/esm/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.js +28 -0
  39. package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +62 -10
  40. package/dist/esm/src/helpers/cli/generateDream.js +109 -18
  41. package/dist/esm/src/helpers/cli/generateMigration.js +12 -8
  42. package/dist/esm/src/helpers/cli/writeGeneratedFile.js +3 -3
  43. package/dist/esm/src/helpers/db/normalizeDataForDb.js +33 -4
  44. package/dist/esm/src/ops/index.js +7 -3
  45. package/dist/types/src/Dream.d.ts +109 -6
  46. package/dist/types/src/bin/index.d.ts +11 -0
  47. package/dist/types/src/cli/index.d.ts +27 -0
  48. package/dist/types/src/db/DreamDbConnection.d.ts +15 -1
  49. package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +34 -2
  50. package/dist/types/src/dream/Query.d.ts +101 -6
  51. package/dist/types/src/dream/QueryDriver/Base.d.ts +59 -0
  52. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +9 -0
  53. package/dist/types/src/dream/QueryDriver/Postgres.d.ts +29 -1
  54. package/dist/types/src/dream/internal/executeDatabaseQuery.d.ts +2 -1
  55. package/dist/types/src/dream/internal/findOrCreateLookupStatement.d.ts +14 -0
  56. package/dist/types/src/errors/CreateOrFindByFailedToCreateAndFind.d.ts +40 -1
  57. package/dist/types/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.d.ts +8 -1
  58. package/dist/types/src/errors/GeneratedFilesAlreadyExist.d.ts +18 -0
  59. package/dist/types/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.d.ts +12 -0
  60. package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +23 -1
  61. package/dist/types/src/helpers/cli/generateDream.d.ts +25 -0
  62. package/dist/types/src/helpers/cli/generateMigration.d.ts +20 -2
  63. package/dist/types/src/helpers/cli/syncWithFailureContext.d.ts +7 -2
  64. package/dist/types/src/helpers/cli/writeGeneratedFile.d.ts +6 -1
  65. package/dist/types/src/helpers/db/normalizeDataForDb.d.ts +7 -0
  66. package/dist/types/src/ops/index.d.ts +7 -3
  67. package/dist/types/src/types/associations/shared.d.ts +7 -5
  68. package/dist/types/src/types/types/associations/shared.ts +27 -5
  69. package/docs/assets/hierarchy.js +1 -1
  70. package/docs/assets/search.js +1 -1
  71. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  72. package/docs/classes/db.KyselyQueryDriver.html +87 -50
  73. package/docs/classes/db.PostgresQueryDriver.html +76 -50
  74. package/docs/classes/db.QueryDriverBase.html +87 -50
  75. package/docs/classes/errors.CannotSaveMissingDream.html +2 -2
  76. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  77. package/docs/classes/errors.ColumnOverflow.html +3 -3
  78. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +6 -3
  79. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  80. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  81. package/docs/classes/errors.DecryptionError.html +2 -2
  82. package/docs/classes/errors.DecryptionParseError.html +2 -2
  83. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  84. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  85. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  86. package/docs/classes/errors.InvalidClockTime.html +2 -2
  87. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  88. package/docs/classes/errors.InvalidDateTime.html +2 -2
  89. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  90. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  91. package/docs/classes/errors.NotNullViolation.html +3 -3
  92. package/docs/classes/errors.RecordNotFound.html +3 -3
  93. package/docs/classes/errors.SortableScopeDidNotStabilize.html +3 -3
  94. package/docs/classes/errors.SortableScopeLockWaitTimedOut.html +3 -3
  95. package/docs/classes/errors.ValidationError.html +3 -3
  96. package/docs/classes/index.CalendarDate.html +33 -33
  97. package/docs/classes/index.ClockTime.html +32 -32
  98. package/docs/classes/index.ClockTimeTz.html +35 -35
  99. package/docs/classes/index.DateTime.html +86 -86
  100. package/docs/classes/index.Decorators.html +19 -19
  101. package/docs/classes/index.Dream.html +204 -134
  102. package/docs/classes/index.DreamApp.html +13 -13
  103. package/docs/classes/index.DreamTransaction.html +3 -3
  104. package/docs/classes/index.Env.html +5 -5
  105. package/docs/classes/index.Query.html +147 -100
  106. package/docs/classes/system.CliFileWriter.html +4 -4
  107. package/docs/classes/system.DreamBin.html +7 -3
  108. package/docs/classes/system.DreamCLI.html +7 -7
  109. package/docs/classes/system.DreamImporter.html +2 -2
  110. package/docs/classes/system.DreamLogos.html +2 -2
  111. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  112. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  113. package/docs/classes/system.PathHelpers.html +3 -3
  114. package/docs/classes/utils.Encrypt.html +4 -4
  115. package/docs/classes/utils.Range.html +2 -2
  116. package/docs/functions/db.closeAllDbConnections.html +1 -1
  117. package/docs/functions/db.dreamDbConnections.html +1 -1
  118. package/docs/functions/db.untypedDb.html +1 -1
  119. package/docs/functions/db.validateColumn.html +1 -1
  120. package/docs/functions/db.validateTable.html +1 -1
  121. package/docs/functions/errors.pgErrorType.html +1 -1
  122. package/docs/functions/index.DreamSerializer.html +1 -1
  123. package/docs/functions/index.ObjectSerializer.html +1 -1
  124. package/docs/functions/index.ReplicaSafe.html +1 -1
  125. package/docs/functions/index.STI.html +1 -1
  126. package/docs/functions/index.SoftDelete.html +1 -1
  127. package/docs/functions/utils.camelize.html +1 -1
  128. package/docs/functions/utils.capitalize.html +1 -1
  129. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  130. package/docs/functions/utils.compact.html +1 -1
  131. package/docs/functions/utils.groupBy.html +1 -1
  132. package/docs/functions/utils.hyphenize.html +1 -1
  133. package/docs/functions/utils.intersection.html +1 -1
  134. package/docs/functions/utils.isEmpty.html +3 -3
  135. package/docs/functions/utils.normalizeUnicode.html +1 -1
  136. package/docs/functions/utils.pascalize.html +1 -1
  137. package/docs/functions/utils.percent.html +1 -1
  138. package/docs/functions/utils.range.html +1 -1
  139. package/docs/functions/utils.round.html +1 -1
  140. package/docs/functions/utils.sanitizeString.html +1 -1
  141. package/docs/functions/utils.snakeify.html +1 -1
  142. package/docs/functions/utils.sort.html +1 -1
  143. package/docs/functions/utils.sortBy.html +1 -1
  144. package/docs/functions/utils.sortObjectByKey.html +1 -1
  145. package/docs/functions/utils.sortObjectByValue.html +1 -1
  146. package/docs/functions/utils.uncapitalize.html +1 -1
  147. package/docs/functions/utils.uniq.html +1 -1
  148. package/docs/hierarchy.html +1 -1
  149. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  150. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  151. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  152. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  153. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  154. package/docs/interfaces/types.DecoratorContext.html +2 -2
  155. package/docs/interfaces/types.DecryptCallbacks.html +2 -2
  156. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  157. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  158. package/docs/interfaces/types.DreamDbConfig.html +6 -6
  159. package/docs/interfaces/types.DurationObject.html +2 -2
  160. package/docs/interfaces/types.EncryptOptions.html +2 -2
  161. package/docs/interfaces/types.EncryptionLegacyKeyUsedEvent.html +4 -4
  162. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  163. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  164. package/docs/interfaces/types.IntrospectedColumn.html +7 -7
  165. package/docs/interfaces/types.IntrospectedDatabase.html +2 -2
  166. package/docs/interfaces/types.IntrospectedEnum.html +2 -2
  167. package/docs/interfaces/types.IntrospectedTable.html +5 -5
  168. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  169. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  170. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  171. package/docs/types/openapi.OpenapiFormats.html +1 -1
  172. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  173. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  174. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  175. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  176. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  177. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  178. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  179. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  180. package/docs/types/openapi.OpenapiSchemaCombinatorFields.html +1 -1
  181. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  182. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
  183. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
  184. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
  185. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  186. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  187. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  188. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  189. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  190. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  191. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  192. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  193. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  194. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  195. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  196. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  197. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  198. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  199. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  200. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  201. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  202. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  203. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  204. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  205. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  206. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  207. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  208. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  209. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  210. package/docs/types/types.CalendarDateObject.html +1 -1
  211. package/docs/types/types.Camelized.html +1 -1
  212. package/docs/types/types.ClockTimeObject.html +1 -1
  213. package/docs/types/types.DbConnectionType.html +1 -1
  214. package/docs/types/types.DbTypes.html +1 -1
  215. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  216. package/docs/types/types.DreamAttributes.html +1 -1
  217. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  218. package/docs/types/types.DreamClassColumn.html +1 -1
  219. package/docs/types/types.DreamColumn.html +1 -1
  220. package/docs/types/types.DreamColumnNames.html +1 -1
  221. package/docs/types/types.DreamLogLevel.html +1 -1
  222. package/docs/types/types.DreamLogger.html +2 -2
  223. package/docs/types/types.DreamModelSerializerType.html +1 -1
  224. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  225. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  226. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  227. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  228. package/docs/types/types.DreamSerializable.html +1 -1
  229. package/docs/types/types.DreamSerializableArray.html +1 -1
  230. package/docs/types/types.DreamSerializerKey.html +1 -1
  231. package/docs/types/types.DreamSerializers.html +1 -1
  232. package/docs/types/types.DreamVirtualColumns.html +1 -1
  233. package/docs/types/types.DurationUnit.html +1 -1
  234. package/docs/types/types.EncryptAlgorithm.html +1 -1
  235. package/docs/types/types.HasManyStatement.html +1 -1
  236. package/docs/types/types.HasOneStatement.html +1 -1
  237. package/docs/types/types.Hyphenized.html +1 -1
  238. package/docs/types/types.IntrospectedValueType.html +1 -1
  239. package/docs/types/types.Pascalized.html +1 -1
  240. package/docs/types/types.PrimaryKeyType.html +1 -1
  241. package/docs/types/types.RoundingPrecision.html +1 -1
  242. package/docs/types/types.SerializerCasing.html +1 -1
  243. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  244. package/docs/types/types.Snakeified.html +1 -1
  245. package/docs/types/types.StrictInterface.html +1 -1
  246. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  247. package/docs/types/types.UpdateableProperties.html +1 -1
  248. package/docs/types/types.ValidationType.html +1 -1
  249. package/docs/types/types.ViewModel.html +2 -2
  250. package/docs/types/types.ViewModelClass.html +1 -1
  251. package/docs/types/types.WeekdayName.html +1 -1
  252. package/docs/types/types.WhereStatementForDream.html +1 -1
  253. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  254. package/docs/variables/index.DreamConst.html +1 -1
  255. package/docs/variables/index.ops.html +8 -4
  256. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  257. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  258. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  259. package/docs/variables/system.primaryKeyTypes.html +1 -1
  260. package/package.json +1 -1
  261. package/dist/cjs/src/helpers/cli/generateFactory.js +0 -10
  262. package/dist/cjs/src/helpers/cli/generateSerializer.js +0 -18
  263. package/dist/cjs/src/helpers/cli/generateUnitSpec.js +0 -10
  264. package/dist/esm/src/helpers/cli/generateFactory.js +0 -10
  265. package/dist/esm/src/helpers/cli/generateSerializer.js +0 -18
  266. package/dist/esm/src/helpers/cli/generateUnitSpec.js +0 -10
  267. package/dist/types/src/helpers/cli/generateFactory.d.ts +0 -5
  268. package/dist/types/src/helpers/cli/generateSerializer.d.ts +0 -9
  269. 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
- return await this.where({
382
- [this.dreamInstance['_primaryKey']]: primaryKey,
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, an exception
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').first();
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, an exception
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
- // Implementing via `limit(1).all()`, rather than the simpler `!!(await this.first())`
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 executeDatabaseQuery(kyselyQuery, 'executeTakeFirst');
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 executeDatabaseQuery(kyselyQuery, 'execute');
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 executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
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 executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
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 executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
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 executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
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 executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
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 executeDatabaseQuery(kyselyQuery, 'execute');
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 executeDatabaseQuery(kyselyQuery, 'execute')).map(singleResult => shortAliases.map(alias => singleResult[alias]));
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
- switch (pgErrorType(error)) {
819
- case COLUMN_OVERFLOW:
820
- throw new ColumnOverflow(error instanceof Error ? error : new Error('database column overflow'));
821
- case INVALID_INPUT_SYNTAX:
822
- throw new DataTypeColumnTypeMismatch(error instanceof Error ? error : new Error('database column type error'));
823
- case NOT_NULL_VIOLATION:
824
- throw new NotNullViolation(error instanceof Error ? error : new Error('not null violation'));
825
- case CHECK_VIOLATION:
826
- throw new CheckConstraintViolation(error instanceof Error ? error : new Error('check constraint violation'));
827
- }
828
- throw error;
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 executeDatabaseQuery(pageSelect, 'execute')).map(row => pageColumns.map(({ alias }) => row[alias]));
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 executeDatabaseQuery(keySelect, 'execute')).map(row => keyColumns.map(({ alias }) => row[alias]));
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 executeDatabaseQuery(kyselyQuery, 'execute');
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 executeDatabaseQuery(kyselyQuery, 'execute');
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
  *
@@ -1,7 +1,11 @@
1
+ import findOrCreateLookupStatement from './findOrCreateLookupStatement.js';
1
2
  export default async function findOrCreateBy(dreamClass, txn = null, attributes, extraOpts = {}) {
3
+ // where().first() rather than findBy: a value the database rejects is raised, never read as no match,
4
+ // so it cannot lead to creating a record that already exists
2
5
  const existingRecord = await dreamClass
3
6
  .txn(txn)
4
- .findBy(dreamClass['extractAttributesFromUpdateableProperties'](attributes));
7
+ .where(findOrCreateLookupStatement(dreamClass, attributes))
8
+ .first();
5
9
  if (existingRecord)
6
10
  return existingRecord;
7
11
  const { createWith, skipHooks } = extraOpts;