@deterministic-code/datasources 0.1.0 → 0.1.1

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 (302) hide show
  1. package/dist/ArrayReader.d.ts +12 -0
  2. package/dist/BatchCommandIBulkWriteStrategy.d.ts +33 -0
  3. package/dist/BuiltRepository.d.ts +16 -0
  4. package/dist/BulkCRUDRepository.d.ts +17 -0
  5. package/dist/BulkCommand.d.ts +5 -0
  6. package/dist/BulkWriteEnrichment.d.ts +4 -0
  7. package/dist/CRUDRepository.d.ts +40 -0
  8. package/dist/CRUDRepositoryBuilder.d.ts +19 -0
  9. package/dist/CRUDRepositoryOptions.d.ts +7 -0
  10. package/dist/CRUDRepositoryStatements.d.ts +17 -0
  11. package/dist/CascadeDeleteEnrichmentStrategy.d.ts +7 -0
  12. package/dist/CascadeDeleteUpdateEnrichmentStrategy.d.ts +8 -0
  13. package/dist/CommandMiddlewareHost.d.ts +22 -0
  14. package/dist/ConsoleCommandMiddleware.d.ts +14 -0
  15. package/dist/CreateEnrichmentStrategy.d.ts +14 -0
  16. package/dist/Datasource.d.ts +30 -0
  17. package/dist/DatasourceError.d.ts +3 -0
  18. package/dist/DatasourceFactory.d.ts +12 -0
  19. package/dist/DatasourceOptions.d.ts +1 -0
  20. package/dist/DatasourceUrl.d.ts +12 -0
  21. package/dist/DatasourceUrlParser.d.ts +4 -0
  22. package/dist/DefaultCreateEnrichmentStrategy.d.ts +7 -0
  23. package/dist/DefaultReadonlyEnrichmentStrategy.d.ts +19 -0
  24. package/dist/DefaultUpdateEnrichmentStrategy.d.ts +7 -0
  25. package/dist/DefaultWriteEnrichmentStrategy.d.ts +21 -0
  26. package/dist/DeleteEnrichmentStrategy.d.ts +11 -0
  27. package/dist/DriverLoader.d.ts +5 -0
  28. package/dist/Enrichment.d.ts +13 -0
  29. package/dist/EnrichmentPropertyType.d.ts +7 -0
  30. package/dist/ExpectConsoleMiddleware.d.ts +15 -0
  31. package/dist/FirstResultReader.d.ts +19 -0
  32. package/dist/HashsetOptimizedReadonlyEnrichmentStrategy.d.ts +9 -0
  33. package/dist/IBulkWriteStrategy.d.ts +12 -0
  34. package/dist/ICRUDRepository.d.ts +12 -0
  35. package/dist/ICRUDRepositoryFactory.d.ts +7 -0
  36. package/dist/ICommand.d.ts +20 -0
  37. package/dist/ICommandMiddleware.d.ts +12 -0
  38. package/dist/IConnection.d.ts +12 -0
  39. package/dist/IDatasource.d.ts +18 -0
  40. package/dist/IDatasourceFactory.d.ts +5 -0
  41. package/dist/IDriver.d.ts +19 -0
  42. package/dist/IEnriched.d.ts +6 -0
  43. package/dist/IReader.d.ts +8 -0
  44. package/dist/IReadonlyEnrichmentStrategy.d.ts +7 -0
  45. package/dist/IReadonlyRepository.d.ts +11 -0
  46. package/dist/IRepository.d.ts +4 -0
  47. package/dist/IRepositoryFactory.d.ts +7 -0
  48. package/dist/IRepositoryTree.d.ts +4 -0
  49. package/dist/IRepositoryTreeNode.d.ts +7 -0
  50. package/dist/ITransaction.d.ts +8 -0
  51. package/dist/IUnitOfWork.d.ts +5 -0
  52. package/dist/IWriteEnrichmentStrategy.d.ts +7 -0
  53. package/dist/Information.d.ts +5 -0
  54. package/dist/IsolationLevel.d.ts +7 -0
  55. package/dist/LazyWriteEnrichmentStrategy.d.ts +4 -0
  56. package/dist/NestedToObjectEnrichmentStrategy.d.ts +10 -0
  57. package/dist/Parameter.d.ts +7 -0
  58. package/dist/ParameterDirection.d.ts +7 -0
  59. package/dist/ProviderCatalog.d.ts +7 -0
  60. package/dist/ProviderNotImplementedError.d.ts +4 -0
  61. package/dist/ProviderNotInstalledError.d.ts +4 -0
  62. package/dist/ReadonlyRepository.d.ts +59 -0
  63. package/dist/ReadonlyRepositoryBuilder.d.ts +12 -0
  64. package/dist/ReadonlyRepositoryMappingOptions.d.ts +14 -0
  65. package/dist/ReadonlyRepositoryStatements.d.ts +8 -0
  66. package/dist/RepositoryAccess.d.ts +11 -0
  67. package/dist/RepositoryFactory.d.ts +23 -0
  68. package/dist/RepositoryNotRegisteredError.d.ts +4 -0
  69. package/dist/RepositoryTree.d.ts +6 -0
  70. package/dist/RepositoryTreeNode.d.ts +9 -0
  71. package/dist/Response.d.ts +7 -0
  72. package/dist/Row.d.ts +2 -0
  73. package/dist/SingleCommandIBulkWriteStrategy.d.ts +11 -0
  74. package/dist/StatementMappers.d.ts +16 -0
  75. package/dist/StoredEnrichment.d.ts +11 -0
  76. package/dist/StoredProcedureBulkWriteStrategy.d.ts +27 -0
  77. package/dist/StreamingReader.d.ts +16 -0
  78. package/dist/UnitOfWorkTransaction.d.ts +12 -0
  79. package/dist/UpdateEnrichmentStrategy.d.ts +18 -0
  80. package/dist/WriteMode.d.ts +1 -0
  81. package/dist/builders/Field.d.ts +5 -0
  82. package/dist/builders/Scalar.d.ts +1 -0
  83. package/dist/builders/index.d.ts +2 -0
  84. package/dist/collectAsync.d.ts +1 -0
  85. package/dist/converters/Cell.d.ts +2 -0
  86. package/dist/converters/ChangeCasingConverter.d.ts +7 -0
  87. package/dist/converters/Constructable.d.ts +1 -0
  88. package/dist/converters/MetaDataRowTypeConverter.d.ts +14 -0
  89. package/dist/converters/PluralizerConverter.d.ts +4 -0
  90. package/dist/converters/RegexConverter.d.ts +7 -0
  91. package/dist/converters/ReplaceConverter.d.ts +7 -0
  92. package/dist/converters/RowTypeConverter.d.ts +10 -0
  93. package/dist/converters/ToLowerCaseConverter.d.ts +4 -0
  94. package/dist/converters/ToUpperCaseConverter.d.ts +4 -0
  95. package/dist/converters/TypeConverter.d.ts +5 -0
  96. package/dist/createCRUDRepositoryOptions.d.ts +7 -0
  97. package/dist/createReadonlyRepositoryMappingOptions.d.ts +6 -0
  98. package/dist/dotEnv.d.ts +5 -0
  99. package/dist/index.d.ts +119 -0
  100. package/dist/mappers/ChangeCasingMapper.d.ts +9 -0
  101. package/dist/mappers/DictionaryMapper.d.ts +8 -0
  102. package/dist/mappers/FlatToNestedMapper.d.ts +20 -0
  103. package/dist/mappers/IFlatToNestedMapper.d.ts +6 -0
  104. package/dist/mappers/IMapper.d.ts +4 -0
  105. package/dist/mappers/IdentityMapper.d.ts +5 -0
  106. package/dist/mappers/PluralizerMapper.d.ts +6 -0
  107. package/dist/mappers/RowTypeMapper.d.ts +6 -0
  108. package/dist/mappers/TransformConverterMapper.d.ts +5 -0
  109. package/dist/mappers/TransformMapper.d.ts +6 -0
  110. package/dist/mappers/TypeMap.d.ts +8 -0
  111. package/dist/mappers/TypeMapGroup.d.ts +5 -0
  112. package/dist/mocks/index.d.ts +9 -0
  113. package/dist/mocks/mock-command.d.ts +28 -0
  114. package/dist/mocks/mock-connection.d.ts +22 -0
  115. package/dist/mocks/mock-datasource.d.ts +24 -0
  116. package/dist/mocks/mock-driver.d.ts +15 -0
  117. package/dist/mocks/mock-reader.d.ts +14 -0
  118. package/dist/mocks/mock-repository.d.ts +42 -0
  119. package/dist/mocks/mock-select-statement.d.ts +10 -0
  120. package/dist/mocks/mock-transaction.d.ts +16 -0
  121. package/dist/mocks/mock-where.d.ts +8 -0
  122. package/dist/parametersToRecord.d.ts +2 -0
  123. package/dist/readerToAsyncIterable.d.ts +2 -0
  124. package/dist/splitStatements.d.ts +1 -0
  125. package/dist/statementParameters.d.ts +1 -0
  126. package/dist/statementUsesParameter.d.ts +1 -0
  127. package/dist/statements/Aggregate.d.ts +6 -0
  128. package/dist/statements/ArrayExpression.d.ts +5 -0
  129. package/dist/statements/BulkDeleteStatement.d.ts +11 -0
  130. package/dist/statements/BulkDeleteStatementTextRenderer.d.ts +4 -0
  131. package/dist/statements/BulkInsertStatement.d.ts +12 -0
  132. package/dist/statements/BulkInsertStatementTextRenderer.d.ts +4 -0
  133. package/dist/statements/BulkUpdateStatement.d.ts +16 -0
  134. package/dist/statements/BulkUpdateStatementTextRenderer.d.ts +8 -0
  135. package/dist/statements/CheckConstraint.d.ts +7 -0
  136. package/dist/statements/Cmp.d.ts +9 -0
  137. package/dist/statements/Column.d.ts +9 -0
  138. package/dist/statements/ColumnDefault.d.ts +5 -0
  139. package/dist/statements/Condition.d.ts +3 -0
  140. package/dist/statements/ConditionRenderer.d.ts +12 -0
  141. package/dist/statements/CreateIndexStatement.d.ts +10 -0
  142. package/dist/statements/CreateIndexStatementTextRenderer.d.ts +5 -0
  143. package/dist/statements/CreateTableStatement.d.ts +11 -0
  144. package/dist/statements/CreateTableStatementTextRenderer.d.ts +18 -0
  145. package/dist/statements/DefaultStatementTextRenderer.d.ts +5 -0
  146. package/dist/statements/DeleteStatement.d.ts +9 -0
  147. package/dist/statements/DeleteStatementTextRenderer.d.ts +7 -0
  148. package/dist/statements/ExecuteStoredProcedureStatement.d.ts +9 -0
  149. package/dist/statements/ExecuteStoredProcedureStatementTextRenderer.d.ts +4 -0
  150. package/dist/statements/ExpressionEvaluator.d.ts +5 -0
  151. package/dist/statements/ExpressionRenderer.d.ts +8 -0
  152. package/dist/statements/ForeignKey.d.ts +19 -0
  153. package/dist/statements/IBulkDeleteStatement.d.ts +3 -0
  154. package/dist/statements/IBulkInsertStatement.d.ts +3 -0
  155. package/dist/statements/IBulkUpdateStatement.d.ts +3 -0
  156. package/dist/statements/ICondition.d.ts +4 -0
  157. package/dist/statements/IConditionVisitor.d.ts +6 -0
  158. package/dist/statements/ICreateIndexStatement.d.ts +3 -0
  159. package/dist/statements/ICreateTableStatement.d.ts +3 -0
  160. package/dist/statements/IDeleteStatement.d.ts +5 -0
  161. package/dist/statements/IExecuteStoredProcedureStatement.d.ts +3 -0
  162. package/dist/statements/IExpression.d.ts +2 -0
  163. package/dist/statements/IInsertStatement.d.ts +3 -0
  164. package/dist/statements/IReturnStatement.d.ts +3 -0
  165. package/dist/statements/ISelectStatement.d.ts +5 -0
  166. package/dist/statements/IStatement.d.ts +4 -0
  167. package/dist/statements/IStatementVisitor.d.ts +26 -0
  168. package/dist/statements/ITableConstraint.d.ts +5 -0
  169. package/dist/statements/IUpdateStatement.d.ts +5 -0
  170. package/dist/statements/IWhere.d.ts +6 -0
  171. package/dist/statements/Identifier.d.ts +5 -0
  172. package/dist/statements/InsertStatement.d.ts +9 -0
  173. package/dist/statements/InsertStatementTextRenderer.d.ts +4 -0
  174. package/dist/statements/Join.d.ts +14 -0
  175. package/dist/statements/Literal.d.ts +6 -0
  176. package/dist/statements/Native.d.ts +5 -0
  177. package/dist/statements/NotNull.d.ts +4 -0
  178. package/dist/statements/Operator.d.ts +107 -0
  179. package/dist/statements/OrderBy.d.ts +10 -0
  180. package/dist/statements/PrimaryKey.d.ts +7 -0
  181. package/dist/statements/ReturnStatement.d.ts +9 -0
  182. package/dist/statements/ReturnStatementTextRenderer.d.ts +4 -0
  183. package/dist/statements/SelectInsertedIdStatement.d.ts +8 -0
  184. package/dist/statements/SelectInsertedIdStatementTextRenderer.d.ts +4 -0
  185. package/dist/statements/SelectStatement.d.ts +20 -0
  186. package/dist/statements/SelectStatementOptions.d.ts +15 -0
  187. package/dist/statements/SelectStatementTextRenderer.d.ts +19 -0
  188. package/dist/statements/StatementEvaluator.d.ts +4 -0
  189. package/dist/statements/StatementParser.d.ts +4 -0
  190. package/dist/statements/StatementRenderer.d.ts +42 -0
  191. package/dist/statements/StatementTextRenderer.d.ts +14 -0
  192. package/dist/statements/Subquery.d.ts +6 -0
  193. package/dist/statements/UniqueConstraint.d.ts +7 -0
  194. package/dist/statements/UpdateStatement.d.ts +14 -0
  195. package/dist/statements/UpdateStatementTextRenderer.d.ts +14 -0
  196. package/dist/statements/Where.d.ts +16 -0
  197. package/dist/statements/WhereText.d.ts +8 -0
  198. package/dist/statements/index.d.ts +75 -0
  199. package/dist/stubs/StubCommand.d.ts +15 -0
  200. package/dist/stubs/StubConnection.d.ts +13 -0
  201. package/dist/stubs/StubDatasource.d.ts +20 -0
  202. package/dist/stubs/StubReader.d.ts +11 -0
  203. package/dist/stubs/StubRepository.d.ts +22 -0
  204. package/dist/stubs/StubTransaction.d.ts +10 -0
  205. package/dist/stubs/createStubDatasource.d.ts +2 -0
  206. package/dist/stubs/index.d.ts +7 -0
  207. package/dist/stubs/notImplemented.d.ts +1 -0
  208. package/package.json +14 -6
  209. package/src/ArrayReader.test.ts +23 -0
  210. package/src/BulkCRUDRepository.test.ts +183 -0
  211. package/src/BulkWriteStrategy.test.ts +293 -0
  212. package/src/CRUDRepository.test.ts +420 -0
  213. package/src/CRUDRepository.ts +1 -1
  214. package/src/CRUDRepositoryOptions.test.ts +76 -0
  215. package/src/Command.test.ts +105 -0
  216. package/src/CommandMiddlewareHost.test.ts +98 -0
  217. package/src/Connection.test.ts +34 -0
  218. package/src/ConsoleCommandMiddleware.test.ts +149 -0
  219. package/src/Datasource.test.ts +63 -0
  220. package/src/DatasourceError.test.ts +10 -0
  221. package/src/DatasourceFactory.test.ts +159 -0
  222. package/src/DatasourceUrlParser.test.ts +173 -0
  223. package/src/ExpectConsoleMiddleware.test.ts +79 -0
  224. package/src/FirstResultReader.test.ts +43 -0
  225. package/src/IsolationLevel.test.ts +13 -0
  226. package/src/ProviderCatalog.test.ts +110 -0
  227. package/src/ProviderNotImplementedError.test.ts +11 -0
  228. package/src/ProviderNotInstalledError.test.ts +11 -0
  229. package/src/ReadonlyRepository.test.ts +406 -0
  230. package/src/RepositoryFactory.test.ts +94 -0
  231. package/src/StatementMappers.test.ts +221 -0
  232. package/src/StreamingReader.test.ts +126 -0
  233. package/src/Transaction.test.ts +37 -0
  234. package/src/UnitOfWorkTransaction.test.ts +43 -0
  235. package/src/UserViewCRUDRepository.test.ts +323 -0
  236. package/src/builders/builders.test.ts +404 -0
  237. package/src/converters/ChangeCasingConverter.test.ts +34 -0
  238. package/src/converters/PluralizerConverter.test.ts +43 -0
  239. package/src/converters/RegexConverter.test.ts +11 -0
  240. package/src/converters/ReplaceConverter.test.ts +9 -0
  241. package/src/converters/RowTypeConverter.test.ts +79 -0
  242. package/src/converters/ToLowerCaseConverter.test.ts +9 -0
  243. package/src/converters/ToUpperCaseConverter.test.ts +9 -0
  244. package/src/dotEnv.test.ts +21 -0
  245. package/src/edgeCases.test.ts +485 -0
  246. package/src/mappers/ChangeCasingMapper.test.ts +32 -0
  247. package/src/mappers/DictionaryMapper.test.ts +69 -0
  248. package/src/mappers/FlatToNestedMapper.test.ts +269 -0
  249. package/src/mappers/IdentityMapper.test.ts +12 -0
  250. package/src/mappers/PluralizerMapper.test.ts +40 -0
  251. package/src/mappers/RowTypeMapper.test.ts +12 -0
  252. package/src/mappers/TransformConverterMapper.test.ts +181 -0
  253. package/src/mappers/transformMapper.test.ts +66 -0
  254. package/src/mocks/MockCommand.test.ts +75 -0
  255. package/src/mocks/MockConnection.test.ts +34 -0
  256. package/src/mocks/MockDatasource.test.ts +40 -0
  257. package/src/mocks/MockReader.test.ts +17 -0
  258. package/src/mocks/MockRepository.test.ts +32 -0
  259. package/src/mocks/MockSelectStatement.test.ts +11 -0
  260. package/src/mocks/MockTransaction.test.ts +17 -0
  261. package/src/mocks/MockWhere.test.ts +11 -0
  262. package/src/parametersToRecord.test.ts +19 -0
  263. package/src/public-surface.test.ts +23 -0
  264. package/src/readWithWhereClauseAndCustomFlatToNestedObjectMappingOptions.test.ts +126 -0
  265. package/src/readerToAsyncIterable.test.ts +39 -0
  266. package/src/repository.test.ts +410 -0
  267. package/src/repository.type.test.ts +29 -0
  268. package/src/statements/SelectInsertedIdStatement.test.ts +11 -0
  269. package/src/statements/StatementCompile.test.ts +231 -0
  270. package/src/statements/StatementParser.test.ts +304 -0
  271. package/src/statements/index.ts +1 -1
  272. package/src/stubs/StubDatasource.test.ts +99 -0
  273. package/src/tests/contact/Address.ts +14 -0
  274. package/src/tests/contact/AddressType.ts +6 -0
  275. package/src/tests/contact/Base.ts +7 -0
  276. package/src/tests/contact/Contact.ts +12 -0
  277. package/src/tests/contact/ContactBase.ts +8 -0
  278. package/src/tests/contact/ContactChangeLog.ts +8 -0
  279. package/src/tests/contact/ContactGroup.ts +6 -0
  280. package/src/tests/contact/ContactGroupBase.ts +6 -0
  281. package/src/tests/contact/ContactGroupMember.ts +6 -0
  282. package/src/tests/contact/ContactSource.ts +6 -0
  283. package/src/tests/contact/ContactType.ts +6 -0
  284. package/src/tests/contact/ContactsService.test.ts +118 -0
  285. package/src/tests/contact/ContactsService.ts +32 -0
  286. package/src/tests/contact/ImportContactRequest.ts +4 -0
  287. package/src/tests/contact/ImportContactResult.ts +5 -0
  288. package/src/tests/contact/LegacyContact.ts +7 -0
  289. package/src/tests/contact/LegacyMigrationResult.ts +5 -0
  290. package/src/tests/contact/Phone.ts +9 -0
  291. package/src/tests/contact/PhoneType.ts +6 -0
  292. package/src/tests/contact/asContactsView.ts +63 -0
  293. package/src/tests/contact/contactColumns.ts +40 -0
  294. package/src/tests/contact/contactLookupIds.ts +32 -0
  295. package/src/tests/contact/contactLookups.ts +6 -0
  296. package/src/tests/contact/contactsParity.ts +63 -0
  297. package/src/tests/contact/contactsServiceOf.ts +38 -0
  298. package/src/tests/contact/contactsView.ts +31 -0
  299. package/src/tests/contact/expectContactsView.ts +23 -0
  300. package/src/tests/contact/extraContact.ts +61 -0
  301. package/src/tests/contact/logMapped.ts +5 -0
  302. package/src/tests/contact/seedContacts.ts +31 -0
