@casekit/orm 0.0.1-alpha.14 → 0.0.1-alpha.16

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 (215) hide show
  1. package/lib/orm.query.test.d.ts +2 -0
  2. package/lib/orm.query.test.d.ts.map +1 -0
  3. package/lib/orm.query.test.js +22 -0
  4. package/lib/orm.query.test.js.map +1 -0
  5. package/package.json +3 -6
  6. package/src/Connection.ts +65 -0
  7. package/src/errors.ts +18 -0
  8. package/src/index.ts +16 -0
  9. package/src/logger.ts +3 -0
  10. package/src/migrate/commands/implode.ts +46 -0
  11. package/src/migrate/index.ts +1 -0
  12. package/src/migrate/migrator.ts +24 -0
  13. package/src/migrate/sql/createExtensionsSql.test.ts +26 -0
  14. package/src/migrate/sql/createExtensionsSql.ts +16 -0
  15. package/src/migrate/sql/createForeignKeyConstraintSql.test.ts +50 -0
  16. package/src/migrate/sql/createForeignKeyConstraintSql.ts +44 -0
  17. package/src/migrate/sql/createSchemasSql.test.ts +81 -0
  18. package/src/migrate/sql/createSchemasSql.ts +15 -0
  19. package/src/migrate/sql/createTableSql.properties.ts +38 -0
  20. package/src/migrate/sql/createTableSql.test.ts +74 -0
  21. package/src/migrate/sql/createTableSql.ts +53 -0
  22. package/src/migrate/sql/createUniqueConstraintSql.ts +27 -0
  23. package/src/migrate/sql/dropSchemasSql.ts +15 -0
  24. package/src/migrate/sql/dropTableSql.ts +13 -0
  25. package/src/orm.query.test.ts +28 -0
  26. package/src/orm.ts +370 -0
  27. package/src/pull/index.ts +1 -0
  28. package/src/pull/introspect/getForeignKeys.ts +64 -0
  29. package/src/pull/introspect/getPrimaryKeys.ts +26 -0
  30. package/src/pull/introspect/getTables.ts +51 -0
  31. package/src/pull/introspect/getUniqueConstraints.ts +39 -0
  32. package/src/pull/parse/parseCreateUniqueIndexStatement.test.ts +14 -0
  33. package/src/pull/parse/parseCreateUniqueIndexStatement.ts +19 -0
  34. package/src/pull/pull.ts +78 -0
  35. package/src/pull/render/renderModel.test.ts +144 -0
  36. package/src/pull/render/renderModel.ts +141 -0
  37. package/src/pull/render/renderModelsIndex.ts +24 -0
  38. package/src/pull/render/renderRelations.ts +77 -0
  39. package/src/pull/render/renderRelationsIndex.ts +24 -0
  40. package/src/pull/types/ColumnMeta.ts +10 -0
  41. package/src/pull/types/ForeignKey.ts +6 -0
  42. package/src/pull/types/PrimaryKey.ts +6 -0
  43. package/src/pull/types/UniqueConstraint.ts +8 -0
  44. package/src/pull/util/format.ts +17 -0
  45. package/src/pull/util/quote.ts +7 -0
  46. package/src/pull/util/unquote.ts +9 -0
  47. package/src/queries/clauses/IncludeClause.ts +39 -0
  48. package/src/queries/clauses/LateralByClause.ts +4 -0
  49. package/src/queries/clauses/ReturningClause.ts +7 -0
  50. package/src/queries/clauses/SelectClause.ts +7 -0
  51. package/src/queries/clauses/WhereClause.ts +16 -0
  52. package/src/queries/clauses/helpers/OptionalColumn.ts +18 -0
  53. package/src/queries/clauses/helpers/OptionalParams.ts +14 -0
  54. package/src/queries/clauses/helpers/RequiredColumn.ts +8 -0
  55. package/src/queries/clauses/helpers/RequiredParams.ts +14 -0
  56. package/src/queries/clauses/include/IncludedRelationModel.ts +13 -0
  57. package/src/queries/clauses/include/IncludedRelationName.ts +11 -0
  58. package/src/queries/clauses/include/IncludedRelationQuery.ts +20 -0
  59. package/src/queries/clauses/where/buildWhereClause.ts +121 -0
  60. package/src/queries/clauses/where/buildWhereClauses.test.ts +145 -0
  61. package/src/queries/clauses/where/buildWhereClauses.ts +45 -0
  62. package/src/queries/clauses/where/operators.ts +13 -0
  63. package/src/queries/clauses/where/types/WhereClauseValue.ts +39 -0
  64. package/src/queries/count/buildCount.ts +77 -0
  65. package/src/queries/count/countToSql.ts +66 -0
  66. package/src/queries/count/tests/count.test.ts +35 -0
  67. package/src/queries/count/types/BaseCountParams.ts +11 -0
  68. package/src/queries/count/types/CountBuilder.ts +45 -0
  69. package/src/queries/count/types/CountParams.ts +27 -0
  70. package/src/queries/count.ts +33 -0
  71. package/src/queries/create/buildCreate.ts +67 -0
  72. package/src/queries/create/createResultSchema.ts +24 -0
  73. package/src/queries/create/createToSql.ts +44 -0
  74. package/src/queries/create/tests/createMany.varied-keys.test.ts +28 -0
  75. package/src/queries/create/tests/createOne.test-d.ts +116 -0
  76. package/src/queries/create/tests/createOne.test.ts +197 -0
  77. package/src/queries/create/types/BaseCreateManyParams.ts +7 -0
  78. package/src/queries/create/types/BaseCreateOneParams.ts +7 -0
  79. package/src/queries/create/types/CreateManyParams.ts +15 -0
  80. package/src/queries/create/types/CreateManyResult.ts +17 -0
  81. package/src/queries/create/types/CreateOneParams.ts +22 -0
  82. package/src/queries/create/types/CreateOneResult.ts +17 -0
  83. package/src/queries/createMany.ts +38 -0
  84. package/src/queries/createOne.ts +27 -0
  85. package/src/queries/delete/buildDelete.ts +56 -0
  86. package/src/queries/delete/deleteResultSchema.ts +23 -0
  87. package/src/queries/delete/deleteToSql.ts +48 -0
  88. package/src/queries/delete/tests/deleteOne.test.ts +108 -0
  89. package/src/queries/delete/types/BaseDeleteParams.ts +9 -0
  90. package/src/queries/delete/types/DeleteManyResult.ts +16 -0
  91. package/src/queries/delete/types/DeleteOneResult.ts +16 -0
  92. package/src/queries/delete/types/DeleteParams.ts +12 -0
  93. package/src/queries/deleteMany.ts +33 -0
  94. package/src/queries/deleteOne.ts +32 -0
  95. package/src/queries/find/buildFind.ts +138 -0
  96. package/src/queries/find/findResultSchema.ts +32 -0
  97. package/src/queries/find/findToSql.test.ts +123 -0
  98. package/src/queries/find/findToSql.ts +141 -0
  99. package/src/queries/find/getIncludedManyToManyRelations.ts +49 -0
  100. package/src/queries/find/getIncludedOneToManyRelations.ts +44 -0
  101. package/src/queries/find/tests/findMany.include.test.ts +107 -0
  102. package/src/queries/find/tests/findMany.limit.test-d.ts +75 -0
  103. package/src/queries/find/tests/findMany.limit.test.ts +176 -0
  104. package/src/queries/find/tests/findMany.nullable-relations.test.ts +127 -0
  105. package/src/queries/find/tests/findMany.orderBy.test-d.ts +84 -0
  106. package/src/queries/find/tests/findMany.orderBy.test.ts +184 -0
  107. package/src/queries/find/tests/findMany.select.test-d.ts +117 -0
  108. package/src/queries/find/tests/findMany.select.test.ts +188 -0
  109. package/src/queries/find/tests/findMany.too-deep.test-d.ts +154 -0
  110. package/src/queries/find/tests/findMany.where.test-d.ts +85 -0
  111. package/src/queries/find/tests/findMany.where.test.ts +76 -0
  112. package/src/queries/find/tests/middleware.find.where.test.ts +467 -0
  113. package/src/queries/find/types/BaseFindParams.ts +18 -0
  114. package/src/queries/find/types/FindBuilder.ts +73 -0
  115. package/src/queries/find/types/FindManyParams.ts +24 -0
  116. package/src/queries/find/types/FindManyResult.ts +13 -0
  117. package/src/queries/find/types/FindOneParams.ts +17 -0
  118. package/src/queries/find/types/FindOneResult.ts +50 -0
  119. package/src/queries/findMany.ts +134 -0
  120. package/src/queries/findOne.ts +30 -0
  121. package/src/queries/middleware/Middleware.ts +53 -0
  122. package/src/queries/middleware/ValuesMiddleware.ts +9 -0
  123. package/src/queries/middleware/WhereMiddleware.ts +16 -0
  124. package/src/queries/update/buildUpdate.ts +76 -0
  125. package/src/queries/update/tests/updateOne.test.ts +118 -0
  126. package/src/queries/update/types/BaseUpdateParams.ts +10 -0
  127. package/src/queries/update/types/UpdateManyResult.ts +16 -0
  128. package/src/queries/update/types/UpdateOneResult.ts +16 -0
  129. package/src/queries/update/types/UpdateParams.ts +14 -0
  130. package/src/queries/update/types/UpdateValues.ts +12 -0
  131. package/src/queries/update/updateResultSchema.ts +23 -0
  132. package/src/queries/update/updateToSql.ts +43 -0
  133. package/src/queries/updateMany.ts +33 -0
  134. package/src/queries/updateOne.ts +33 -0
  135. package/src/queries/util/hasConditions.test.ts +36 -0
  136. package/src/queries/util/hasConditions.ts +14 -0
  137. package/src/queries/util/rowToObject.test.ts +76 -0
  138. package/src/queries/util/rowToObject.ts +13 -0
  139. package/src/queries/util/tableAlias.test.ts +20 -0
  140. package/src/queries/util/tableAlias.ts +10 -0
  141. package/src/schema/populate/composeMiddleware.ts +51 -0
  142. package/src/schema/populate/populateConfiguration.ts +48 -0
  143. package/src/schema/populate/populateModel.ts +98 -0
  144. package/src/schema/populate/suggestedColumnSchema.ts +62 -0
  145. package/src/schema/types/base/BaseColumn.ts +10 -0
  146. package/src/schema/types/base/BaseConfiguration.ts +14 -0
  147. package/src/schema/types/base/BaseModel.ts +12 -0
  148. package/src/schema/types/base/BaseModels.ts +3 -0
  149. package/src/schema/types/base/BaseOrm.ts +8 -0
  150. package/src/schema/types/base/BaseRelation.ts +19 -0
  151. package/src/schema/types/base/BaseRelations.ts +3 -0
  152. package/src/schema/types/constraints/ForeignKey.ts +13 -0
  153. package/src/schema/types/constraints/UniqueConstraint.ts +9 -0
  154. package/src/schema/types/helpers/ColumnName.ts +6 -0
  155. package/src/schema/types/helpers/ColumnType.ts +23 -0
  156. package/src/schema/types/helpers/Columns.ts +3 -0
  157. package/src/schema/types/helpers/HasDefault.ts +11 -0
  158. package/src/schema/types/helpers/IsNullable.ts +7 -0
  159. package/src/schema/types/helpers/IsProvided.ts +8 -0
  160. package/src/schema/types/helpers/IsSerial.ts +10 -0
  161. package/src/schema/types/helpers/ModelName.ts +6 -0
  162. package/src/schema/types/loose/LooseColumnDefinition.ts +80 -0
  163. package/src/schema/types/loose/LooseModelDefinition.ts +47 -0
  164. package/src/schema/types/loose/LooseModelDefinitions.ts +3 -0
  165. package/src/schema/types/loose/LooseRelationDefinition.ts +10 -0
  166. package/src/schema/types/loose/LooseRelationsDefinition.ts +8 -0
  167. package/src/schema/types/loose/LooseRelationsDefinitions.ts +7 -0
  168. package/src/schema/types/postgres/DataType.ts +65 -0
  169. package/src/schema/types/relations/ManyToManyRelation.ts +15 -0
  170. package/src/schema/types/relations/ManyToOneRelation.ts +14 -0
  171. package/src/schema/types/relations/OneToManyRelation.ts +12 -0
  172. package/src/schema/types/strict/ColumnDefinition.ts +81 -0
  173. package/src/schema/types/strict/ModelDefinition.ts +47 -0
  174. package/src/schema/types/strict/ModelDefinitions.ts +3 -0
  175. package/src/schema/types/strict/RelationDefinition.ts +13 -0
  176. package/src/schema/types/strict/RelationsDefinition.ts +8 -0
  177. package/src/schema/types/strict/RelationsDefinitions.ts +7 -0
  178. package/src/schema/validate/validateConfiguration.ts +9 -0
  179. package/src/schema/validate/validateModel.ts +32 -0
  180. package/src/sql/SQLStatement.test.ts +112 -0
  181. package/src/sql/SQLStatement.ts +86 -0
  182. package/src/sql/index.ts +2 -0
  183. package/src/sql/sql.ts +47 -0
  184. package/src/test/db/index.ts +27 -0
  185. package/src/test/db/models/foo.model.ts +24 -0
  186. package/src/test/db/models/foo.relations.ts +4 -0
  187. package/src/test/db/models/post.model.ts +34 -0
  188. package/src/test/db/models/post.relations.ts +21 -0
  189. package/src/test/db/models/tenant.model.ts +14 -0
  190. package/src/test/db/models/tenant.relations.ts +22 -0
  191. package/src/test/db/models/tenantUser.model.ts +23 -0
  192. package/src/test/db/models/tenantUser.relations.ts +15 -0
  193. package/src/test/db/models/user.model.ts +29 -0
  194. package/src/test/db/models/user.relations.ts +38 -0
  195. package/src/test/db/models.ts +15 -0
  196. package/src/test/db/relations.ts +13 -0
  197. package/src/test/gen/column.ts +39 -0
  198. package/src/test/gen/index.ts +2 -0
  199. package/src/test/gen/model.ts +58 -0
  200. package/src/test/gen/sqldate.ts +8 -0
  201. package/src/test/globalSetup.ts +6 -0
  202. package/src/test/seed/index.ts +75 -0
  203. package/src/test/util/withRollback.ts +18 -0
  204. package/src/test/util/withTransaction.ts +19 -0
  205. package/src/types/ColumnName.ts +6 -0
  206. package/src/types/ColumnType.ts +20 -0
  207. package/src/types/Configuration.ts +21 -0
  208. package/src/types/ModelType.ts +20 -0
  209. package/src/types/util/DeepRequired.ts +7 -0
  210. package/src/types/util/DisallowExtraKeys.ts +5 -0
  211. package/src/types/util/NonEmptyArray.ts +1 -0
  212. package/src/types/util/Simplify.ts +8 -0
  213. package/src/util/ensureArray.ts +4 -0
  214. package/src/util/interleave.test.ts +35 -0
  215. package/src/util/interleave.ts +8 -0
