@slonik/sql-tag 40.2.2

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 (233) hide show
  1. package/README.md +2 -0
  2. package/dist/Logger.d.ts +2 -0
  3. package/dist/Logger.d.ts.map +1 -0
  4. package/dist/Logger.js +8 -0
  5. package/dist/Logger.js.map +1 -0
  6. package/dist/factories/createPrimitiveValueExpressions.d.ts +3 -0
  7. package/dist/factories/createPrimitiveValueExpressions.d.ts.map +1 -0
  8. package/dist/factories/createPrimitiveValueExpressions.js +33 -0
  9. package/dist/factories/createPrimitiveValueExpressions.js.map +1 -0
  10. package/dist/factories/createSqlTag.d.ts +38 -0
  11. package/dist/factories/createSqlTag.d.ts.map +1 -0
  12. package/dist/factories/createSqlTag.js +170 -0
  13. package/dist/factories/createSqlTag.js.map +1 -0
  14. package/dist/factories/createSqlTag.test/array.test.d.ts +2 -0
  15. package/dist/factories/createSqlTag.test/array.test.d.ts.map +1 -0
  16. package/dist/factories/createSqlTag.test/array.test.js +76 -0
  17. package/dist/factories/createSqlTag.test/array.test.js.map +1 -0
  18. package/dist/factories/createSqlTag.test/date.test.d.ts +2 -0
  19. package/dist/factories/createSqlTag.test/date.test.d.ts.map +1 -0
  20. package/dist/factories/createSqlTag.test/date.test.js +25 -0
  21. package/dist/factories/createSqlTag.test/date.test.js.map +1 -0
  22. package/dist/factories/createSqlTag.test/identifier.test.d.ts +2 -0
  23. package/dist/factories/createSqlTag.test/identifier.test.d.ts.map +1 -0
  24. package/dist/factories/createSqlTag.test/identifier.test.js +38 -0
  25. package/dist/factories/createSqlTag.test/identifier.test.js.map +1 -0
  26. package/dist/factories/createSqlTag.test/interval.test.d.ts +2 -0
  27. package/dist/factories/createSqlTag.test/interval.test.d.ts.map +1 -0
  28. package/dist/factories/createSqlTag.test/interval.test.js +43 -0
  29. package/dist/factories/createSqlTag.test/interval.test.js.map +1 -0
  30. package/dist/factories/createSqlTag.test/join.test.d.ts +2 -0
  31. package/dist/factories/createSqlTag.test/join.test.d.ts.map +1 -0
  32. package/dist/factories/createSqlTag.test/join.test.js +87 -0
  33. package/dist/factories/createSqlTag.test/join.test.js.map +1 -0
  34. package/dist/factories/createSqlTag.test/json.test.d.ts +2 -0
  35. package/dist/factories/createSqlTag.test/json.test.d.ts.map +1 -0
  36. package/dist/factories/createSqlTag.test/json.test.js +88 -0
  37. package/dist/factories/createSqlTag.test/json.test.js.map +1 -0
  38. package/dist/factories/createSqlTag.test/jsonb.test.d.ts +2 -0
  39. package/dist/factories/createSqlTag.test/jsonb.test.d.ts.map +1 -0
  40. package/dist/factories/createSqlTag.test/jsonb.test.js +88 -0
  41. package/dist/factories/createSqlTag.test/jsonb.test.js.map +1 -0
  42. package/dist/factories/createSqlTag.test/literalValue.test.d.ts +2 -0
  43. package/dist/factories/createSqlTag.test/literalValue.test.d.ts.map +1 -0
  44. package/dist/factories/createSqlTag.test/literalValue.test.js +18 -0
  45. package/dist/factories/createSqlTag.test/literalValue.test.js.map +1 -0
  46. package/dist/factories/createSqlTag.test/sql.test.d.ts +2 -0
  47. package/dist/factories/createSqlTag.test/sql.test.d.ts.map +1 -0
  48. package/dist/factories/createSqlTag.test/sql.test.js +138 -0
  49. package/dist/factories/createSqlTag.test/sql.test.js.map +1 -0
  50. package/dist/factories/createSqlTag.test/timestamp.test.d.ts +2 -0
  51. package/dist/factories/createSqlTag.test/timestamp.test.d.ts.map +1 -0
  52. package/dist/factories/createSqlTag.test/timestamp.test.js +25 -0
  53. package/dist/factories/createSqlTag.test/timestamp.test.js.map +1 -0
  54. package/dist/factories/createSqlTag.test/type.test.d.ts +2 -0
  55. package/dist/factories/createSqlTag.test/type.test.d.ts.map +1 -0
  56. package/dist/factories/createSqlTag.test/type.test.js +19 -0
  57. package/dist/factories/createSqlTag.test/type.test.js.map +1 -0
  58. package/dist/factories/createSqlTag.test/typeAlias.test.d.ts +2 -0
  59. package/dist/factories/createSqlTag.test/typeAlias.test.d.ts.map +1 -0
  60. package/dist/factories/createSqlTag.test/typeAlias.test.js +47 -0
  61. package/dist/factories/createSqlTag.test/typeAlias.test.js.map +1 -0
  62. package/dist/factories/createSqlTag.test/unnest.test.d.ts +2 -0
  63. package/dist/factories/createSqlTag.test/unnest.test.d.ts.map +1 -0
  64. package/dist/factories/createSqlTag.test/unnest.test.js +127 -0
  65. package/dist/factories/createSqlTag.test/unnest.test.js.map +1 -0
  66. package/dist/factories/createSqlTokenSqlFragment.d.ts +3 -0
  67. package/dist/factories/createSqlTokenSqlFragment.d.ts.map +1 -0
  68. package/dist/factories/createSqlTokenSqlFragment.js +57 -0
  69. package/dist/factories/createSqlTokenSqlFragment.js.map +1 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +6 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/regexRules/slonikPlaceholderRegexRule.d.ts +2 -0
  75. package/dist/regexRules/slonikPlaceholderRegexRule.d.ts.map +1 -0
  76. package/dist/regexRules/slonikPlaceholderRegexRule.js +5 -0
  77. package/dist/regexRules/slonikPlaceholderRegexRule.js.map +1 -0
  78. package/dist/sqlFragmentFactories/createArraySqlFragment.d.ts +3 -0
  79. package/dist/sqlFragmentFactories/createArraySqlFragment.d.ts.map +1 -0
  80. package/dist/sqlFragmentFactories/createArraySqlFragment.js +48 -0
  81. package/dist/sqlFragmentFactories/createArraySqlFragment.js.map +1 -0
  82. package/dist/sqlFragmentFactories/createBinarySqlFragment.d.ts +3 -0
  83. package/dist/sqlFragmentFactories/createBinarySqlFragment.d.ts.map +1 -0
  84. package/dist/sqlFragmentFactories/createBinarySqlFragment.js +16 -0
  85. package/dist/sqlFragmentFactories/createBinarySqlFragment.js.map +1 -0
  86. package/dist/sqlFragmentFactories/createDateSqlFragment.d.ts +3 -0
  87. package/dist/sqlFragmentFactories/createDateSqlFragment.d.ts.map +1 -0
  88. package/dist/sqlFragmentFactories/createDateSqlFragment.js +16 -0
  89. package/dist/sqlFragmentFactories/createDateSqlFragment.js.map +1 -0
  90. package/dist/sqlFragmentFactories/createFragmentSqlFragment.d.ts +3 -0
  91. package/dist/sqlFragmentFactories/createFragmentSqlFragment.d.ts.map +1 -0
  92. package/dist/sqlFragmentFactories/createFragmentSqlFragment.js +34 -0
  93. package/dist/sqlFragmentFactories/createFragmentSqlFragment.js.map +1 -0
  94. package/dist/sqlFragmentFactories/createIdentifierSqlFragment.d.ts +3 -0
  95. package/dist/sqlFragmentFactories/createIdentifierSqlFragment.d.ts.map +1 -0
  96. package/dist/sqlFragmentFactories/createIdentifierSqlFragment.js +21 -0
  97. package/dist/sqlFragmentFactories/createIdentifierSqlFragment.js.map +1 -0
  98. package/dist/sqlFragmentFactories/createIntervalSqlFragment.d.ts +3 -0
  99. package/dist/sqlFragmentFactories/createIntervalSqlFragment.d.ts.map +1 -0
  100. package/dist/sqlFragmentFactories/createIntervalSqlFragment.js +57 -0
  101. package/dist/sqlFragmentFactories/createIntervalSqlFragment.js.map +1 -0
  102. package/dist/sqlFragmentFactories/createJsonSqlFragment.d.ts +3 -0
  103. package/dist/sqlFragmentFactories/createJsonSqlFragment.d.ts.map +1 -0
  104. package/dist/sqlFragmentFactories/createJsonSqlFragment.js +49 -0
  105. package/dist/sqlFragmentFactories/createJsonSqlFragment.js.map +1 -0
  106. package/dist/sqlFragmentFactories/createListSqlFragment.d.ts +3 -0
  107. package/dist/sqlFragmentFactories/createListSqlFragment.d.ts.map +1 -0
  108. package/dist/sqlFragmentFactories/createListSqlFragment.js +38 -0
  109. package/dist/sqlFragmentFactories/createListSqlFragment.js.map +1 -0
  110. package/dist/sqlFragmentFactories/createQuerySqlFragment.d.ts +3 -0
  111. package/dist/sqlFragmentFactories/createQuerySqlFragment.d.ts.map +1 -0
  112. package/dist/sqlFragmentFactories/createQuerySqlFragment.js +34 -0
  113. package/dist/sqlFragmentFactories/createQuerySqlFragment.js.map +1 -0
  114. package/dist/sqlFragmentFactories/createTimestampSqlFragment.d.ts +3 -0
  115. package/dist/sqlFragmentFactories/createTimestampSqlFragment.d.ts.map +1 -0
  116. package/dist/sqlFragmentFactories/createTimestampSqlFragment.js +18 -0
  117. package/dist/sqlFragmentFactories/createTimestampSqlFragment.js.map +1 -0
  118. package/dist/sqlFragmentFactories/createUnnestSqlFragment.d.ts +3 -0
  119. package/dist/sqlFragmentFactories/createUnnestSqlFragment.d.ts.map +1 -0
  120. package/dist/sqlFragmentFactories/createUnnestSqlFragment.js +81 -0
  121. package/dist/sqlFragmentFactories/createUnnestSqlFragment.js.map +1 -0
  122. package/dist/tokens.d.ts +14 -0
  123. package/dist/tokens.d.ts.map +1 -0
  124. package/dist/tokens.js +17 -0
  125. package/dist/tokens.js.map +1 -0
  126. package/dist/types.d.ts +99 -0
  127. package/dist/types.d.ts.map +1 -0
  128. package/dist/types.js +3 -0
  129. package/dist/types.js.map +1 -0
  130. package/dist/utilities/countArrayDimensions.d.ts +2 -0
  131. package/dist/utilities/countArrayDimensions.d.ts.map +1 -0
  132. package/dist/utilities/countArrayDimensions.js +14 -0
  133. package/dist/utilities/countArrayDimensions.js.map +1 -0
  134. package/dist/utilities/countArrayDimensions.test.d.ts +2 -0
  135. package/dist/utilities/countArrayDimensions.test.d.ts.map +1 -0
  136. package/dist/utilities/countArrayDimensions.test.js +13 -0
  137. package/dist/utilities/countArrayDimensions.test.js.map +1 -0
  138. package/dist/utilities/escapeIdentifier.d.ts +5 -0
  139. package/dist/utilities/escapeIdentifier.d.ts.map +1 -0
  140. package/dist/utilities/escapeIdentifier.js +12 -0
  141. package/dist/utilities/escapeIdentifier.js.map +1 -0
  142. package/dist/utilities/escapeIdentifier.test.d.ts +2 -0
  143. package/dist/utilities/escapeIdentifier.test.d.ts.map +1 -0
  144. package/dist/utilities/escapeIdentifier.test.js +13 -0
  145. package/dist/utilities/escapeIdentifier.test.js.map +1 -0
  146. package/dist/utilities/escapeLiteralValue.d.ts +5 -0
  147. package/dist/utilities/escapeLiteralValue.d.ts.map +1 -0
  148. package/dist/utilities/escapeLiteralValue.js +29 -0
  149. package/dist/utilities/escapeLiteralValue.js.map +1 -0
  150. package/dist/utilities/escapeLiteralValue.test.d.ts +2 -0
  151. package/dist/utilities/escapeLiteralValue.test.d.ts.map +1 -0
  152. package/dist/utilities/escapeLiteralValue.test.js +14 -0
  153. package/dist/utilities/escapeLiteralValue.test.js.map +1 -0
  154. package/dist/utilities/formatSlonikPlaceholder.d.ts +14 -0
  155. package/dist/utilities/formatSlonikPlaceholder.d.ts.map +1 -0
  156. package/dist/utilities/formatSlonikPlaceholder.js +20 -0
  157. package/dist/utilities/formatSlonikPlaceholder.js.map +1 -0
  158. package/dist/utilities/hasOwnProperty.d.ts +6 -0
  159. package/dist/utilities/hasOwnProperty.d.ts.map +1 -0
  160. package/dist/utilities/hasOwnProperty.js +12 -0
  161. package/dist/utilities/hasOwnProperty.js.map +1 -0
  162. package/dist/utilities/isPlainObject.d.ts +2 -0
  163. package/dist/utilities/isPlainObject.d.ts.map +1 -0
  164. package/dist/utilities/isPlainObject.js +16 -0
  165. package/dist/utilities/isPlainObject.js.map +1 -0
  166. package/dist/utilities/isPrimitiveValueExpression.d.ts +2 -0
  167. package/dist/utilities/isPrimitiveValueExpression.d.ts.map +1 -0
  168. package/dist/utilities/isPrimitiveValueExpression.js +12 -0
  169. package/dist/utilities/isPrimitiveValueExpression.js.map +1 -0
  170. package/dist/utilities/isSqlToken.d.ts +3 -0
  171. package/dist/utilities/isSqlToken.d.ts.map +1 -0
  172. package/dist/utilities/isSqlToken.js +35 -0
  173. package/dist/utilities/isSqlToken.js.map +1 -0
  174. package/dist/utilities/safeStringify.d.ts +2 -0
  175. package/dist/utilities/safeStringify.d.ts.map +1 -0
  176. package/dist/utilities/safeStringify.js +18 -0
  177. package/dist/utilities/safeStringify.js.map +1 -0
  178. package/dist/utilities/stripArrayNotation.d.ts +2 -0
  179. package/dist/utilities/stripArrayNotation.d.ts.map +1 -0
  180. package/dist/utilities/stripArrayNotation.js +12 -0
  181. package/dist/utilities/stripArrayNotation.js.map +1 -0
  182. package/dist/utilities/stripArrayNotation.test.d.ts +2 -0
  183. package/dist/utilities/stripArrayNotation.test.d.ts.map +1 -0
  184. package/dist/utilities/stripArrayNotation.test.js +13 -0
  185. package/dist/utilities/stripArrayNotation.test.js.map +1 -0
  186. package/package.json +88 -0
  187. package/src/Logger.ts +5 -0
  188. package/src/declarations.d.ts +8 -0
  189. package/src/factories/createPrimitiveValueExpressions.ts +43 -0
  190. package/src/factories/createSqlTag.test/array.test.ts +107 -0
  191. package/src/factories/createSqlTag.test/date.test.ts +26 -0
  192. package/src/factories/createSqlTag.test/identifier.test.ts +39 -0
  193. package/src/factories/createSqlTag.test/interval.test.ts +44 -0
  194. package/src/factories/createSqlTag.test/join.test.ts +127 -0
  195. package/src/factories/createSqlTag.test/json.test.ts +106 -0
  196. package/src/factories/createSqlTag.test/jsonb.test.ts +106 -0
  197. package/src/factories/createSqlTag.test/literalValue.test.ts +17 -0
  198. package/src/factories/createSqlTag.test/sql.test.ts +165 -0
  199. package/src/factories/createSqlTag.test/timestamp.test.ts +29 -0
  200. package/src/factories/createSqlTag.test/type.test.ts +17 -0
  201. package/src/factories/createSqlTag.test/typeAlias.test.ts +53 -0
  202. package/src/factories/createSqlTag.test/unnest.test.ts +173 -0
  203. package/src/factories/createSqlTag.ts +256 -0
  204. package/src/factories/createSqlTokenSqlFragment.ts +60 -0
  205. package/src/index.ts +2 -0
  206. package/src/regexRules/slonikPlaceholderRegexRule.ts +1 -0
  207. package/src/sqlFragmentFactories/createArraySqlFragment.ts +67 -0
  208. package/src/sqlFragmentFactories/createBinarySqlFragment.ts +17 -0
  209. package/src/sqlFragmentFactories/createDateSqlFragment.ts +19 -0
  210. package/src/sqlFragmentFactories/createFragmentSqlFragment.ts +48 -0
  211. package/src/sqlFragmentFactories/createIdentifierSqlFragment.ts +24 -0
  212. package/src/sqlFragmentFactories/createIntervalSqlFragment.ts +71 -0
  213. package/src/sqlFragmentFactories/createJsonSqlFragment.ts +66 -0
  214. package/src/sqlFragmentFactories/createListSqlFragment.ts +48 -0
  215. package/src/sqlFragmentFactories/createQuerySqlFragment.ts +48 -0
  216. package/src/sqlFragmentFactories/createTimestampSqlFragment.ts +22 -0
  217. package/src/sqlFragmentFactories/createUnnestSqlFragment.ts +118 -0
  218. package/src/tokens.ts +14 -0
  219. package/src/types.ts +189 -0
  220. package/src/utilities/countArrayDimensions.test.ts +8 -0
  221. package/src/utilities/countArrayDimensions.ts +12 -0
  222. package/src/utilities/escapeIdentifier.test.ts +8 -0
  223. package/src/utilities/escapeIdentifier.ts +8 -0
  224. package/src/utilities/escapeLiteralValue.test.ts +9 -0
  225. package/src/utilities/escapeLiteralValue.ts +26 -0
  226. package/src/utilities/formatSlonikPlaceholder.ts +15 -0
  227. package/src/utilities/hasOwnProperty.ts +10 -0
  228. package/src/utilities/isPlainObject.ts +14 -0
  229. package/src/utilities/isPrimitiveValueExpression.ts +11 -0
  230. package/src/utilities/isSqlToken.ts +52 -0
  231. package/src/utilities/safeStringify.ts +25 -0
  232. package/src/utilities/stripArrayNotation.test.ts +8 -0
  233. package/src/utilities/stripArrayNotation.ts +9 -0
