@simplysm/orm-common 13.0.68 → 13.0.70

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 (204) hide show
  1. package/README.md +54 -1447
  2. package/dist/create-db-context.d.ts +10 -10
  3. package/dist/create-db-context.js +9 -9
  4. package/dist/create-db-context.js.map +1 -1
  5. package/dist/ddl/column-ddl.d.ts +4 -4
  6. package/dist/ddl/initialize.d.ts +17 -17
  7. package/dist/ddl/initialize.js +2 -2
  8. package/dist/ddl/initialize.js.map +1 -1
  9. package/dist/ddl/relation-ddl.d.ts +6 -6
  10. package/dist/ddl/schema-ddl.d.ts +4 -4
  11. package/dist/ddl/table-ddl.d.ts +24 -24
  12. package/dist/ddl/table-ddl.js +4 -4
  13. package/dist/ddl/table-ddl.js.map +1 -1
  14. package/dist/errors/db-transaction-error.d.ts +15 -15
  15. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  16. package/dist/exec/executable.d.ts +23 -23
  17. package/dist/exec/executable.js +3 -3
  18. package/dist/exec/executable.js.map +1 -1
  19. package/dist/exec/queryable.d.ts +160 -160
  20. package/dist/exec/queryable.js +119 -119
  21. package/dist/exec/queryable.js.map +1 -1
  22. package/dist/exec/search-parser.d.ts +37 -37
  23. package/dist/exec/search-parser.d.ts.map +1 -1
  24. package/dist/expr/expr-unit.d.ts +4 -4
  25. package/dist/expr/expr.d.ts +257 -257
  26. package/dist/expr/expr.js +265 -265
  27. package/dist/expr/expr.js.map +1 -1
  28. package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
  29. package/dist/query-builder/base/expr-renderer-base.js +2 -2
  30. package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
  31. package/dist/query-builder/base/query-builder-base.d.ts +26 -26
  32. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  33. package/dist/query-builder/base/query-builder-base.js +22 -22
  34. package/dist/query-builder/base/query-builder-base.js.map +1 -1
  35. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
  36. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  37. package/dist/query-builder/mssql/mssql-expr-renderer.js +18 -18
  38. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
  39. package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
  40. package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
  41. package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
  42. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
  43. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
  44. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  45. package/dist/query-builder/mysql/mysql-expr-renderer.js +17 -17
  46. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
  47. package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
  48. package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
  49. package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
  50. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
  51. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
  52. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  53. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +17 -17
  54. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
  55. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
  56. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  57. package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
  58. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
  59. package/dist/query-builder/query-builder.d.ts +1 -1
  60. package/dist/schema/factory/column-builder.d.ts +79 -79
  61. package/dist/schema/factory/column-builder.js +42 -42
  62. package/dist/schema/factory/index-builder.d.ts +39 -39
  63. package/dist/schema/factory/index-builder.js +26 -26
  64. package/dist/schema/factory/relation-builder.d.ts +99 -99
  65. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  66. package/dist/schema/factory/relation-builder.js +38 -38
  67. package/dist/schema/procedure-builder.d.ts +49 -49
  68. package/dist/schema/procedure-builder.d.ts.map +1 -1
  69. package/dist/schema/procedure-builder.js +33 -33
  70. package/dist/schema/table-builder.d.ts +59 -59
  71. package/dist/schema/table-builder.d.ts.map +1 -1
  72. package/dist/schema/table-builder.js +43 -43
  73. package/dist/schema/view-builder.d.ts +49 -49
  74. package/dist/schema/view-builder.d.ts.map +1 -1
  75. package/dist/schema/view-builder.js +32 -32
  76. package/dist/types/column.d.ts +22 -22
  77. package/dist/types/column.js +1 -1
  78. package/dist/types/column.js.map +1 -1
  79. package/dist/types/db.d.ts +40 -40
  80. package/dist/types/expr.d.ts +59 -59
  81. package/dist/types/expr.d.ts.map +1 -1
  82. package/dist/types/query-def.d.ts +44 -44
  83. package/dist/types/query-def.d.ts.map +1 -1
  84. package/dist/utils/result-parser.d.ts +11 -11
  85. package/dist/utils/result-parser.js +3 -3
  86. package/dist/utils/result-parser.js.map +1 -1
  87. package/package.json +5 -5
  88. package/src/create-db-context.ts +20 -20
  89. package/src/ddl/column-ddl.ts +4 -4
  90. package/src/ddl/initialize.ts +259 -259
  91. package/src/ddl/relation-ddl.ts +89 -89
  92. package/src/ddl/schema-ddl.ts +4 -4
  93. package/src/ddl/table-ddl.ts +189 -189
  94. package/src/errors/db-transaction-error.ts +13 -13
  95. package/src/exec/executable.ts +25 -25
  96. package/src/exec/queryable.ts +2033 -2033
  97. package/src/exec/search-parser.ts +57 -57
  98. package/src/expr/expr-unit.ts +4 -4
  99. package/src/expr/expr.ts +2140 -2140
  100. package/src/query-builder/base/expr-renderer-base.ts +237 -237
  101. package/src/query-builder/base/query-builder-base.ts +213 -213
  102. package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
  103. package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
  104. package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
  105. package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
  106. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
  107. package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
  108. package/src/query-builder/query-builder.ts +19 -19
  109. package/src/schema/factory/column-builder.ts +423 -423
  110. package/src/schema/factory/index-builder.ts +164 -164
  111. package/src/schema/factory/relation-builder.ts +453 -453
  112. package/src/schema/procedure-builder.ts +232 -232
  113. package/src/schema/table-builder.ts +319 -319
  114. package/src/schema/view-builder.ts +221 -221
  115. package/src/types/column.ts +188 -188
  116. package/src/types/db.ts +208 -208
  117. package/src/types/expr.ts +697 -697
  118. package/src/types/query-def.ts +513 -513
  119. package/src/utils/result-parser.ts +458 -458
  120. package/tests/db-context/create-db-context.spec.ts +224 -0
  121. package/tests/db-context/define-db-context.spec.ts +68 -0
  122. package/tests/ddl/basic.expected.ts +341 -0
  123. package/tests/ddl/basic.spec.ts +714 -0
  124. package/tests/ddl/column-builder.expected.ts +310 -0
  125. package/tests/ddl/column-builder.spec.ts +637 -0
  126. package/tests/ddl/index-builder.expected.ts +38 -0
  127. package/tests/ddl/index-builder.spec.ts +202 -0
  128. package/tests/ddl/procedure-builder.expected.ts +52 -0
  129. package/tests/ddl/procedure-builder.spec.ts +234 -0
  130. package/tests/ddl/relation-builder.expected.ts +36 -0
  131. package/tests/ddl/relation-builder.spec.ts +372 -0
  132. package/tests/ddl/table-builder.expected.ts +113 -0
  133. package/tests/ddl/table-builder.spec.ts +433 -0
  134. package/tests/ddl/view-builder.expected.ts +38 -0
  135. package/tests/ddl/view-builder.spec.ts +176 -0
  136. package/tests/dml/delete.expected.ts +96 -0
  137. package/tests/dml/delete.spec.ts +160 -0
  138. package/tests/dml/insert.expected.ts +192 -0
  139. package/tests/dml/insert.spec.ts +288 -0
  140. package/tests/dml/update.expected.ts +176 -0
  141. package/tests/dml/update.spec.ts +318 -0
  142. package/tests/dml/upsert.expected.ts +215 -0
  143. package/tests/dml/upsert.spec.ts +242 -0
  144. package/tests/errors/queryable-errors.spec.ts +177 -0
  145. package/tests/escape.spec.ts +100 -0
  146. package/tests/examples/pivot.expected.ts +211 -0
  147. package/tests/examples/pivot.spec.ts +533 -0
  148. package/tests/examples/sampling.expected.ts +69 -0
  149. package/tests/examples/sampling.spec.ts +104 -0
  150. package/tests/examples/unpivot.expected.ts +120 -0
  151. package/tests/examples/unpivot.spec.ts +226 -0
  152. package/tests/exec/search-parser.spec.ts +283 -0
  153. package/tests/executable/basic.expected.ts +18 -0
  154. package/tests/executable/basic.spec.ts +54 -0
  155. package/tests/expr/comparison.expected.ts +282 -0
  156. package/tests/expr/comparison.spec.ts +400 -0
  157. package/tests/expr/conditional.expected.ts +134 -0
  158. package/tests/expr/conditional.spec.ts +276 -0
  159. package/tests/expr/date.expected.ts +332 -0
  160. package/tests/expr/date.spec.ts +526 -0
  161. package/tests/expr/math.expected.ts +62 -0
  162. package/tests/expr/math.spec.ts +106 -0
  163. package/tests/expr/string.expected.ts +218 -0
  164. package/tests/expr/string.spec.ts +356 -0
  165. package/tests/expr/utility.expected.ts +147 -0
  166. package/tests/expr/utility.spec.ts +182 -0
  167. package/tests/select/basic.expected.ts +322 -0
  168. package/tests/select/basic.spec.ts +502 -0
  169. package/tests/select/filter.expected.ts +357 -0
  170. package/tests/select/filter.spec.ts +1068 -0
  171. package/tests/select/group.expected.ts +169 -0
  172. package/tests/select/group.spec.ts +244 -0
  173. package/tests/select/join.expected.ts +582 -0
  174. package/tests/select/join.spec.ts +805 -0
  175. package/tests/select/order.expected.ts +150 -0
  176. package/tests/select/order.spec.ts +189 -0
  177. package/tests/select/recursive-cte.expected.ts +244 -0
  178. package/tests/select/recursive-cte.spec.ts +514 -0
  179. package/tests/select/result-meta.spec.ts +270 -0
  180. package/tests/select/subquery.expected.ts +363 -0
  181. package/tests/select/subquery.spec.ts +537 -0
  182. package/tests/select/view.expected.ts +155 -0
  183. package/tests/select/view.spec.ts +235 -0
  184. package/tests/select/window.expected.ts +345 -0
  185. package/tests/select/window.spec.ts +618 -0
  186. package/tests/setup/MockExecutor.ts +18 -0
  187. package/tests/setup/TestDbContext.ts +59 -0
  188. package/tests/setup/models/Company.ts +13 -0
  189. package/tests/setup/models/Employee.ts +10 -0
  190. package/tests/setup/models/MonthlySales.ts +11 -0
  191. package/tests/setup/models/Post.ts +16 -0
  192. package/tests/setup/models/Sales.ts +10 -0
  193. package/tests/setup/models/User.ts +19 -0
  194. package/tests/setup/procedure/GetAllUsers.ts +9 -0
  195. package/tests/setup/procedure/GetUserById.ts +12 -0
  196. package/tests/setup/test-utils.ts +72 -0
  197. package/tests/setup/views/ActiveUsers.ts +8 -0
  198. package/tests/setup/views/UserSummary.ts +11 -0
  199. package/tests/types/nullable-queryable-record.spec.ts +145 -0
  200. package/tests/utils/result-parser-perf.spec.ts +210 -0
  201. package/tests/utils/result-parser.spec.ts +701 -0
  202. package/docs/expressions.md +0 -172
  203. package/docs/queries.md +0 -444
  204. package/docs/schema.md +0 -245