@@ -0,0 +1,14 @@
1
+ import { ColumnType } from "../../../schema/types/helpers/ColumnType";
2
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
3
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
4
+ import { OptionalColumn } from "./OptionalColumn";
5
+
6
+ export type OptionalParams<
7
+ Models extends LooseModelDefinitions,
8
+ M extends ModelName<Models>,
9
+ > =
10
+ OptionalColumn<Models[M]> extends never
11
+ ? unknown
12
+ : {
13
+ [C in OptionalColumn<Models[M]>]?: ColumnType<Models, M, C>;
14
+ };
@@ -0,0 +1,8 @@
1
+ import { ColumnName } from "../../../schema/types/helpers/ColumnName";
2
+ import { LooseModelDefinition } from "../../../schema/types/loose/LooseModelDefinition";
3
+ import { OptionalColumn } from "./OptionalColumn";
4
+
5
+ export type RequiredColumn<Model extends LooseModelDefinition> = Exclude<
6
+ ColumnName<Model>,
7
+ OptionalColumn<Model>
8
+ >;
@@ -0,0 +1,14 @@
1
+ import { ColumnType } from "../../../schema/types/helpers/ColumnType";
2
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
3
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
4
+ import { RequiredColumn } from "./RequiredColumn";
5
+
6
+ export type RequiredParams<
7
+ Models extends LooseModelDefinitions,
8
+ M extends ModelName<Models>,
9
+ > =
10
+ RequiredColumn<Models[M]> extends never
11
+ ? unknown
12
+ : {
13
+ [C in RequiredColumn<Models[M]>]: ColumnType<Models, M, C>;
14
+ };
@@ -0,0 +1,13 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { LooseRelationDefinition } from "../../../schema/types/loose/LooseRelationDefinition";
4
+ import { LooseRelationsDefinitions } from "../../../schema/types/loose/LooseRelationsDefinitions";
5
+
6
+ export type IncludedRelationModel<
7
+ Models extends LooseModelDefinitions,
8
+ Relations extends LooseRelationsDefinitions<Models>,
9
+ M extends ModelName<Models>,
10
+ R extends keyof Relations[M],
11
+ > = Relations[M][R] extends LooseRelationDefinition
12
+ ? Extract<Relations[M][R]["model"], ModelName<Models>>
13
+ : never;
@@ -0,0 +1,11 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { LooseRelationsDefinitions } from "../../../schema/types/loose/LooseRelationsDefinitions";
4
+ import { FindManyParams } from "../../find/types/FindManyParams";
5
+
6
+ export type IncludedRelationName<
7
+ Models extends LooseModelDefinitions,
8
+ Relations extends LooseRelationsDefinitions<Models>,
9
+ M extends ModelName<Models>,
10
+ Q extends FindManyParams<Models, Relations, M>,
11
+ > = keyof Relations[M] & keyof Q["include"];
@@ -0,0 +1,20 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { LooseRelationsDefinitions } from "../../../schema/types/loose/LooseRelationsDefinitions";
4
+ import { FindManyParams } from "../../find/types/FindManyParams";
5
+ import { IncludedRelationModel } from "./IncludedRelationModel";
6
+
7
+ export type IncludedRelationQuery<
8
+ Models extends LooseModelDefinitions,
9
+ Relations extends LooseRelationsDefinitions<Models>,
10
+ M extends ModelName<Models>,
11
+ R extends keyof Q["include"] & keyof Relations[M],
12
+ Q extends FindManyParams<Models, Relations, M>,
13
+ > =
14
+ Q["include"][R] extends FindManyParams<
15
+ Models,
16
+ Relations,
17
+ IncludedRelationModel<Models, Relations, M, R>
18
+ >
19
+ ? Q["include"][R]
20
+ : never;
@@ -0,0 +1,121 @@
1
+ import { OrmError } from "../../../errors";
2
+ import { SQLStatement, sql } from "../../../sql";
3
+ import {
4
+ $eq,
5
+ $gt,
6
+ $gte,
7
+ $ilike,
8
+ $in,
9
+ $is,
10
+ $like,
11
+ $lt,
12
+ $lte,
13
+ $ne,
14
+ $not,
15
+ } from "./operators";
16
+
17
+ export const buildWhereClause = (
18
+ table: string,
19
+ column: string,
20
+ v: unknown,
21
+ ): SQLStatement => {
22
+ if (v === null) {
23
+ return sql`%I.%I IS NULL`.withIdentifiers(table, column);
24
+ } else if (
25
+ typeof v === "string" ||
26
+ typeof v === "number" ||
27
+ typeof v === "boolean" ||
28
+ v instanceof Date ||
29
+ Object.getOwnPropertySymbols(v).length === 0
30
+ ) {
31
+ return sql`%I.%I = ${v}`.withIdentifiers(table, column);
32
+ } else {
33
+ const clauses = Object.getOwnPropertySymbols(v).map(
34
+ (op): SQLStatement => {
35
+ const v2 = (v as Record<symbol, unknown>)[op];
36
+ switch (op) {
37
+ case $eq:
38
+ return sql`%I.%I = ${v2}`.withIdentifiers(
39
+ table,
40
+ column,
41
+ );
42
+ case $gt:
43
+ return sql`%I.%I > ${v2}`.withIdentifiers(
44
+ table,
45
+ column,
46
+ );
47
+ case $gte:
48
+ return sql`%I.%I >= ${v2}`.withIdentifiers(
49
+ table,
50
+ column,
51
+ );
52
+ case $ilike:
53
+ return sql`%I.%I ILIKE ${v2}`.withIdentifiers(
54
+ table,
55
+ column,
56
+ );
57
+ case $in:
58
+ if (!Array.isArray(v2)) {
59
+ throw new OrmError(
60
+ "Non-array passed to IN clause",
61
+ { data: { table, column, v, v2 } },
62
+ );
63
+ } else if (v2.length === 0) {
64
+ return sql`%I.%I IN (NULL)`.withIdentifiers(
65
+ table,
66
+ column,
67
+ );
68
+ } else {
69
+ const values = v2.map((v) => sql`${v}`);
70
+ return sql`%I.%I IN (${sql.splat(values)})`.withIdentifiers(
71
+ table,
72
+ column,
73
+ );
74
+ }
75
+ case $is:
76
+ return sql`%I.%I IS ${v2}`.withIdentifiers(
77
+ table,
78
+ column,
79
+ );
80
+ case $like:
81
+ return sql`%I.%I LIKE ${v2}`.withIdentifiers(
82
+ table,
83
+ column,
84
+ );
85
+ case $lt:
86
+ return sql`%I.%I < ${v2}`.withIdentifiers(
87
+ table,
88
+ column,
89
+ );
90
+ case $lte:
91
+ return sql`%I.%I <= ${v2}`.withIdentifiers(
92
+ table,
93
+ column,
94
+ );
95
+ case $ne:
96
+ return sql`%I.%I != ${v2}`.withIdentifiers(
97
+ table,
98
+ column,
99
+ );
100
+ case $not:
101
+ if (v2 !== null) {
102
+ throw new OrmError(
103
+ "Unexpected value other than NULL in NOT clause: " +
104
+ v2,
105
+ );
106
+ }
107
+ return sql`%I.%I IS NOT NULL`.withIdentifiers(
108
+ table,
109
+ column,
110
+ );
111
+ default:
112
+ throw new OrmError("Unexpected symbol in query", {
113
+ data: { table, column, op, v },
114
+ });
115
+ }
116
+ },
117
+ );
118
+
119
+ return sql.splat(clauses, " AND ");
120
+ }
121
+ };
@@ -0,0 +1,145 @@
1
+ import { describe, expect, test } from "vitest";
2
+
3
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
4
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
5
+ import { db } from "../../../test/db";
6
+ import { WhereClause } from "../types/../WhereClause";
7
+ import { buildWhereClauses } from "./buildWhereClauses";
8
+ import {
9
+ $and,
10
+ $eq,
11
+ $gt,
12
+ $gte,
13
+ $ilike,
14
+ $in,
15
+ $is,
16
+ $like,
17
+ $lt,
18
+ $lte,
19
+ $ne,
20
+ $not,
21
+ $or,
22
+ } from "./operators";
23
+
24
+ /**
25
+ * NB. The dollars in the resulting SQL render as `undefined` in the test output,
26
+ * but do generate (and test!) the SQL correctly. This is due to a limitation
27
+ * of vitest.each that I haven't figured a way round yet.
28
+ */
29
+ describe("buildWhereClauses", () => {
30
+ test.each([
31
+ [{ id: 3 }, "(a.id = $1)", [3]],
32
+ [{ id: { [$eq]: 3 } }, "(a.id = $1)", [3]],
33
+ [{ a: "foo" }, "(a.a = $1)", ["foo"]],
34
+ [
35
+ { timestamp: { [$gt]: new Date(2024, 1, 1) } },
36
+ `(a.timestamp > $1)`,
37
+ [new Date(2024, 1, 1)],
38
+ ],
39
+ [
40
+ { timestamp: { [$gte]: new Date(2024, 1, 3) } },
41
+ "(a.timestamp >= $1)",
42
+ [new Date(2024, 1, 3)],
43
+ ],
44
+ [{ bigint: { [$lt]: 47 } }, "(a.bigint < $1)", [47]],
45
+ [
46
+ { timestamp: { [$lte]: new Date(2024, 1, 3) } },
47
+ "(a.timestamp <= $1)",
48
+ [new Date(2024, 1, 3)],
49
+ ],
50
+
51
+ [{ id: 3, text: "foo" }, "(a.id = $1 AND a.text = $2)", [3, "foo"]],
52
+ [
53
+ { id: 3, text: { [$like]: "foo%" } },
54
+ "(a.id = $1 AND a.text LIKE $2)",
55
+ [3, "foo%"],
56
+ ],
57
+ [
58
+ { id: 3, text: { [$ilike]: "foo%" } },
59
+ "(a.id = $1 AND a.text ILIKE $2)",
60
+ [3, "foo%"],
61
+ ],
62
+ [
63
+ { id: 3, text: { [$ne]: "Russell" } },
64
+ "(a.id = $1 AND a.text != $2)",
65
+ [3, "Russell"],
66
+ ],
67
+ [
68
+ { id: 3, text: { [$not]: null } },
69
+ "(a.id = $1 AND a.text IS NOT NULL)",
70
+ [3],
71
+ ],
72
+ [
73
+ { id: 3, text: { [$is]: true } },
74
+ "(a.id = $1 AND a.text IS $2)",
75
+ [3, true],
76
+ ],
77
+ [
78
+ { [$and]: [{ id: 3 }, { text: "foo" }] },
79
+ "(((a.id = $1) AND (a.text = $2)))",
80
+ [3, "foo"],
81
+ ],
82
+ [
83
+ { [$or]: [{ id: 3 }, { text: "foo" }] },
84
+ "(((a.id = $1) OR (a.text = $2)))",
85
+ [3, "foo"],
86
+ ],
87
+ [
88
+ { [$not]: { [$or]: [{ text: "foo" }, { text: "bar" }] } },
89
+ "(NOT (((a.text = $1) OR (a.text = $2))))",
90
+ ["foo", "bar"],
91
+ ],
92
+ [
93
+ { text: { [$in]: ["cat", "dog", "fish"] } },
94
+ "(a.text IN ($1, $2, $3))",
95
+ ["cat", "dog", "fish"],
96
+ ],
97
+ [{ text: { [$in]: [] } }, "(a.text IN (NULL))", []],
98
+ [
99
+ {
100
+ [$not]: {
101
+ [$and]: [
102
+ { text: "foo" },
103
+ {
104
+ [$or]: [
105
+ {
106
+ [$and]: [
107
+ { text: 1, renamedColumn: 2 },
108
+ { renamedColumn: 3 },
109
+ ],
110
+ },
111
+ { [$or]: [{ text: 3 }, { renamedColumn: 5 }] },
112
+ { renamedColumn: 333 },
113
+ ],
114
+ },
115
+ ],
116
+ },
117
+ },
118
+ "(NOT (((a.text = $1) AND (((((a.text = $2 AND a.original_name = $3) AND (a.original_name = $4))) OR (((a.text = $5) OR (a.original_name = $6))) OR (a.original_name = $7))))))",
119
+ ["foo", 1, 2, 3, 3, 5, 333],
120
+ ],
121
+ [
122
+ { [$or]: [{ text: 1, renamedColumn: 2 }, { text: 3 }] },
123
+ "(((a.text = $1 AND a.original_name = $2) OR (a.text = $3)))",
124
+ [1, 2, 3],
125
+ ],
126
+ ])(
127
+ "Where clause of %s returns SQL %s with variables %s",
128
+ (
129
+ where: WhereClause<
130
+ LooseModelDefinitions,
131
+ ModelName<LooseModelDefinitions>
132
+ >,
133
+ sql: string,
134
+ values: unknown[],
135
+ ) => {
136
+ const clause = buildWhereClauses(
137
+ db.config,
138
+ { table: "foo", schema: "casekit", model: "foo", alias: "a" },
139
+ where,
140
+ );
141
+ expect(clause.text).toEqual(sql);
142
+ expect(clause.values).toEqual(values);
143
+ },
144
+ );
145
+ });
@@ -0,0 +1,45 @@
1
+ import { BaseConfiguration } from "../../../schema/types/base/BaseConfiguration";
2
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
3
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
4
+ import { SQLStatement, sql } from "../../../sql";
5
+ import { WhereClause } from "../WhereClause";
6
+ import { buildWhereClause } from "./buildWhereClause";
7
+ import { $and, $not, $or } from "./operators";
8
+
9
+ export const buildWhereClauses = (
10
+ config: BaseConfiguration,
11
+ table: { table: string; schema: string; alias: string; model: string },
12
+ where: WhereClause<LooseModelDefinitions, ModelName<LooseModelDefinitions>>,
13
+ ): SQLStatement => {
14
+ const clauses: SQLStatement[] = [];
15
+ Object.entries(where).forEach(([column, value]) => {
16
+ clauses.push(
17
+ buildWhereClause(
18
+ table.alias,
19
+ config.models[table.model]["columns"][column]["name"],
20
+ value,
21
+ ),
22
+ );
23
+ });
24
+
25
+ if (where[$and]) {
26
+ const subclauses = where[$and].map((condition) => {
27
+ return buildWhereClauses(config, table, condition);
28
+ });
29
+ clauses.push(sql`(${sql.splat(subclauses, " AND ")})`);
30
+ }
31
+
32
+ if (where[$or]) {
33
+ const subclauses = where[$or].map((condition) => {
34
+ return buildWhereClauses(config, table, condition);
35
+ });
36
+ clauses.push(sql`(${sql.splat(subclauses, " OR ")})`);
37
+ }
38
+
39
+ if (where[$not]) {
40
+ const subclauses = buildWhereClauses(config, table, where[$not]);
41
+ clauses.push(sql`NOT ${subclauses}`);
42
+ }
43
+
44
+ return sql`(${sql.splat(clauses, " AND ")})`;
45
+ };
@@ -0,0 +1,13 @@
1
+ export const $and = Symbol("and");
2
+ export const $eq = Symbol("eq");
3
+ export const $gt = Symbol("gt");
4
+ export const $gte = Symbol("gte");
5
+ export const $ilike = Symbol("ilike");
6
+ export const $in = Symbol("in");
7
+ export const $is = Symbol("is");
8
+ export const $like = Symbol("like");
9
+ export const $lt = Symbol("lt");
10
+ export const $lte = Symbol("lte");
11
+ export const $ne = Symbol("ne");
12
+ export const $not = Symbol("not");
13
+ export const $or = Symbol("or");
@@ -0,0 +1,39 @@
1
+ import { ColumnName } from "../../../../schema/types/helpers/ColumnName";
2
+ import { ColumnType } from "../../../../schema/types/helpers/ColumnType";
3
+ import { ModelName } from "../../../../schema/types/helpers/ModelName";
4
+ import { LooseModelDefinitions } from "../../../../schema/types/loose/LooseModelDefinitions";
5
+ import {
6
+ $eq,
7
+ $gt,
8
+ $gte,
9
+ $ilike,
10
+ $in,
11
+ $is,
12
+ $like,
13
+ $lt,
14
+ $lte,
15
+ $ne,
16
+ $not,
17
+ } from "../operators";
18
+
19
+ export type WhereClauseValue<
20
+ Models extends LooseModelDefinitions,
21
+ M extends ModelName<Models>,
22
+ C extends ColumnName<Models[M]>,
23
+ T = ColumnType<Models, M, C>,
24
+ > =
25
+ | ColumnType<Models, M, C>
26
+ | null
27
+ | {
28
+ [$eq]?: T;
29
+ [$ne]?: T;
30
+ [$not]?: null;
31
+ [$is]?: null | true | false | { [$not]: true | false };
32
+ [$gt]?: T;
33
+ [$gte]?: T;
34
+ [$lt]?: T;
35
+ [$lte]?: T;
36
+ [$in]?: T[];
37
+ [$like]?: string;
38
+ [$ilike]?: string;
39
+ };
@@ -0,0 +1,77 @@
1
+ import { BaseConfiguration } from "src/schema/types/base/BaseConfiguration";
2
+
3
+ import { ensureArray } from "../../util/ensureArray";
4
+ import { tableAlias } from "../util/tableAlias";
5
+ import { BaseCountParams } from "./types/BaseCountParams";
6
+ import { CountBuilder } from "./types/CountBuilder";
7
+
8
+ export const buildCount = (
9
+ config: BaseConfiguration,
10
+ m: string,
11
+ query: BaseCountParams,
12
+ path: string[] = [],
13
+ _tableIndex = 0,
14
+ ): CountBuilder => {
15
+ const builder: CountBuilder = {
16
+ table: {
17
+ table: config.models[m]["table"],
18
+ model: m,
19
+ schema: config.models[m]["schema"],
20
+ alias: tableAlias(_tableIndex++),
21
+ joins: [],
22
+ where: config.middleware.count?.where
23
+ ? config.middleware.count.where(query.where, {
24
+ config,
25
+ model: m,
26
+ })
27
+ : query.where,
28
+ },
29
+ tableIndex: _tableIndex,
30
+ };
31
+
32
+ const model = config.models[m];
33
+
34
+ const alias = builder.table.alias;
35
+
36
+ for (const [r, subquery] of Object.entries(query.include ?? {})) {
37
+ const relation = config.relations[m][r];
38
+ const joinedModel = config.models[relation.model];
39
+ if (relation.type === "N:1") {
40
+ const joinBuilder = buildCount(
41
+ config,
42
+ relation.model,
43
+ subquery!,
44
+ [...path, r],
45
+ builder.tableIndex++,
46
+ );
47
+ const joinedTable = joinBuilder.table;
48
+ builder.table.joins.push(
49
+ {
50
+ from: {
51
+ schema: config.models[m].schema,
52
+ table: config.models[m].table,
53
+ alias,
54
+ model: m,
55
+ columns: ensureArray(relation.foreignKey).map(
56
+ (c) => model.columns[c].name,
57
+ ),
58
+ },
59
+ to: {
60
+ schema: joinedModel.schema,
61
+ table: joinedTable.table,
62
+ alias: joinedTable.alias,
63
+ model: relation.model,
64
+ columns: joinedModel.primaryKey.map(
65
+ (c) => joinedModel.columns[c].name,
66
+ ),
67
+ },
68
+ where: joinedTable.where,
69
+ type: relation.optional ? "left" : "inner",
70
+ },
71
+ ...joinedTable.joins,
72
+ );
73
+ }
74
+ }
75
+
76
+ return builder;
77
+ };
@@ -0,0 +1,66 @@
1
+ import pgfmt from "pg-format";
2
+
3
+ import { OrmError } from "../../errors";
4
+ import { BaseConfiguration } from "../../schema/types/base/BaseConfiguration";
5
+ import { SQLStatement, sql } from "../../sql";
6
+ import { buildWhereClauses } from "../clauses/where/buildWhereClauses";
7
+ import { hasConditions } from "../util/hasConditions";
8
+ import { CountBuilder } from "./types/CountBuilder";
9
+
10
+ export const countToSql = (
11
+ config: BaseConfiguration,
12
+ builder: CountBuilder,
13
+ ): SQLStatement => {
14
+ const table = builder.table;
15
+
16
+ const frag = sql`SELECT count(1) as "count"`;
17
+
18
+ frag.push(pgfmt(`\nFROM %I.%I %I`, table.schema, table.table, table.alias));
19
+
20
+ for (const join of table.joins ?? []) {
21
+ if (join.from.columns.length !== join.to.columns.length) {
22
+ throw new OrmError(
23
+ "Number of foreign keys doesn't match number of primary keys in join",
24
+ { data: builder },
25
+ );
26
+ }
27
+
28
+ frag.push(join.type === "left" ? "\nLEFT JOIN" : "\nJOIN");
29
+ frag.push(
30
+ pgfmt(
31
+ "\n %I.%I %I\n ON ",
32
+ join.to.schema,
33
+ join.to.table,
34
+ join.to.alias,
35
+ ),
36
+ );
37
+ frag.push(
38
+ join.from.columns
39
+ .map((from, i) =>
40
+ pgfmt(
41
+ "%I.%I = %I.%I",
42
+ join.from.alias,
43
+ from,
44
+ join.to.alias,
45
+ join.to.columns[i],
46
+ ),
47
+ )
48
+ .join(" AND "),
49
+ );
50
+ if (hasConditions(join.where)) {
51
+ frag.push(
52
+ sql`\n AND ${buildWhereClauses(config, join.to, join.where)}`,
53
+ );
54
+ }
55
+ }
56
+
57
+ frag.push(sql`\nWHERE 1 = 1`);
58
+
59
+ if (hasConditions(table.where)) {
60
+ frag.push(
61
+ sql`\n AND ${buildWhereClauses(config, table, table.where)}`,
62
+ );
63
+ }
64
+
65
+ return frag;
66
+ };
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from "vitest";
2
+
3
+ import { db } from "../../../test/db";
4
+ import { seed } from "../../../test/seed";
5
+
6
+ describe("count", () => {
7
+ test("it counts the number of rows a query would return", async () => {
8
+ await db.transact(
9
+ async (db) => {
10
+ await seed(db, {
11
+ users: [
12
+ {
13
+ username: "Russell",
14
+ tenants: [{ name: "WFMA", posts: 3 }],
15
+ },
16
+ {
17
+ username: "Stewart Home",
18
+ tenants: [{ name: "Popova Park", posts: 2 }],
19
+ },
20
+ ],
21
+ });
22
+ const result = await db.count("post", {
23
+ include: {
24
+ author: {
25
+ where: { username: "Stewart Home" },
26
+ },
27
+ },
28
+ });
29
+
30
+ expect(result).toBe(2);
31
+ },
32
+ { rollback: true },
33
+ );
34
+ });
35
+ });
@@ -0,0 +1,11 @@
1
+ import { ModelName } from "../../../schema/types/helpers/ModelName";
2
+ import { LooseModelDefinitions } from "../../../schema/types/loose/LooseModelDefinitions";
3
+ import { WhereClause } from "../../clauses/WhereClause";
4
+
5
+ export type BaseCountParams = {
6
+ include?: Partial<Record<string, BaseCountParams>>;
7
+ where?: WhereClause<
8
+ LooseModelDefinitions,
9
+ ModelName<LooseModelDefinitions>
10
+ >;
11
+ };