@@ -0,0 +1,173 @@
1
+ import { FragmentToken } from '../../tokens';
2
+ import { createSqlTag } from '../createSqlTag';
3
+ import test from 'ava';
4
+
5
+ const sql = createSqlTag();
6
+
7
+ test('creates an unnest expression using primitive values (type name identifier)', (t) => {
8
+ const query = sql.fragment`SELECT * FROM ${sql.unnest(
9
+ [
10
+ [1, 2, 3],
11
+ [4, 5, 6],
12
+ ],
13
+ ['int4', 'int4', 'int4'],
14
+ )}`;
15
+
16
+ t.deepEqual(query, {
17
+ sql: 'SELECT * FROM unnest($slonik_1::"int4"[], $slonik_2::"int4"[], $slonik_3::"int4"[])',
18
+ type: FragmentToken,
19
+ values: [
20
+ [1, 4],
21
+ [2, 5],
22
+ [3, 6],
23
+ ],
24
+ });
25
+ });
26
+
27
+ test('creates an unnest expression using primitive values (sql token)', (t) => {
28
+ const query = sql.fragment`SELECT * FROM ${sql.unnest(
29
+ [
30
+ [1, 2, 3],
31
+ [4, 5, 6],
32
+ ],
33
+ [sql.fragment`integer`, sql.fragment`integer`, sql.fragment`integer`],
34
+ )}`;
35
+
36
+ t.deepEqual(query, {
37
+ sql: 'SELECT * FROM unnest($slonik_1::integer[], $slonik_2::integer[], $slonik_3::integer[])',
38
+ type: FragmentToken,
39
+ values: [
40
+ [1, 4],
41
+ [2, 5],
42
+ [3, 6],
43
+ ],
44
+ });
45
+ });
46
+
47
+ test('treats type as sql.identifier', (t) => {
48
+ const query = sql.fragment`SELECT bar, baz FROM ${sql.unnest(
49
+ [
50
+ [1, 3],
51
+ [2, 4],
52
+ ],
53
+ [
54
+ ['foo', 'int4'],
55
+ ['foo', 'int4'],
56
+ ],
57
+ )} AS foo(bar, baz)`;
58
+
59
+ t.deepEqual(query, {
60
+ sql: 'SELECT bar, baz FROM unnest($slonik_1::"foo"."int4"[], $slonik_2::"foo"."int4"[]) AS foo(bar, baz)',
61
+ type: FragmentToken,
62
+ values: [
63
+ [1, 2],
64
+ [3, 4],
65
+ ],
66
+ });
67
+ });
68
+
69
+ test('creates an unnest expression using arrays', (t) => {
70
+ const query = sql.fragment`SELECT * FROM ${sql.unnest(
71
+ [
72
+ [1, 2, 3],
73
+ [4, 5, 6],
74
+ ],
75
+ ['int4', 'int4', 'int4'],
76
+ )}`;
77
+
78
+ t.deepEqual(query, {
79
+ sql: 'SELECT * FROM unnest($slonik_1::"int4"[], $slonik_2::"int4"[], $slonik_3::"int4"[])',
80
+ type: FragmentToken,
81
+ values: [
82
+ [1, 4],
83
+ [2, 5],
84
+ [3, 6],
85
+ ],
86
+ });
87
+ });
88
+
89
+ test('creates incremental alias names if no alias names are provided', (t) => {
90
+ const query = sql.fragment`SELECT * FROM ${sql.unnest(
91
+ [
92
+ [1, 2, 3],
93
+ [4, 5, 6],
94
+ ],
95
+ ['int4', 'int4', 'int4'],
96
+ )}`;
97
+
98
+ t.deepEqual(query, {
99
+ sql: 'SELECT * FROM unnest($slonik_1::"int4"[], $slonik_2::"int4"[], $slonik_3::"int4"[])',
100
+ type: FragmentToken,
101
+ values: [
102
+ [1, 4],
103
+ [2, 5],
104
+ [3, 6],
105
+ ],
106
+ });
107
+ });
108
+
109
+ test('recognizes an array of arrays array', (t) => {
110
+ const query = sql.fragment`SELECT * FROM ${sql.unnest(
111
+ [[[[1], [2], [3]]]],
112
+ ['int4[]'],
113
+ )}`;
114
+
115
+ t.deepEqual(query, {
116
+ sql: 'SELECT * FROM unnest($slonik_1::"int4"[][])',
117
+ type: FragmentToken,
118
+ values: [[[[1], [2], [3]]]],
119
+ });
120
+ });
121
+
122
+ test('throws if tuple member is not a primitive value expression', (t) => {
123
+ const error = t.throws(() => {
124
+ sql.fragment`SELECT * FROM ${sql.unnest(
125
+ [
126
+ [
127
+ // @ts-expect-error Intentional invalid value.
128
+ () => {},
129
+ 2,
130
+ 3,
131
+ ],
132
+ [4, 5],
133
+ ],
134
+ ['int4', 'int4', 'int4'],
135
+ )}`;
136
+ });
137
+
138
+ t.is(
139
+ error?.message,
140
+ 'Invalid unnest tuple member type. Must be a primitive value expression.',
141
+ );
142
+ });
143
+
144
+ test('throws if tuple member length varies in a list of tuples', (t) => {
145
+ const error = t.throws(() => {
146
+ sql.fragment`SELECT * FROM ${sql.unnest(
147
+ [
148
+ [1, 2, 3],
149
+ [4, 5],
150
+ ],
151
+ ['int4', 'int4', 'int4'],
152
+ )}`;
153
+ });
154
+
155
+ t.is(
156
+ error?.message,
157
+ 'Each tuple in a list of tuples must have an equal number of members.',
158
+ );
159
+ });
160
+
161
+ test('throws if tuple member length does not match column types length', (t) => {
162
+ const error = t.throws(() => {
163
+ sql.fragment`SELECT * FROM ${sql.unnest(
164
+ [
165
+ [1, 2, 3],
166
+ [4, 5, 6],
167
+ ],
168
+ ['int4', 'int4'],
169
+ )}`;
170
+ });
171
+
172
+ t.is(error?.message, 'Column types length must match tuple member length.');
173
+ });
@@ -0,0 +1,256 @@
1
+ import { Logger } from '../Logger';
2
+ import {
3
+ ArrayToken,
4
+ BinaryToken,
5
+ DateToken,
6
+ FragmentToken,
7
+ IdentifierToken,
8
+ IntervalToken,
9
+ JsonBinaryToken,
10
+ JsonToken,
11
+ ListToken,
12
+ QueryToken,
13
+ TimestampToken,
14
+ UnnestToken,
15
+ } from '../tokens';
16
+ import {
17
+ type ArraySqlToken,
18
+ type BinarySqlToken,
19
+ type DateSqlToken,
20
+ type FragmentSqlToken,
21
+ type IdentifierSqlToken,
22
+ type IntervalInput,
23
+ type IntervalSqlToken,
24
+ type JsonBinarySqlToken,
25
+ type JsonSqlToken,
26
+ type ListSqlToken,
27
+ type PrimitiveValueExpression,
28
+ type SerializableValue,
29
+ type SqlFragment,
30
+ type SqlToken as SqlTokenType,
31
+ type TimestampSqlToken,
32
+ type TypeNameIdentifier,
33
+ type UnnestSqlToken,
34
+ type ValueExpression,
35
+ } from '../types';
36
+ import { escapeLiteralValue } from '../utilities/escapeLiteralValue';
37
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
38
+ import { isPrimitiveValueExpression } from '../utilities/isPrimitiveValueExpression';
39
+ import { isSqlToken } from '../utilities/isSqlToken';
40
+ import { safeStringify } from '../utilities/safeStringify';
41
+ import { createSqlTokenSqlFragment } from './createSqlTokenSqlFragment';
42
+ import { InvalidInputError } from '@slonik/errors';
43
+ import { z, type ZodTypeAny } from 'zod';
44
+
45
+ const log = Logger.child({
46
+ namespace: 'sql',
47
+ });
48
+
49
+ const createFragment = (
50
+ parts: readonly string[],
51
+ values: readonly ValueExpression[],
52
+ ) => {
53
+ let rawSql = '';
54
+
55
+ const parameterValues: PrimitiveValueExpression[] = [];
56
+
57
+ let index = 0;
58
+
59
+ for (const part of parts) {
60
+ const token = values[index++];
61
+
62
+ rawSql += part;
63
+
64
+ if (index >= parts.length) {
65
+ continue;
66
+ }
67
+
68
+ if (token === undefined) {
69
+ log.debug(
70
+ {
71
+ index,
72
+ parts: JSON.parse(safeStringify(parts)),
73
+ values: JSON.parse(safeStringify(values)),
74
+ },
75
+ 'bound values',
76
+ );
77
+
78
+ throw new InvalidInputError(
79
+ `SQL tag cannot be bound to undefined value at index ${index}.`,
80
+ );
81
+ } else if (isPrimitiveValueExpression(token)) {
82
+ rawSql += formatSlonikPlaceholder(parameterValues.length + 1);
83
+
84
+ parameterValues.push(token);
85
+ } else if (isSqlToken(token)) {
86
+ const sqlFragment = createSqlTokenSqlFragment(
87
+ token,
88
+ parameterValues.length,
89
+ );
90
+
91
+ rawSql += sqlFragment.sql;
92
+
93
+ for (const value of sqlFragment.values) {
94
+ parameterValues.push(value);
95
+ }
96
+ } else {
97
+ log.error(
98
+ {
99
+ constructedSql: rawSql,
100
+ index,
101
+ offendingToken: JSON.parse(safeStringify(token)),
102
+ },
103
+ 'unexpected value expression',
104
+ );
105
+
106
+ throw new TypeError('Unexpected value expression.');
107
+ }
108
+ }
109
+
110
+ return {
111
+ sql: rawSql,
112
+ values: parameterValues,
113
+ };
114
+ };
115
+
116
+ export const createSqlTag = <
117
+ K extends PropertyKey,
118
+ P extends ZodTypeAny,
119
+ Z extends Record<K, P>,
120
+ >(
121
+ configuration: {
122
+ typeAliases?: Z;
123
+ } = {},
124
+ ) => {
125
+ const typeAliases = configuration.typeAliases;
126
+
127
+ return {
128
+ array: (
129
+ values: readonly PrimitiveValueExpression[],
130
+ memberType: SqlTokenType | TypeNameIdentifier,
131
+ ): ArraySqlToken => {
132
+ return Object.freeze({
133
+ memberType,
134
+ type: ArrayToken,
135
+ values,
136
+ });
137
+ },
138
+ binary: (data: Buffer): BinarySqlToken => {
139
+ return Object.freeze({
140
+ data,
141
+ type: BinaryToken,
142
+ });
143
+ },
144
+ date: (date: Date): DateSqlToken => {
145
+ return Object.freeze({
146
+ date,
147
+ type: DateToken,
148
+ });
149
+ },
150
+ fragment: (
151
+ parts: readonly string[],
152
+ ...args: readonly ValueExpression[]
153
+ ): FragmentSqlToken => {
154
+ return Object.freeze({
155
+ ...createFragment(parts, args),
156
+ type: FragmentToken,
157
+ });
158
+ },
159
+ identifier: (names: readonly string[]): IdentifierSqlToken => {
160
+ return Object.freeze({
161
+ names,
162
+ type: IdentifierToken,
163
+ });
164
+ },
165
+ interval: (interval: IntervalInput): IntervalSqlToken => {
166
+ return Object.freeze({
167
+ interval,
168
+ type: IntervalToken,
169
+ });
170
+ },
171
+ join: (
172
+ members: readonly ValueExpression[],
173
+ glue: SqlFragment,
174
+ ): ListSqlToken => {
175
+ return Object.freeze({
176
+ glue,
177
+ members,
178
+ type: ListToken,
179
+ });
180
+ },
181
+ json: (value: SerializableValue): JsonSqlToken => {
182
+ return Object.freeze({
183
+ type: JsonToken,
184
+ value,
185
+ });
186
+ },
187
+ jsonb: (value: SerializableValue): JsonBinarySqlToken => {
188
+ return Object.freeze({
189
+ type: JsonBinaryToken,
190
+ value,
191
+ });
192
+ },
193
+ literalValue: (value: string): SqlFragment => {
194
+ return Object.freeze({
195
+ sql: escapeLiteralValue(value),
196
+ type: FragmentToken,
197
+ values: [],
198
+ });
199
+ },
200
+ timestamp: (date: Date): TimestampSqlToken => {
201
+ return Object.freeze({
202
+ date,
203
+ type: TimestampToken,
204
+ });
205
+ },
206
+ type: <T extends ZodTypeAny>(parser: T) => {
207
+ return (
208
+ parts: readonly string[],
209
+ ...args: readonly ValueExpression[]
210
+ ) => {
211
+ return Object.freeze({
212
+ ...createFragment(parts, args),
213
+ parser,
214
+ type: QueryToken,
215
+ });
216
+ };
217
+ },
218
+ typeAlias: <Y extends keyof Z>(parserAlias: Y) => {
219
+ if (!typeAliases?.[parserAlias]) {
220
+ throw new Error(
221
+ 'Type alias "' + String(parserAlias) + '" does not exist.',
222
+ );
223
+ }
224
+
225
+ return (
226
+ parts: readonly string[],
227
+ ...args: readonly ValueExpression[]
228
+ ) => {
229
+ return Object.freeze({
230
+ ...createFragment(parts, args),
231
+ parser: typeAliases[parserAlias],
232
+ type: QueryToken,
233
+ });
234
+ };
235
+ },
236
+ unnest: (
237
+ tuples: ReadonlyArray<readonly PrimitiveValueExpression[]>,
238
+ columnTypes:
239
+ | Array<[...string[], TypeNameIdentifier]>
240
+ | Array<SqlFragment | TypeNameIdentifier>,
241
+ ): UnnestSqlToken => {
242
+ return Object.freeze({
243
+ columnTypes,
244
+ tuples,
245
+ type: UnnestToken,
246
+ });
247
+ },
248
+ unsafe: (parts: readonly string[], ...args: readonly ValueExpression[]) => {
249
+ return Object.freeze({
250
+ ...createFragment(parts, args),
251
+ parser: z.any(),
252
+ type: QueryToken,
253
+ });
254
+ },
255
+ };
256
+ };
@@ -0,0 +1,60 @@
1
+ import { createArraySqlFragment } from '../sqlFragmentFactories/createArraySqlFragment';
2
+ import { createBinarySqlFragment } from '../sqlFragmentFactories/createBinarySqlFragment';
3
+ import { createDateSqlFragment } from '../sqlFragmentFactories/createDateSqlFragment';
4
+ import { createFragmentSqlFragment } from '../sqlFragmentFactories/createFragmentSqlFragment';
5
+ import { createIdentifierSqlFragment } from '../sqlFragmentFactories/createIdentifierSqlFragment';
6
+ import { createIntervalSqlFragment } from '../sqlFragmentFactories/createIntervalSqlFragment';
7
+ import { createJsonSqlFragment } from '../sqlFragmentFactories/createJsonSqlFragment';
8
+ import { createListSqlFragment } from '../sqlFragmentFactories/createListSqlFragment';
9
+ import { createQuerySqlFragment } from '../sqlFragmentFactories/createQuerySqlFragment';
10
+ import { createTimestampSqlFragment } from '../sqlFragmentFactories/createTimestampSqlFragment';
11
+ import { createUnnestSqlFragment } from '../sqlFragmentFactories/createUnnestSqlFragment';
12
+ import {
13
+ ArrayToken,
14
+ BinaryToken,
15
+ DateToken,
16
+ FragmentToken,
17
+ IdentifierToken,
18
+ IntervalToken,
19
+ JsonBinaryToken,
20
+ JsonToken,
21
+ ListToken,
22
+ QueryToken,
23
+ TimestampToken,
24
+ UnnestToken,
25
+ } from '../tokens';
26
+ import { type SqlFragment, type SqlToken as SqlTokenType } from '../types';
27
+ import { UnexpectedStateError } from '@slonik/errors';
28
+
29
+ export const createSqlTokenSqlFragment = (
30
+ token: SqlTokenType,
31
+ greatestParameterPosition: number,
32
+ ): SqlFragment => {
33
+ if (token.type === ArrayToken) {
34
+ return createArraySqlFragment(token, greatestParameterPosition);
35
+ } else if (token.type === BinaryToken) {
36
+ return createBinarySqlFragment(token, greatestParameterPosition);
37
+ } else if (token.type === DateToken) {
38
+ return createDateSqlFragment(token, greatestParameterPosition);
39
+ } else if (token.type === FragmentToken) {
40
+ return createFragmentSqlFragment(token, greatestParameterPosition);
41
+ } else if (token.type === IdentifierToken) {
42
+ return createIdentifierSqlFragment(token);
43
+ } else if (token.type === IntervalToken) {
44
+ return createIntervalSqlFragment(token, greatestParameterPosition);
45
+ } else if (token.type === JsonBinaryToken) {
46
+ return createJsonSqlFragment(token, greatestParameterPosition, true);
47
+ } else if (token.type === JsonToken) {
48
+ return createJsonSqlFragment(token, greatestParameterPosition, false);
49
+ } else if (token.type === ListToken) {
50
+ return createListSqlFragment(token, greatestParameterPosition);
51
+ } else if (token.type === QueryToken) {
52
+ return createQuerySqlFragment(token, greatestParameterPosition);
53
+ } else if (token.type === TimestampToken) {
54
+ return createTimestampSqlFragment(token, greatestParameterPosition);
55
+ } else if (token.type === UnnestToken) {
56
+ return createUnnestSqlFragment(token, greatestParameterPosition);
57
+ }
58
+
59
+ throw new UnexpectedStateError('Unexpected token type.');
60
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { createSqlTag } from './factories/createSqlTag';
2
+ export { type SqlTag } from './types';
@@ -0,0 +1 @@
1
+ export const slonikPlaceholderRegexRule = /\$slonik_(\d+)/gu;
@@ -0,0 +1,67 @@
1
+ import { createSqlTokenSqlFragment } from '../factories/createSqlTokenSqlFragment';
2
+ import { type ArraySqlToken, type SqlFragment } from '../types';
3
+ import { escapeIdentifier } from '../utilities/escapeIdentifier';
4
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
5
+ import { isPrimitiveValueExpression } from '../utilities/isPrimitiveValueExpression';
6
+ import { isSqlToken } from '../utilities/isSqlToken';
7
+ import { InvalidInputError, UnexpectedStateError } from '@slonik/errors';
8
+
9
+ export const createArraySqlFragment = (
10
+ token: ArraySqlToken,
11
+ greatestParameterPosition: number,
12
+ ): SqlFragment => {
13
+ let placeholderIndex = greatestParameterPosition;
14
+
15
+ for (const value of token.values) {
16
+ if (token.memberType === 'bytea') {
17
+ if (Buffer.isBuffer(value)) {
18
+ continue;
19
+ } else {
20
+ throw new InvalidInputError(
21
+ 'Invalid array member type. Non-buffer value bound to bytea type.',
22
+ );
23
+ }
24
+ }
25
+
26
+ if (!isPrimitiveValueExpression(value)) {
27
+ throw new InvalidInputError(
28
+ 'Invalid array member type. Must be a primitive value expression.',
29
+ );
30
+ }
31
+ }
32
+
33
+ const values = [token.values];
34
+
35
+ placeholderIndex++;
36
+
37
+ let sql = formatSlonikPlaceholder(placeholderIndex) + '::';
38
+
39
+ if (
40
+ isSqlToken(token.memberType) &&
41
+ token.memberType.type === 'SLONIK_TOKEN_FRAGMENT'
42
+ ) {
43
+ const sqlFragment = createSqlTokenSqlFragment(
44
+ token.memberType,
45
+ placeholderIndex,
46
+ );
47
+
48
+ if (sqlFragment.values.length > 0) {
49
+ throw new UnexpectedStateError(
50
+ 'Type is not expected to have a value binding.',
51
+ );
52
+ }
53
+
54
+ sql += sqlFragment.sql;
55
+ } else if (typeof token.memberType === 'string') {
56
+ sql += escapeIdentifier(token.memberType) + '[]';
57
+ } else {
58
+ throw new InvalidInputError(
59
+ 'Unsupported `memberType`. `memberType` must be a string or SqlToken of "SLONIK_TOKEN_FRAGMENT" type.',
60
+ );
61
+ }
62
+
63
+ return {
64
+ sql,
65
+ values,
66
+ };
67
+ };
@@ -0,0 +1,17 @@
1
+ import { type BinarySqlToken, type SqlFragment } from '../types';
2
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
3
+ import { InvalidInputError } from '@slonik/errors';
4
+
5
+ export const createBinarySqlFragment = (
6
+ token: BinarySqlToken,
7
+ greatestParameterPosition: number,
8
+ ): SqlFragment => {
9
+ if (!Buffer.isBuffer(token.data)) {
10
+ throw new InvalidInputError('Binary value must be a buffer.');
11
+ }
12
+
13
+ return {
14
+ sql: formatSlonikPlaceholder(greatestParameterPosition + 1),
15
+ values: [token.data],
16
+ };
17
+ };
@@ -0,0 +1,19 @@
1
+ import { type DateSqlToken, type SqlFragment } from '../types';
2
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
3
+ import { InvalidInputError } from '@slonik/errors';
4
+
5
+ export const createDateSqlFragment = (
6
+ token: DateSqlToken,
7
+ greatestParameterPosition: number,
8
+ ): SqlFragment => {
9
+ if (!(token.date instanceof Date)) {
10
+ throw new InvalidInputError(
11
+ 'Date parameter value must be an instance of Date.',
12
+ );
13
+ }
14
+
15
+ return {
16
+ sql: formatSlonikPlaceholder(greatestParameterPosition + 1) + '::date',
17
+ values: [token.date.toISOString().slice(0, 10)],
18
+ };
19
+ };
@@ -0,0 +1,48 @@
1
+ import { slonikPlaceholderRegexRule } from '../regexRules/slonikPlaceholderRegexRule';
2
+ import { type FragmentSqlToken, type SqlFragment } from '../types';
3
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
4
+ import { UnexpectedStateError } from '@slonik/errors';
5
+
6
+ export const createFragmentSqlFragment = (
7
+ token: FragmentSqlToken,
8
+ greatestParameterPosition: number,
9
+ ): SqlFragment => {
10
+ let sql = '';
11
+
12
+ let leastMatchedParameterPosition = Number.POSITIVE_INFINITY;
13
+ let greatestMatchedParameterPosition = 0;
14
+
15
+ sql += token.sql.replaceAll(slonikPlaceholderRegexRule, (match, g1) => {
16
+ const parameterPosition = Number.parseInt(g1, 10);
17
+
18
+ if (parameterPosition > greatestMatchedParameterPosition) {
19
+ greatestMatchedParameterPosition = parameterPosition;
20
+ }
21
+
22
+ if (parameterPosition < leastMatchedParameterPosition) {
23
+ leastMatchedParameterPosition = parameterPosition;
24
+ }
25
+
26
+ return formatSlonikPlaceholder(
27
+ parameterPosition + greatestParameterPosition,
28
+ );
29
+ });
30
+
31
+ if (greatestMatchedParameterPosition > token.values.length) {
32
+ throw new UnexpectedStateError(
33
+ 'The greatest parameter position is greater than the number of parameter values.',
34
+ );
35
+ }
36
+
37
+ if (
38
+ leastMatchedParameterPosition !== Number.POSITIVE_INFINITY &&
39
+ leastMatchedParameterPosition !== 1
40
+ ) {
41
+ throw new UnexpectedStateError('Parameter position must start at 1.');
42
+ }
43
+
44
+ return {
45
+ sql,
46
+ values: token.values,
47
+ };
48
+ };
@@ -0,0 +1,24 @@
1
+ import { type IdentifierSqlToken, type SqlFragment } from '../types';
2
+ import { escapeIdentifier } from '../utilities/escapeIdentifier';
3
+ import { InvalidInputError } from '@slonik/errors';
4
+
5
+ export const createIdentifierSqlFragment = (
6
+ token: IdentifierSqlToken,
7
+ ): SqlFragment => {
8
+ const sql = token.names
9
+ .map((identifierName) => {
10
+ if (typeof identifierName !== 'string') {
11
+ throw new InvalidInputError(
12
+ 'Identifier name array member type must be a string.',
13
+ );
14
+ }
15
+
16
+ return escapeIdentifier(identifierName);
17
+ })
18
+ .join('.');
19
+
20
+ return {
21
+ sql,
22
+ values: [],
23
+ };
24
+ };