@@ -0,0 +1,120 @@
1
+ import { mysql, pgsql, tsql } from "@simplysm/core-common";
2
+ import type { ExpectedSql } from "../setup/test-utils";
3
+
4
+ //#region ========== UNPIVOT - Basic ==========
5
+
6
+ export const unpivotBasic: ExpectedSql = {
7
+ mysql: mysql`
8
+ SELECT \`T1\`.\`id\` AS \`id\`, \`T1\`.\`category\` AS \`category\`,
9
+ \`T1.unpvt\`.\`month\` AS \`month\`, \`T1.unpvt\`.\`amount\` AS \`amount\`
10
+ FROM \`TestDb\`.\`MonthlySales\` AS \`T1\`
11
+ LEFT OUTER JOIN LATERAL (
12
+ SELECT 'jan' AS \`month\`, \`T1\`.\`jan\` AS \`amount\`
13
+ UNION ALL
14
+ SELECT 'feb' AS \`month\`, \`T1\`.\`feb\` AS \`amount\`
15
+ UNION ALL
16
+ SELECT 'mar' AS \`month\`, \`T1\`.\`mar\` AS \`amount\`
17
+ ) AS \`T1.unpvt\` ON TRUE
18
+ `,
19
+ mssql: tsql`
20
+ SELECT [T1].[id] AS [id], [T1].[category] AS [category],
21
+ [T1.unpvt].[month] AS [month], [T1.unpvt].[amount] AS [amount]
22
+ FROM [TestDb].[TestSchema].[MonthlySales] AS [T1]
23
+ OUTER APPLY (
24
+ SELECT N'jan' AS [month], [T1].[jan] AS [amount]
25
+ UNION ALL
26
+ SELECT N'feb' AS [month], [T1].[feb] AS [amount]
27
+ UNION ALL
28
+ SELECT N'mar' AS [month], [T1].[mar] AS [amount]
29
+ ) AS [T1.unpvt]
30
+ `,
31
+ postgresql: pgsql`
32
+ SELECT "T1"."id" AS "id", "T1"."category" AS "category",
33
+ "T1.unpvt"."month" AS "month", "T1.unpvt"."amount" AS "amount"
34
+ FROM "TestSchema"."MonthlySales" AS "T1"
35
+ LEFT OUTER JOIN LATERAL (
36
+ SELECT 'jan' AS "month", "T1"."jan" AS "amount"
37
+ UNION ALL
38
+ SELECT 'feb' AS "month", "T1"."feb" AS "amount"
39
+ UNION ALL
40
+ SELECT 'mar' AS "month", "T1"."mar" AS "amount"
41
+ ) AS "T1.unpvt" ON TRUE
42
+ `,
43
+ };
44
+
45
+ export const unpivotTwoColumns: ExpectedSql = {
46
+ mysql: mysql`
47
+ SELECT \`T1\`.\`id\` AS \`id\`, \`T1\`.\`category\` AS \`category\`, \`T1\`.\`mar\` AS \`mar\`,
48
+ \`T1.unpvt\`.\`period\` AS \`period\`, \`T1.unpvt\`.\`value\` AS \`value\`
49
+ FROM \`TestDb\`.\`MonthlySales\` AS \`T1\`
50
+ LEFT OUTER JOIN LATERAL (
51
+ SELECT 'jan' AS \`period\`, \`T1\`.\`jan\` AS \`value\`
52
+ UNION ALL
53
+ SELECT 'feb' AS \`period\`, \`T1\`.\`feb\` AS \`value\`
54
+ ) AS \`T1.unpvt\` ON TRUE
55
+ `,
56
+ mssql: tsql`
57
+ SELECT [T1].[id] AS [id], [T1].[category] AS [category], [T1].[mar] AS [mar],
58
+ [T1.unpvt].[period] AS [period], [T1.unpvt].[value] AS [value]
59
+ FROM [TestDb].[TestSchema].[MonthlySales] AS [T1]
60
+ OUTER APPLY (
61
+ SELECT N'jan' AS [period], [T1].[jan] AS [value]
62
+ UNION ALL
63
+ SELECT N'feb' AS [period], [T1].[feb] AS [value]
64
+ ) AS [T1.unpvt]
65
+ `,
66
+ postgresql: pgsql`
67
+ SELECT "T1"."id" AS "id", "T1"."category" AS "category", "T1"."mar" AS "mar",
68
+ "T1.unpvt"."period" AS "period", "T1.unpvt"."value" AS "value"
69
+ FROM "TestSchema"."MonthlySales" AS "T1"
70
+ LEFT OUTER JOIN LATERAL (
71
+ SELECT 'jan' AS "period", "T1"."jan" AS "value"
72
+ UNION ALL
73
+ SELECT 'feb' AS "period", "T1"."feb" AS "value"
74
+ ) AS "T1.unpvt" ON TRUE
75
+ `,
76
+ };
77
+
78
+ export const unpivotWithWhere: ExpectedSql = {
79
+ mysql: mysql`
80
+ SELECT \`T1\`.\`id\` AS \`id\`, \`T1\`.\`category\` AS \`category\`,
81
+ \`T1.unpvt\`.\`month\` AS \`month\`, \`T1.unpvt\`.\`amount\` AS \`amount\`
82
+ FROM \`TestDb\`.\`MonthlySales\` AS \`T1\`
83
+ LEFT OUTER JOIN LATERAL (
84
+ SELECT 'jan' AS \`month\`, \`T1\`.\`jan\` AS \`amount\`
85
+ UNION ALL
86
+ SELECT 'feb' AS \`month\`, \`T1\`.\`feb\` AS \`amount\`
87
+ UNION ALL
88
+ SELECT 'mar' AS \`month\`, \`T1\`.\`mar\` AS \`amount\`
89
+ ) AS \`T1.unpvt\` ON TRUE
90
+ WHERE \`T1\`.\`category\`<=>'A'
91
+ `,
92
+ mssql: tsql`
93
+ SELECT [T1].[id] AS [id], [T1].[category] AS [category],
94
+ [T1.unpvt].[month] AS [month], [T1.unpvt].[amount] AS [amount]
95
+ FROM [TestDb].[TestSchema].[MonthlySales] AS [T1]
96
+ OUTER APPLY (
97
+ SELECT N'jan' AS [month], [T1].[jan] AS [amount]
98
+ UNION ALL
99
+ SELECT N'feb' AS [month], [T1].[feb] AS [amount]
100
+ UNION ALL
101
+ SELECT N'mar' AS [month], [T1].[mar] AS [amount]
102
+ ) AS [T1.unpvt]
103
+ WHERE (([T1].[category] IS NULL AND N'A' IS NULL) OR [T1].[category] = N'A')
104
+ `,
105
+ postgresql: pgsql`
106
+ SELECT "T1"."id" AS "id", "T1"."category" AS "category",
107
+ "T1.unpvt"."month" AS "month", "T1.unpvt"."amount" AS "amount"
108
+ FROM "TestSchema"."MonthlySales" AS "T1"
109
+ LEFT OUTER JOIN LATERAL (
110
+ SELECT 'jan' AS "month", "T1"."jan" AS "amount"
111
+ UNION ALL
112
+ SELECT 'feb' AS "month", "T1"."feb" AS "amount"
113
+ UNION ALL
114
+ SELECT 'mar' AS "month", "T1"."mar" AS "amount"
115
+ ) AS "T1.unpvt" ON TRUE
116
+ WHERE "T1"."category" IS NOT DISTINCT FROM 'A'
117
+ `,
118
+ };
119
+
120
+ //#endregion
@@ -0,0 +1,226 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createTestDb } from "../setup/TestDbContext";
3
+ import { expr } from "../../src/expr/expr";
4
+ import { createQueryBuilder } from "../../src/query-builder/query-builder";
5
+ import { dialects } from "../setup/test-utils";
6
+ import "../setup/test-utils";
7
+ import * as expected from "./unpivot.expected";
8
+
9
+ describe("SELECT - UNPIVOT (join + union)", () => {
10
+ describe("Basic", () => {
11
+ const db = createTestDb();
12
+ const def = db
13
+ .monthlySales()
14
+ .join("unpvt", (qr, c) =>
15
+ qr.union(
16
+ qr.select({ month: expr.val("string", "jan"), amount: c.jan }),
17
+ qr.select({ month: expr.val("string", "feb"), amount: c.feb }),
18
+ qr.select({ month: expr.val("string", "mar"), amount: c.mar }),
19
+ ),
20
+ )
21
+ .select((item) => ({
22
+ id: item.id,
23
+ category: item.category,
24
+ month: item.unpvt![0].month,
25
+ amount: item.unpvt![0].amount,
26
+ }))
27
+ .getSelectQueryDef();
28
+
29
+ it("Verify QueryDef", () => {
30
+ expect(def).toEqual({
31
+ type: "select",
32
+ as: "T1",
33
+ from: { database: "TestDb", schema: "TestSchema", name: "MonthlySales" },
34
+ select: {
35
+ id: { type: "column", path: ["T1", "id"] },
36
+ category: { type: "column", path: ["T1", "category"] },
37
+ month: { type: "column", path: ["T1.unpvt", "month"] },
38
+ amount: { type: "column", path: ["T1.unpvt", "amount"] },
39
+ },
40
+ joins: [
41
+ {
42
+ type: "select",
43
+ as: "T1.unpvt",
44
+ from: [
45
+ {
46
+ type: "select",
47
+ as: "T1.unpvt",
48
+ select: {
49
+ month: { type: "value", value: "jan" },
50
+ amount: { type: "column", path: ["T1", "jan"] },
51
+ },
52
+ },
53
+ {
54
+ type: "select",
55
+ as: "T1.unpvt",
56
+ select: {
57
+ month: { type: "value", value: "feb" },
58
+ amount: { type: "column", path: ["T1", "feb"] },
59
+ },
60
+ },
61
+ {
62
+ type: "select",
63
+ as: "T1.unpvt",
64
+ select: {
65
+ month: { type: "value", value: "mar" },
66
+ amount: { type: "column", path: ["T1", "mar"] },
67
+ },
68
+ },
69
+ ],
70
+ isSingle: false,
71
+ },
72
+ ],
73
+ });
74
+ });
75
+
76
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
77
+ const builder = createQueryBuilder(dialect);
78
+ expect(builder.build(def)).toMatchSql(expected.unpivotBasic[dialect]);
79
+ });
80
+ });
81
+
82
+ describe("2-column unpivot", () => {
83
+ const db = createTestDb();
84
+ const def = db
85
+ .monthlySales()
86
+ .join("unpvt", (qr, c) =>
87
+ qr.union(
88
+ qr.select({ period: expr.val("string", "jan"), value: c.jan }),
89
+ qr.select({ period: expr.val("string", "feb"), value: c.feb }),
90
+ ),
91
+ )
92
+ .select((item) => ({
93
+ id: item.id,
94
+ category: item.category,
95
+ mar: item.mar,
96
+ period: item.unpvt![0].period,
97
+ value: item.unpvt![0].value,
98
+ }))
99
+ .getSelectQueryDef();
100
+
101
+ it("Verify QueryDef", () => {
102
+ expect(def).toEqual({
103
+ type: "select",
104
+ as: "T1",
105
+ from: { database: "TestDb", schema: "TestSchema", name: "MonthlySales" },
106
+ select: {
107
+ id: { type: "column", path: ["T1", "id"] },
108
+ category: { type: "column", path: ["T1", "category"] },
109
+ mar: { type: "column", path: ["T1", "mar"] },
110
+ period: { type: "column", path: ["T1.unpvt", "period"] },
111
+ value: { type: "column", path: ["T1.unpvt", "value"] },
112
+ },
113
+ joins: [
114
+ {
115
+ type: "select",
116
+ as: "T1.unpvt",
117
+ from: [
118
+ {
119
+ type: "select",
120
+ as: "T1.unpvt",
121
+ select: {
122
+ period: { type: "value", value: "jan" },
123
+ value: { type: "column", path: ["T1", "jan"] },
124
+ },
125
+ },
126
+ {
127
+ type: "select",
128
+ as: "T1.unpvt",
129
+ select: {
130
+ period: { type: "value", value: "feb" },
131
+ value: { type: "column", path: ["T1", "feb"] },
132
+ },
133
+ },
134
+ ],
135
+ isSingle: false,
136
+ },
137
+ ],
138
+ });
139
+ });
140
+
141
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
142
+ const builder = createQueryBuilder(dialect);
143
+ expect(builder.build(def)).toMatchSql(expected.unpivotTwoColumns[dialect]);
144
+ });
145
+ });
146
+
147
+ describe("WHERE -> UNPIVOT", () => {
148
+ const db = createTestDb();
149
+ const def = db
150
+ .monthlySales()
151
+ .where((r) => [expr.eq(r.category, "A")])
152
+ .join("unpvt", (qr, c) =>
153
+ qr.union(
154
+ qr.select({ month: expr.val("string", "jan"), amount: c.jan }),
155
+ qr.select({ month: expr.val("string", "feb"), amount: c.feb }),
156
+ qr.select({ month: expr.val("string", "mar"), amount: c.mar }),
157
+ ),
158
+ )
159
+ .select((item) => ({
160
+ id: item.id,
161
+ category: item.category,
162
+ month: item.unpvt![0].month,
163
+ amount: item.unpvt![0].amount,
164
+ }))
165
+ .getSelectQueryDef();
166
+
167
+ it("Verify QueryDef", () => {
168
+ expect(def).toEqual({
169
+ type: "select",
170
+ as: "T1",
171
+ from: { database: "TestDb", schema: "TestSchema", name: "MonthlySales" },
172
+ where: [
173
+ {
174
+ type: "eq",
175
+ source: { type: "column", path: ["T1", "category"] },
176
+ target: { type: "value", value: "A" },
177
+ },
178
+ ],
179
+ select: {
180
+ id: { type: "column", path: ["T1", "id"] },
181
+ category: { type: "column", path: ["T1", "category"] },
182
+ month: { type: "column", path: ["T1.unpvt", "month"] },
183
+ amount: { type: "column", path: ["T1.unpvt", "amount"] },
184
+ },
185
+ joins: [
186
+ {
187
+ type: "select",
188
+ as: "T1.unpvt",
189
+ from: [
190
+ {
191
+ type: "select",
192
+ as: "T1.unpvt",
193
+ select: {
194
+ month: { type: "value", value: "jan" },
195
+ amount: { type: "column", path: ["T1", "jan"] },
196
+ },
197
+ },
198
+ {
199
+ type: "select",
200
+ as: "T1.unpvt",
201
+ select: {
202
+ month: { type: "value", value: "feb" },
203
+ amount: { type: "column", path: ["T1", "feb"] },
204
+ },
205
+ },
206
+ {
207
+ type: "select",
208
+ as: "T1.unpvt",
209
+ select: {
210
+ month: { type: "value", value: "mar" },
211
+ amount: { type: "column", path: ["T1", "mar"] },
212
+ },
213
+ },
214
+ ],
215
+ isSingle: false,
216
+ },
217
+ ],
218
+ });
219
+ });
220
+
221
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
222
+ const builder = createQueryBuilder(dialect);
223
+ expect(builder.build(def)).toMatchSql(expected.unpivotWithWhere[dialect]);
224
+ });
225
+ });
226
+ });
@@ -0,0 +1,283 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseSearchQuery } from "../../src/exec/search-parser";
3
+
4
+ describe("parseSearchQuery", () => {
5
+ //#region ========== Basic behavior ==========
6
+
7
+ describe("Basic behavior", () => {
8
+ it("Empty string → empty result", () => {
9
+ expect(parseSearchQuery("")).toEqual({ or: [], must: [], not: [] });
10
+ expect(parseSearchQuery(" ")).toEqual({ or: [], must: [], not: [] });
11
+ });
12
+
13
+ it("Single word → add LIKE pattern to or array", () => {
14
+ expect(parseSearchQuery("Apple")).toEqual({
15
+ or: ["%Apple%"],
16
+ must: [],
17
+ not: [],
18
+ });
19
+ });
20
+
21
+ it("Multiple words (space separated) → OR condition", () => {
22
+ expect(parseSearchQuery("사과 바나나")).toEqual({
23
+ or: ["%사과%", "%바나나%"],
24
+ must: [],
25
+ not: [],
26
+ });
27
+ });
28
+ });
29
+
30
+ //#endregion
31
+
32
+ //#region ========== Prefixes ==========
33
+
34
+ describe("Prefixes", () => {
35
+ it("+ prefix → must (AND condition)", () => {
36
+ expect(parseSearchQuery("+사과")).toEqual({
37
+ or: [],
38
+ must: ["%사과%"],
39
+ not: [],
40
+ });
41
+ });
42
+
43
+ it("- prefix → not (NOT condition)", () => {
44
+ expect(parseSearchQuery("-바나나")).toEqual({
45
+ or: [],
46
+ must: [],
47
+ not: ["%바나나%"],
48
+ });
49
+ });
50
+
51
+ it("Mixed prefixes", () => {
52
+ expect(parseSearchQuery("사과 +딸기 -바나나")).toEqual({
53
+ or: ["%사과%"],
54
+ must: ["%딸기%"],
55
+ not: ["%바나나%"],
56
+ });
57
+ });
58
+
59
+ it("Multiple identical prefixes", () => {
60
+ expect(parseSearchQuery("+사과 +바나나")).toEqual({
61
+ or: [],
62
+ must: ["%사과%", "%바나나%"],
63
+ not: [],
64
+ });
65
+ });
66
+
67
+ it("Ignore when only prefixes present", () => {
68
+ expect(parseSearchQuery("+ - ")).toEqual({ or: [], must: [], not: [] });
69
+ expect(parseSearchQuery("+ 사과")).toEqual({ or: ["%사과%"], must: [], not: [] });
70
+ });
71
+
72
+ it("Consecutive prefixes", () => {
73
+ // First prefix is processed, remaining are included as literal
74
+ expect(parseSearchQuery("++term")).toEqual({
75
+ or: [],
76
+ must: ["%+term%"], // + prefix processed, "+term" remains as literal including +
77
+ not: [],
78
+ });
79
+ expect(parseSearchQuery("--word")).toEqual({
80
+ or: [],
81
+ must: [],
82
+ not: ["%-word%"], // - prefix processed, "-word" remains as literal including -
83
+ });
84
+ });
85
+ });
86
+
87
+ //#endregion
88
+
89
+ //#region ========== Quotes ==========
90
+
91
+ describe("Quotes", () => {
92
+ it("Quoted phrase → must (exact match)", () => {
93
+ expect(parseSearchQuery('"Delicious Fruit"')).toEqual({
94
+ or: [],
95
+ must: ["%Delicious Fruit%"],
96
+ not: [],
97
+ });
98
+ });
99
+
100
+ it("+quote → must", () => {
101
+ expect(parseSearchQuery('+"Delicious Fruit"')).toEqual({
102
+ or: [],
103
+ must: ["%Delicious Fruit%"],
104
+ not: [],
105
+ });
106
+ });
107
+
108
+ it("-quote → not", () => {
109
+ expect(parseSearchQuery('-"Delicious Fruit"')).toEqual({
110
+ or: [],
111
+ must: [],
112
+ not: ["%Delicious Fruit%"],
113
+ });
114
+ });
115
+
116
+ it("Ignore empty quotes", () => {
117
+ expect(parseSearchQuery('""')).toEqual({ or: [], must: [], not: [] });
118
+ expect(parseSearchQuery('" "')).toEqual({ or: [], must: [], not: [] });
119
+ });
120
+
121
+ it("Mix quotes and regular words", () => {
122
+ expect(parseSearchQuery('사과 "Delicious Fruit" 바나나')).toEqual({
123
+ or: ["%사과%", "%바나나%"],
124
+ must: ["%Delicious Fruit%"],
125
+ not: [],
126
+ });
127
+ });
128
+ });
129
+
130
+ //#endregion
131
+
132
+ //#region ========== Wildcards ==========
133
+
134
+ describe("Wildcards", () => {
135
+ it("* at start → search by suffix", () => {
136
+ expect(parseSearchQuery("*사과")).toEqual({
137
+ or: ["%사과"],
138
+ must: [],
139
+ not: [],
140
+ });
141
+ });
142
+
143
+ it("* at end → search by prefix", () => {
144
+ expect(parseSearchQuery("사과*")).toEqual({
145
+ or: ["사과%"],
146
+ must: [],
147
+ not: [],
148
+ });
149
+ });
150
+
151
+ it("* on both sides → contains search (explicit)", () => {
152
+ expect(parseSearchQuery("*사과*")).toEqual({
153
+ or: ["%사과%"],
154
+ must: [],
155
+ not: [],
156
+ });
157
+ });
158
+
159
+ it("* in middle → middle wildcard", () => {
160
+ expect(parseSearchQuery("사*과")).toEqual({
161
+ or: ["사%과"],
162
+ must: [],
163
+ not: [],
164
+ });
165
+ });
166
+ });
167
+
168
+ //#endregion
169
+
170
+ //#region ========== Escaping ==========
171
+
172
+ describe("Escaping", () => {
173
+ it("\\* → literal *", () => {
174
+ expect(parseSearchQuery("app\\*test")).toEqual({
175
+ or: ["%app*test%"],
176
+ must: [],
177
+ not: [],
178
+ });
179
+ });
180
+
181
+ it("\\% → SQL LIKE \\%", () => {
182
+ expect(parseSearchQuery("100\\%")).toEqual({
183
+ or: ["%100\\%%"],
184
+ must: [],
185
+ not: [],
186
+ });
187
+ });
188
+
189
+ it('\\" → literal "', () => {
190
+ expect(parseSearchQuery('\\"test\\"')).toEqual({
191
+ or: ['%"test"%'],
192
+ must: [],
193
+ not: [],
194
+ });
195
+ });
196
+
197
+ it("\\+ → literal + (not prefix)", () => {
198
+ expect(parseSearchQuery("\\+positive")).toEqual({
199
+ or: ["%+positive%"],
200
+ must: [],
201
+ not: [],
202
+ });
203
+ });
204
+
205
+ it("\\- → literal - (not prefix)", () => {
206
+ expect(parseSearchQuery("\\-negative")).toEqual({
207
+ or: ["%-negative%"],
208
+ must: [],
209
+ not: [],
210
+ });
211
+ });
212
+
213
+ it("\\\\ → literal \\", () => {
214
+ expect(parseSearchQuery("path\\\\to\\\\file")).toEqual({
215
+ or: ["%path\\\\to\\\\file%"],
216
+ must: [],
217
+ not: [],
218
+ });
219
+ });
220
+ });
221
+
222
+ //#endregion
223
+
224
+ //#region ========== SQL special characters ==========
225
+
226
+ describe("SQL special character escaping", () => {
227
+ it("_ → \\_ (SQL LIKE escape)", () => {
228
+ expect(parseSearchQuery("file_name")).toEqual({
229
+ or: ["%file\\_name%"],
230
+ must: [],
231
+ not: [],
232
+ });
233
+ });
234
+
235
+ it("% → \\% (SQL LIKE escape)", () => {
236
+ expect(parseSearchQuery("100%")).toEqual({
237
+ or: ["%100\\%%"],
238
+ must: [],
239
+ not: [],
240
+ });
241
+ });
242
+
243
+ it("[ → \\[ (SQL LIKE escape)", () => {
244
+ expect(parseSearchQuery("array[0]")).toEqual({
245
+ or: ["%array\\[0]%"],
246
+ must: [],
247
+ not: [],
248
+ });
249
+ });
250
+ });
251
+
252
+ //#endregion
253
+
254
+ //#region ========== Complex search ==========
255
+
256
+ describe("Complex search", () => {
257
+ it("TSDoc example: normal + quote + exclude + required", () => {
258
+ expect(parseSearchQuery('사과 "Delicious Fruit" -바나나 +딸기')).toEqual({
259
+ or: ["%사과%"],
260
+ must: ["%Delicious Fruit%", "%딸기%"],
261
+ not: ["%바나나%"],
262
+ });
263
+ });
264
+
265
+ it("Mix wildcards and regular words", () => {
266
+ expect(parseSearchQuery("app* test")).toEqual({
267
+ or: ["app%", "%test%"],
268
+ must: [],
269
+ not: [],
270
+ });
271
+ });
272
+
273
+ it("Mix all elements", () => {
274
+ expect(parseSearchQuery('*start end* +must -not "exact phrase"')).toEqual({
275
+ or: ["%start", "end%"],
276
+ must: ["%exact phrase%", "%must%"],
277
+ not: ["%not%"],
278
+ });
279
+ });
280
+ });
281
+
282
+ //#endregion
283
+ });
@@ -0,0 +1,18 @@
1
+ import { mysql, pgsql, tsql } from "@simplysm/core-common";
2
+ import type { ExpectedSql } from "../setup/test-utils";
3
+
4
+ //#region ========== Procedure Execution ==========
5
+
6
+ export const execProcNoParams: ExpectedSql = {
7
+ mysql: mysql`CALL \`TestDb\`.\`GetUserById\`()`,
8
+ mssql: tsql`EXEC [TestDb].[TestSchema].[GetUserById]`,
9
+ postgresql: pgsql`SELECT "TestSchema"."GetUserById"()`,
10
+ };
11
+
12
+ export const execProcWithParams: ExpectedSql = {
13
+ mysql: mysql`CALL \`TestDb\`.\`GetUserById\`(123)`,
14
+ mssql: tsql`EXEC [TestDb].[TestSchema].[GetUserById] 123`,
15
+ postgresql: pgsql`SELECT "TestSchema"."GetUserById"(123)`,
16
+ };
17
+
18
+ //#endregion
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createTestDb } from "../setup/TestDbContext";
3
+ import { createQueryBuilder } from "../../src/query-builder/query-builder";
4
+ import { dialects } from "../setup/test-utils";
5
+ import "../setup/test-utils";
6
+ import * as expected from "./basic.expected";
7
+
8
+ describe("Executable - Basic", () => {
9
+ describe("getExecProcQueryDef - without parameters", () => {
10
+ const db = createTestDb();
11
+ const def = db.getUserById().getExecProcQueryDef();
12
+
13
+ it("Verify QueryDef", () => {
14
+ expect(def).toEqual({
15
+ type: "execProc",
16
+ procedure: {
17
+ database: "TestDb",
18
+ schema: "TestSchema",
19
+ name: "GetUserById",
20
+ },
21
+ params: undefined,
22
+ });
23
+ });
24
+
25
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
26
+ const builder = createQueryBuilder(dialect);
27
+ expect(builder.build(def)).toMatchSql(expected.execProcNoParams[dialect]);
28
+ });
29
+ });
30
+
31
+ describe("getExecProcQueryDef - with parameters", () => {
32
+ const db = createTestDb();
33
+ const def = db.getUserById().getExecProcQueryDef({ userId: 123 });
34
+
35
+ it("Verify QueryDef", () => {
36
+ expect(def).toEqual({
37
+ type: "execProc",
38
+ procedure: {
39
+ database: "TestDb",
40
+ schema: "TestSchema",
41
+ name: "GetUserById",
42
+ },
43
+ params: {
44
+ userId: { type: "value", value: 123 },
45
+ },
46
+ });
47
+ });
48
+
49
+ it.each(dialects)("[%s] Verify SQL", (dialect) => {
50
+ const builder = createQueryBuilder(dialect);
51
+ expect(builder.build(def)).toMatchSql(expected.execProcWithParams[dialect]);
52
+ });
53
+ });
54
+ });