@@ -0,0 +1,410 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ CRUDRepository,
4
+ DefaultStatementTextRenderer,
5
+ ExecuteStoredProcedureStatement,
6
+ SelectStatement,
7
+ MetaDataRowTypeConverter,
8
+ TransformMapper,
9
+ WhereText,
10
+ Where,
11
+ Operator,
12
+ CRUDRepositoryBuilder,
13
+ ReadonlyRepositoryBuilder,
14
+ createCRUDRepositoryOptions,
15
+ } from "./index.ts";
16
+ import { MockCommand } from "./mocks/mock-command.ts";
17
+ import { MockConnection } from "./mocks/mock-connection.ts";
18
+ import { MockReader } from "./mocks/mock-reader.ts";
19
+ import type { Cell, Cells } from "./converters/Cell.ts";
20
+ import { TypeConverter } from "./converters/TypeConverter.ts";
21
+ import type { Constructable } from "./converters/Constructable.ts";
22
+ import type { CRUDRepositoryOptions } from "./CRUDRepositoryOptions.ts";
23
+
24
+ class Item {
25
+ id = 0;
26
+ name = "";
27
+ }
28
+
29
+ class Label {
30
+ name = "";
31
+ }
32
+
33
+ class PascalId {
34
+ Id = 0;
35
+ }
36
+
37
+ class Composite {
38
+ org_id = 0;
39
+ user_id = 0;
40
+ name = "";
41
+ }
42
+
43
+ const asType = <T>(type: new () => T) => type as Constructable<Cells>;
44
+
45
+ const create = (overrides: Partial<CRUDRepositoryOptions<Cells>> = {}) => {
46
+ const connection = new MockConnection(undefined, "memory");
47
+ const command = new MockCommand(connection);
48
+ connection.command = command;
49
+ const type = overrides.type ?? asType(Item);
50
+ const options = {
51
+ ...overrides,
52
+ type,
53
+ entity: overrides.entity ?? "users",
54
+ objectConverter: overrides.objectConverter ?? new MetaDataRowTypeConverter(),
55
+ };
56
+ const repo = new CRUDRepository<Cells>(connection, options);
57
+ return { repo, command };
58
+ };
59
+
60
+ describe("CRUDRepository", () => {
61
+ it("builds select and maps rows", async () => {
62
+ const { repo, command } = create();
63
+ repo.addMiddleware({ beforeCommand() {}, afterCommand() {} });
64
+ command.reader = new MockReader([
65
+ { id: 1, name: "a" },
66
+ ]);
67
+ const rows: Cells[] = [];
68
+ for await (const row of repo.read()) rows.push(row);
69
+ expect(rows).toEqual([{ id: 1, name: "a" }]);
70
+ expect(command.executeReaderCalls[0]?.text).toBe("SELECT id, name FROM users");
71
+ command.reader = new MockReader([{ id: 1, name: "a" }]);
72
+ expect(await repo.readSingle()).toEqual({ id: 1, name: "a" });
73
+ });
74
+
75
+ it("uses caller query and returns empty", async () => {
76
+ const { repo, command } = create();
77
+ command.reader = new MockReader([]);
78
+ const where = new Where(new Operator.Equals("id", 9));
79
+ expect(
80
+ await repo.readSingle(new SelectStatement({ table: "users", fields: ["id"], where })),
81
+ ).toBeUndefined();
82
+ expect(command.executeReaderCalls[0]?.text).toBe(
83
+ "SELECT id FROM users WHERE id = $id",
84
+ );
85
+ expect(command.executeReaderCalls[0]?.parameters).toEqual([{ name: "id", value: 9 }]);
86
+ });
87
+
88
+ it("attaches where to the default select", async () => {
89
+ const { repo, command } = create();
90
+ command.reader = new MockReader([]);
91
+ expect(await repo.readSingle(new Where(new Operator.Equals("id", 9)))).toBeUndefined();
92
+ expect(command.executeReaderCalls[0]?.text).toBe(
93
+ "SELECT id, name FROM users WHERE id = $id",
94
+ );
95
+ expect(command.executeReaderCalls[0]?.parameters).toEqual([{ name: "id", value: 9 }]);
96
+ });
97
+
98
+ it("calls a stored procedure and maps rows", async () => {
99
+ const { repo, command } = create();
100
+ command.reader = new MockReader([{ id: 1, name: "a" }]);
101
+ const parameters = [{ name: "id", value: 9 }];
102
+ const call = new ExecuteStoredProcedureStatement("get_user", parameters);
103
+ const rows: Cells[] = [];
104
+ for await (const row of repo.read(call, parameters)) rows.push(row);
105
+ expect(rows).toEqual([{ id: 1, name: "a" }]);
106
+ expect(command.executeReaderCalls[0]?.text).toBe("CALL get_user($id)");
107
+ expect(command.executeReaderCalls[0]?.parameters).toEqual([{ name: "id", value: 9 }]);
108
+ command.reader = new MockReader([{ id: 1, name: "a" }]);
109
+ expect(await repo.readSingle(call, parameters)).toEqual({ id: 1, name: "a" });
110
+ });
111
+
112
+ it("adds updates and deletes through builders", async () => {
113
+ const { repo, command } = create();
114
+ command.response = { result: undefined, rowsAffected: 1 };
115
+ command.reader = new MockReader([{ id: 9 }]);
116
+ const obj = { id: 0, name: "a" };
117
+ const created = await repo.create(obj);
118
+ expect(command.executeCalls[0]?.text).toBe(
119
+ "INSERT INTO users (name) VALUES ($name)",
120
+ );
121
+ expect(command.executeReaderCalls[0]?.text).toBe("users.id");
122
+ expect(created.id).toBe(9);
123
+ const updated = await repo.update(obj);
124
+ expect(updated.rowsAffected).toBe(1);
125
+ expect(command.executeCalls[1]?.text).toBe(
126
+ "UPDATE users SET name = $name WHERE id = $id",
127
+ );
128
+ const removed = await repo.delete(obj);
129
+ expect(removed.rowsAffected).toBe(1);
130
+ expect(command.executeCalls[2]?.text).toBe("DELETE FROM users WHERE id = $id");
131
+ });
132
+
133
+ it("includes an assigned id on create", async () => {
134
+ const { repo, command } = create();
135
+ command.response = { result: undefined, rowsAffected: 1 };
136
+ const created = await repo.create({ id: 42, name: "ada" });
137
+ expect(command.executeCalls[0]?.text).toBe("INSERT INTO users (id, name) VALUES ($id, $name)");
138
+ expect(command.executeReaderCalls).toEqual([]);
139
+ expect(created.id).toBe(42);
140
+ });
141
+
142
+ it("maps create result onto Id when id is absent", async () => {
143
+ const { repo, command } = create({ type: asType(PascalId), ids: ["Id"] });
144
+ command.response = { result: undefined, rowsAffected: 1 };
145
+ command.reader = new MockReader([{ Id: 9 }]);
146
+ const item = Object.assign(new PascalId(), { Id: 0 });
147
+ const created = await repo.create(item);
148
+ expect(created.Id).toBe(9);
149
+ });
150
+
151
+ it("updates without id columns using a tautology", async () => {
152
+ const { repo, command } = create({ ids: [] });
153
+ command.response = { result: undefined, rowsAffected: 1 };
154
+ const updated = await repo.update({ id: 1, name: "a" });
155
+ expect(updated.rowsAffected).toBe(1);
156
+ expect(command.executeCalls[0]?.text).toContain("1 = 1");
157
+ await expect(repo.readSingleById(1)).rejects.toThrow("ReadSingleById requires an Id column");
158
+ const removed = await repo.delete({ id: 1, name: "a" });
159
+ expect(removed.rowsAffected).toBe(1);
160
+ expect(command.executeCalls[1]?.parameters).toEqual([]);
161
+ const removedByWhere = await repo.delete({ id: 1, name: "a" }, new WhereText("id = $id"));
162
+ expect(removedByWhere.rowsAffected).toBe(1);
163
+ expect(command.executeCalls[2]?.parameters).toEqual([
164
+ { name: "id", value: 1 },
165
+ { name: "name", value: "a" },
166
+ ]);
167
+ const versioned = create();
168
+ versioned.command.response = { result: undefined, rowsAffected: 1 };
169
+ const versionedUpdate = await versioned.repo.update(
170
+ { id: 1, name: "b", name_extra: "x" },
171
+ new Where(new Operator.And(
172
+ new Operator.Equals("name_extra", "x"),
173
+ new Operator.Equals("name", "a"),
174
+ )),
175
+ );
176
+ expect(versionedUpdate.rowsAffected).toBe(1);
177
+ expect(versioned.command.executeCalls[0]?.text).toContain("name = $name__where");
178
+ expect(versioned.command.executeCalls[0]?.text).toContain("name_extra = $name_extra");
179
+ const versionedWhere = await versioned.repo.update({ id: 1, name: "b" }, {
180
+ accept: () => "name = $name",
181
+ parameters: [{ name: "skipped" }, { name: "name", value: "a" }],
182
+ });
183
+ expect(versionedWhere.rowsAffected).toBe(1);
184
+ expect(versioned.command.executeCalls[1]?.text).toContain("name = $name__where");
185
+ });
186
+
187
+ it("includes assigned composite ids on create", async () => {
188
+ const { repo, command } = create({
189
+ ids: ["org_id", "user_id"],
190
+ type: asType(Composite),
191
+ });
192
+ const created = await repo.create({ org_id: 1, user_id: 2, name: 3 });
193
+ expect(created.org_id).toBe(1);
194
+ expect(created.user_id).toBe(2);
195
+ expect(command.executeCalls[0]?.text).toBe(
196
+ "INSERT INTO users (name, org_id, user_id) VALUES ($name, $org_id, $user_id)",
197
+ );
198
+ expect(command.executeCalls[0]?.parameters?.map((field) => field.value)).toEqual([3, 1, 2]);
199
+ command.response = { result: undefined, rowsAffected: 1 };
200
+ const updated = await repo.update({ org_id: 1, user_id: 2, name: 4 });
201
+ expect(updated.rowsAffected).toBe(1);
202
+ expect(command.executeCalls[1]?.text).toContain(
203
+ "org_id = $org_id AND user_id = $user_id",
204
+ );
205
+ });
206
+
207
+ it("omits null datetime and version on create and update", async () => {
208
+ const { repo, command } = create();
209
+ command.response = { result: undefined, rowsAffected: 1 };
210
+ command.reader = new MockReader([{ id: 7 }, { id: 7 }]);
211
+ const created = await repo.create({ id: 0, name: "ada", created: null, version: undefined });
212
+ expect(created.id).toBe(7);
213
+ expect(command.executeCalls[0]?.text).toBe("INSERT INTO users (name) VALUES ($name)");
214
+ const updated = await repo.update({ id: 1, name: "ada", created: null, version: null });
215
+ expect(updated.rowsAffected).toBe(1);
216
+ expect(command.executeCalls[1]?.text).toBe(
217
+ "UPDATE users SET name = $name WHERE id = $id",
218
+ );
219
+ const when = new Date("2024-06-01T00:00:00.000Z");
220
+ const grace = await repo.create({ id: 0, name: "grace", created: when, version: new Uint8Array([9, 9]) });
221
+ expect(grace.id).toBe(7);
222
+ expect(command.executeCalls[2]?.text).toBe(
223
+ "INSERT INTO users (created, name, version) VALUES ($created, $name, $version)",
224
+ );
225
+ });
226
+
227
+ it("reads a single row by id columns", async () => {
228
+ const { repo, command } = create();
229
+ command.reader = new MockReader([]);
230
+ expect(await repo.readSingleById(9)).toBeUndefined();
231
+ expect(command.executeReaderCalls[0]?.text).toBe(
232
+ "SELECT id, name FROM users WHERE id = $id",
233
+ );
234
+ expect(command.executeReaderCalls[0]?.parameters).toEqual([{ name: "id", value: 9 }]);
235
+
236
+ const composite = create({ ids: ["org_id", "user_id"], type: asType(Composite) });
237
+ composite.command.reader = new MockReader([]);
238
+ expect(await composite.repo.readSingleById({ org_id: 1, user_id: 2 })).toBeUndefined();
239
+ expect(composite.command.executeReaderCalls[0]?.text).toContain(
240
+ "org_id = $org_id AND user_id = $user_id",
241
+ );
242
+ await expect(composite.repo.readSingleById(1)).rejects.toThrow(
243
+ "Composite id requires a row keyed by each Id column",
244
+ );
245
+ await expect(composite.repo.readSingleById({ org_id: 1 })).rejects.toThrow(
246
+ "Missing Id value",
247
+ );
248
+ });
249
+
250
+ it("throws on missing id, bad rows, and empty update", async () => {
251
+ const connection = new MockConnection();
252
+ const command = new MockCommand(connection);
253
+ connection.command = command;
254
+ const { repo, command: query } = create();
255
+ query.reader = {
256
+ current: undefined,
257
+ next: async () => true,
258
+ nextResult: async () => false,
259
+ async *read() {
260
+ while (await this.next()) {
261
+ if (this.current !== undefined) yield this.current;
262
+ }
263
+ },
264
+ dispose: async () => {},
265
+ };
266
+ await expect(async () => {
267
+ for await (const _ of repo.read()) {
268
+ /* consume */
269
+ }
270
+ }).rejects.toThrow("Reader current must be a row");
271
+ await expect(repo.update({ name: "a" })).rejects.toThrow("Missing Id value");
272
+ await expect(repo.delete({ name: "a" })).rejects.toThrow("Missing Id value");
273
+ await expect(repo.update({ id: 1 })).rejects.toThrow("non-Id field");
274
+ query.reader = {
275
+ current: undefined,
276
+ next: async () => true,
277
+ nextResult: async () => false,
278
+ async *read() {
279
+ while (await this.next()) {
280
+ if (this.current !== undefined) yield this.current;
281
+ }
282
+ },
283
+ dispose: async () => {},
284
+ };
285
+ await expect(async () => {
286
+ for await (const _ of repo.read()) {
287
+ /* consume */
288
+ }
289
+ }).rejects.toThrow("Reader current must be a row");
290
+ query.response = { result: undefined, rowsAffected: 1 };
291
+ const updated = await repo.update({ id: 1, name: "a" }, new WhereText("id = 1"));
292
+ expect(updated.rowsAffected).toBe(1);
293
+ const typed = new CRUDRepository(connection, {
294
+ type: asType(Item),
295
+ entity: "users",
296
+ objectConverter: new MetaDataRowTypeConverter(),
297
+ });
298
+ command.response = { result: undefined, rowsAffected: 1 };
299
+ const created = await typed.create(Object.assign(new Item(), { id: 1, name: "a" }));
300
+ expect(created.id).toBe(1);
301
+ expect(command.executeCalls[0]?.text).toContain("INSERT INTO users");
302
+ const removed = await typed.delete(Object.assign(new Item(), { name: "z" }), new Where(new Operator.Equals("name", "z")));
303
+ expect(removed.rowsAffected).toBe(1);
304
+ expect(command.executeCalls[1]?.text).toBe("DELETE FROM users WHERE name = $name");
305
+ });
306
+
307
+ it("builds repository options from a type", () => {
308
+ const connection = new MockConnection();
309
+ const fromId = createCRUDRepositoryOptions(asType(Item));
310
+ expect(fromId.ids).toEqual(["id"]);
311
+ expect(fromId.entity).toBe("Item");
312
+ expect(createCRUDRepositoryOptions(asType(Label)).ids).toEqual([]);
313
+ expect(createCRUDRepositoryOptions(asType(PascalId)).ids).toEqual(["Id"]);
314
+ expect(new CRUDRepository(connection, fromId)).toBeInstanceOf(CRUDRepository);
315
+ expect(new CRUDRepository(asType(Item), connection)).toBeInstanceOf(CRUDRepository);
316
+ expect(new CRUDRepositoryBuilder().parts(connection, fromId).connection).toBe(connection);
317
+ expect(new CRUDRepositoryBuilder().parts(connection, createCRUDRepositoryOptions(asType(Item))).ids).toEqual(["id"]);
318
+ expect(DefaultStatementTextRenderer.instance.render(new ReadonlyRepositoryBuilder().parts(connection, {}).statements.select())).toBe("SELECT * FROM ");
319
+ expect(DefaultStatementTextRenderer.instance.render(new ReadonlyRepositoryBuilder().parts(connection, { type: asType(Item) }).statements.select())).toBe(
320
+ "SELECT id, name FROM Item",
321
+ );
322
+ });
323
+
324
+ it("builder maps entity and columns", async () => {
325
+ const entity = new TransformMapper<string, string>(
326
+ (name) => (name === "users" ? "people" : name),
327
+ (name) => name,
328
+ );
329
+ const propertyMappings = new TransformMapper<string, string>(
330
+ (name) => ({ id: "user_id", name: "full_name" }[name] ?? name),
331
+ (name) => name,
332
+ );
333
+ const { repo, command } = create({
334
+ entityMapper: entity,
335
+ propertyMappings,
336
+ });
337
+ command.reader = new MockReader([]);
338
+ await repo.readSingle();
339
+ expect(command.executeReaderCalls[0]?.text).toBe(
340
+ "SELECT full_name, user_id FROM people",
341
+ );
342
+ command.response = { result: undefined, rowsAffected: 1 };
343
+ const row = { id: 0, name: "a" };
344
+ const created = await repo.create(row);
345
+ expect(created.id).toBe(0);
346
+ expect(command.executeCalls[0]?.text).toBe(
347
+ "INSERT INTO people (full_name) VALUES ($full_name)",
348
+ );
349
+ const updated = await repo.update(row);
350
+ expect(updated.rowsAffected).toBe(1);
351
+ expect(command.executeCalls[1]?.text).toBe(
352
+ "UPDATE people SET full_name = $full_name WHERE user_id = $user_id",
353
+ );
354
+ const removed = await repo.delete(row);
355
+ expect(removed.rowsAffected).toBe(1);
356
+ expect(command.executeCalls[2]?.text).toBe(
357
+ "DELETE FROM people WHERE user_id = $user_id",
358
+ );
359
+ });
360
+
361
+ it("reverse-converts a field only before values are applied", async () => {
362
+ class Flag extends TypeConverter<Cell, Cell> {
363
+ convert = (value: Cell): Cell => value;
364
+ reverseConvert = (value: Cell): Cell => (value === true ? 1 : 0);
365
+ }
366
+ class Row {
367
+ id = 0;
368
+ on = false;
369
+ }
370
+ const connection = new MockConnection();
371
+ const command = new MockCommand(connection);
372
+ connection.command = command;
373
+ const repo = new CRUDRepository<Row>(connection, {
374
+ type: Row,
375
+ entity: "rows",
376
+ objectConverter: new MetaDataRowTypeConverter<Row>({ on: new Flag() }),
377
+ });
378
+ command.response = { result: undefined, rowsAffected: 1 };
379
+ const created = await repo.create(Object.assign(new Row(), { id: 1, on: true }));
380
+ expect(created.id).toBe(1);
381
+ const updated = await repo.update(
382
+ Object.assign(new Row(), { id: 1, on: true }),
383
+ new Where(new Operator.Equals("on", true)),
384
+ );
385
+ expect(updated.rowsAffected).toBe(1);
386
+ expect(command.executeCalls[1]?.parameters).toContainEqual({ name: "on", value: 1 });
387
+ });
388
+
389
+ it("keeps a where value when the converter has no fields", async () => {
390
+ class Plain extends TypeConverter<Item, Cells> {
391
+ convert = (row: Cells): Item => Object.assign(new Item(), row);
392
+ reverseConvert = (row: Item): Cells => ({ id: row.id, name: row.name });
393
+ }
394
+ const connection = new MockConnection();
395
+ const command = new MockCommand(connection);
396
+ connection.command = command;
397
+ const repo = new CRUDRepository<Item>(connection, {
398
+ type: Item,
399
+ entity: "items",
400
+ objectConverter: new Plain(),
401
+ });
402
+ command.response = { result: undefined, rowsAffected: 1 };
403
+ const updated = await repo.update(
404
+ Object.assign(new Item(), { id: 1, name: "a" }),
405
+ new Where(new Operator.Equals("name", "a")),
406
+ );
407
+ expect(updated.rowsAffected).toBe(1);
408
+ expect(command.executeCalls[0]?.parameters).toContainEqual({ name: "name", value: "a" });
409
+ });
410
+ });
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { MemoryDriver } from "@datasources/memory";
3
+ import { MockConnection } from "./mocks/mock-connection.ts";
4
+ import { CRUDRepository } from "./CRUDRepository.ts";
5
+ import { ReadonlyRepository } from "./ReadonlyRepository.ts";
6
+
7
+ class Item {
8
+ id = 0;
9
+ }
10
+
11
+ describe("repository type", () => {
12
+ it("reports constructor from typed construction", () => {
13
+ const repo = new CRUDRepository(Item, new MockConnection());
14
+ expect(repo.type).toBe(Item);
15
+ expect(new ReadonlyRepository(Item, new MockConnection()).type).toBe(Item);
16
+ });
17
+
18
+ it("crud repository reports item type", () => {
19
+ const repo = new CRUDRepository(Item, new MemoryDriver().connection());
20
+ expect(repo.type).toBe(Item);
21
+ });
22
+
23
+ it("untyped options report Object", () => {
24
+ const repo = new CRUDRepository(new MockConnection(), {
25
+ entity: "items",
26
+ });
27
+ expect(repo.type).toBe(Object);
28
+ });
29
+ });
@@ -0,0 +1,11 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { DefaultStatementTextRenderer, SelectInsertedIdStatement } from "../index.ts";
3
+
4
+ describe("SelectInsertedIdStatement", () => {
5
+ it("requires a provider implementation", () => {
6
+ const statement = new SelectInsertedIdStatement("users", "id");
7
+ expect(() => DefaultStatementTextRenderer.instance.render(statement)).toThrow(
8
+ "provider must implement SelectInsertIdStatement",
9
+ );
10
+ });
11
+ });
@@ -0,0 +1,231 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ Aggregate,
4
+ CheckConstraint,
5
+ ColumnDefault,
6
+ CreateIndexStatement,
7
+ CreateTableStatement,
8
+ DeleteStatement,
9
+ ForeignKey,
10
+ InsertStatement,
11
+ Join,
12
+ NotNull,
13
+ Operator,
14
+ OrderBy,
15
+ PrimaryKey,
16
+ SelectStatement,
17
+ ReferentialAction,
18
+ ReturnStatement,
19
+ UniqueConstraint,
20
+ ExecuteStoredProcedureStatement,
21
+ UpdateStatement,
22
+ WhereText,
23
+ Where,
24
+ DefaultStatementTextRenderer,
25
+ } from "../index.ts";
26
+
27
+ describe("statement compile", () => {
28
+ it("wraps query text and an optional where", () => {
29
+ const select = new SelectStatement({ table: "dual", fields: ["1"] });
30
+ const byId = new WhereText("id = 1");
31
+ const withWhere = new SelectStatement({ table: "dual", fields: ["1"], where: byId });
32
+ expect(DefaultStatementTextRenderer.instance.render(select)).toBe("SELECT 1 FROM dual");
33
+ expect(select.where).toBeUndefined();
34
+ expect(withWhere.where).toBe(byId);
35
+ expect(DefaultStatementTextRenderer.instance.render(byId)).toBe("id = 1");
36
+ });
37
+
38
+ it("compiles a select", () => {
39
+ const where = new Where(
40
+ new Operator.And(new Operator.Equals("id", 1), new Operator.GreaterThan("age", 18)),
41
+ );
42
+ const query = new SelectStatement({ table: "users", fields: ["id", "name"], where });
43
+ expect(DefaultStatementTextRenderer.instance.render(query)).toBe(
44
+ "SELECT id, name FROM users WHERE id = $id AND age > $age",
45
+ );
46
+ expect(query.where).toBe(where);
47
+ });
48
+
49
+ it("joins tables", () => {
50
+ const query = new SelectStatement({
51
+ table: "users",
52
+ fields: ["users.id"],
53
+ where: new Where(new Operator.Equals("users.id", 1)),
54
+ joins: [
55
+ Join.inner("orders", "users.id", "orders.user_id"),
56
+ Join.left("payments", "orders.id", "payments.order_id"),
57
+ Join.right("profiles", "users.id", "profiles.user_id"),
58
+ ],
59
+ });
60
+ expect(DefaultStatementTextRenderer.instance.render(query)).toBe(
61
+ "SELECT users.id FROM users INNER JOIN orders ON users.id = orders.user_id LEFT JOIN payments ON orders.id = payments.order_id RIGHT JOIN profiles ON users.id = profiles.user_id WHERE users.id = $users.id",
62
+ );
63
+ });
64
+
65
+ it("groups aggregates orders and limits", () => {
66
+ const grouped = new SelectStatement({
67
+ table: "orders",
68
+ fields: ["status", Aggregate.count("*", "n"), Aggregate.sum("amount", "total")],
69
+ where: new Where(new Operator.Equals("status", "open")),
70
+ joins: [Join.inner("customers", "orders.customer_id", "customers.id")],
71
+ groupBy: ["status"],
72
+ orderBy: [OrderBy.desc("total"), OrderBy.asc("status")],
73
+ limit: 10,
74
+ });
75
+ expect(DefaultStatementTextRenderer.instance.render(grouped)).toBe(
76
+ "SELECT status, COUNT(*) AS n, SUM(amount) AS total FROM orders INNER JOIN customers ON orders.customer_id = customers.id WHERE status = $status GROUP BY status ORDER BY total DESC, status ASC LIMIT 10",
77
+ );
78
+ });
79
+
80
+ it("groups rows", () => {
81
+ const byStatus = new SelectStatement({ table: "users", fields: ["id"], groupBy: ["id"] });
82
+ expect(DefaultStatementTextRenderer.instance.render(byStatus)).toBe("SELECT id FROM users GROUP BY id");
83
+ });
84
+
85
+ it("aggregates without grouping", () => {
86
+ const bounds = new SelectStatement({ table: "users", fields: [Aggregate.min("n"), Aggregate.max("n")] });
87
+ expect(DefaultStatementTextRenderer.instance.render(bounds)).toBe("SELECT MIN(n), MAX(n) FROM users");
88
+ });
89
+
90
+ it("orders rows", () => {
91
+ const ordered = new SelectStatement({
92
+ table: "users",
93
+ fields: ["id"],
94
+ orderBy: [OrderBy.asc("id"), OrderBy.desc("name")],
95
+ });
96
+ expect(DefaultStatementTextRenderer.instance.render(ordered)).toBe(
97
+ "SELECT id FROM users ORDER BY id ASC, name DESC",
98
+ );
99
+ });
100
+
101
+ it("limits rows", () => {
102
+ const limited = new SelectStatement({ table: "users", fields: ["id"], limit: 10 });
103
+ expect(DefaultStatementTextRenderer.instance.render(limited)).toBe("SELECT id FROM users LIMIT 10");
104
+ });
105
+
106
+ it("compiles an insert", () => {
107
+ const fields = [
108
+ { name: "id", value: 1 },
109
+ { name: "name", value: "a" },
110
+ ];
111
+ const insert = new InsertStatement("users", fields);
112
+ expect(DefaultStatementTextRenderer.instance.render(insert)).toBe(
113
+ "INSERT INTO users (id, name) VALUES ($id, $name)",
114
+ );
115
+ });
116
+
117
+ it("compiles an update", () => {
118
+ const fields = [
119
+ { name: "id", value: 1 },
120
+ { name: "name", value: "a" },
121
+ ];
122
+ const where = new Where(
123
+ new Operator.And(new Operator.Equals("id", 1), new Operator.GreaterThan("age", 18)),
124
+ );
125
+ const update = new UpdateStatement("users", fields, where);
126
+ expect(DefaultStatementTextRenderer.instance.render(update)).toBe(
127
+ "UPDATE users SET id = $id, name = $name WHERE id = $id AND age > $age",
128
+ );
129
+ expect(update.where).toBe(where);
130
+ });
131
+
132
+ it("compiles a delete", () => {
133
+ const where = new Where(
134
+ new Operator.And(new Operator.Equals("id", 1), new Operator.GreaterThan("age", 18)),
135
+ );
136
+ const del = new DeleteStatement("users", where);
137
+ expect(DefaultStatementTextRenderer.instance.render(del)).toBe(
138
+ "DELETE FROM users WHERE id = $id AND age > $age",
139
+ );
140
+ expect(del.where).toBe(where);
141
+ });
142
+
143
+ it("compiles a create table", () => {
144
+ const createTable = new CreateTableStatement("users", [
145
+ { name: "id", type: "INTEGER" },
146
+ { name: "name", type: "TEXT" },
147
+ ]);
148
+ expect(DefaultStatementTextRenderer.instance.render(createTable)).toBe(
149
+ "CREATE TABLE users (id INTEGER, name TEXT)",
150
+ );
151
+ });
152
+
153
+ it("compiles named constraints", () => {
154
+ const namedTable = new CreateTableStatement(
155
+ "orders",
156
+ [
157
+ { name: "id", type: "INTEGER", default: new ColumnDefault("0", "df_id"), notNull: new NotNull("nn_id") },
158
+ { name: "tenant_id", type: "INTEGER" },
159
+ { name: "user_id", type: "INTEGER" },
160
+ { name: "qty", type: "INTEGER" },
161
+ ],
162
+ [
163
+ new PrimaryKey("pk_orders", ["id", "tenant_id"]),
164
+ new ForeignKey("fk_orders_user", ["user_id"], "users", ["id"], ReferentialAction.Cascade),
165
+ new UniqueConstraint("uq_orders_user", ["user_id"]),
166
+ new CheckConstraint("chk_orders_qty", "qty > 0"),
167
+ ],
168
+ );
169
+ expect(DefaultStatementTextRenderer.instance.render(namedTable)).toBe(
170
+ "CREATE TABLE orders (id INTEGER CONSTRAINT df_id DEFAULT 0 CONSTRAINT nn_id NOT NULL, tenant_id INTEGER, user_id INTEGER, qty INTEGER, CONSTRAINT pk_orders PRIMARY KEY (id, tenant_id), CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, CONSTRAINT uq_orders_user UNIQUE (user_id), CONSTRAINT chk_orders_qty CHECK (qty > 0))",
171
+ );
172
+ });
173
+
174
+ it("compiles a create index", () => {
175
+ const createIndex = new CreateIndexStatement("idx_orders_user", "orders", ["user_id"]);
176
+ expect(DefaultStatementTextRenderer.instance.render(createIndex)).toBe(
177
+ "CREATE INDEX idx_orders_user ON orders (user_id)",
178
+ );
179
+ });
180
+
181
+ it("compiles a create unique index", () => {
182
+ const createIndex = new CreateIndexStatement("uq_orders_code", "orders", ["code"], true);
183
+ expect(DefaultStatementTextRenderer.instance.render(createIndex)).toBe(
184
+ "CREATE UNIQUE INDEX uq_orders_code ON orders (code)",
185
+ );
186
+ });
187
+
188
+ it("compiles a call", () => {
189
+ const fields = [
190
+ { name: "id", value: 1 },
191
+ { name: "name", value: "a" },
192
+ ];
193
+ const call = new ExecuteStoredProcedureStatement("save", fields);
194
+ expect(DefaultStatementTextRenderer.instance.render(call)).toBe("CALL save($id, $name)");
195
+ });
196
+
197
+ it("compiles a select function call", () => {
198
+ const fields = [
199
+ { name: "id", value: 1 },
200
+ { name: "name", value: "a" },
201
+ ];
202
+ const ret = new ReturnStatement("add", fields);
203
+ expect(DefaultStatementTextRenderer.instance.render(ret)).toBe("SELECT add($id, $name)");
204
+ });
205
+
206
+ it("renders compare groups not and in", () => {
207
+ const where = new Where(
208
+ new Operator.And(new Operator.Equals("id", 1), new Operator.GreaterThan("age", 18)),
209
+ );
210
+ const inIds = new Where(new Operator.In("id", [1, 2]));
211
+ expect(DefaultStatementTextRenderer.instance.render(where)).toBe("id = $id AND age > $age");
212
+ expect(DefaultStatementTextRenderer.instance.render(inIds)).toBe("id IN ($id_0, $id_1)");
213
+ });
214
+
215
+ it("compare tree", () => {
216
+ const where = new Where(
217
+ new Operator.And(new Operator.Equals("id", 1), new Operator.GreaterThan("age", 18)),
218
+ );
219
+ expect(DefaultStatementTextRenderer.instance.render(where)).toBe("id = $id AND age > $age");
220
+ });
221
+
222
+ it("attaches where to a query", () => {
223
+ const where = new Where(
224
+ new Operator.And(new Operator.Equals("id", 1), new Operator.GreaterThan("age", 18)),
225
+ );
226
+ const query = new SelectStatement({ table: "users", fields: ["id", "name"], where });
227
+ expect(DefaultStatementTextRenderer.instance.render(query)).toBe(
228
+ "SELECT id, name FROM users WHERE id = $id AND age > $age",
229
+ );
230
+ });
231
+ });