@effect-app/infra 4.0.0-beta.9 → 4.0.0-beta.90
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.
- package/CHANGELOG.md +589 -0
- package/dist/CUPS.d.ts +3 -3
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +3 -3
- package/dist/Emailer/Sendgrid.js +1 -1
- package/dist/Emailer/service.d.ts +3 -3
- package/dist/Emailer/service.d.ts.map +1 -1
- package/dist/Emailer/service.js +3 -3
- package/dist/MainFiberSet.d.ts +2 -2
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +3 -3
- package/dist/Model/Repository/internal/internal.d.ts +3 -3
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +11 -7
- package/dist/Model/Repository/makeRepo.d.ts +2 -2
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +1 -1
- package/dist/Model/Repository/validation.d.ts +5 -4
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/query/dsl.d.ts +9 -9
- package/dist/Operations.d.ts +2 -2
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +3 -3
- package/dist/OperationsRepo.d.ts +2 -2
- package/dist/OperationsRepo.d.ts.map +1 -1
- package/dist/OperationsRepo.js +3 -3
- package/dist/QueueMaker/SQLQueue.d.ts +3 -5
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +9 -7
- package/dist/QueueMaker/errors.d.ts +1 -1
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +10 -9
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +11 -9
- package/dist/RequestContext.d.ts +19 -14
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +5 -5
- package/dist/RequestFiberSet.d.ts +2 -2
- package/dist/RequestFiberSet.d.ts.map +1 -1
- package/dist/RequestFiberSet.js +5 -5
- package/dist/Store/ContextMapContainer.d.ts +14 -3
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +64 -3
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +91 -56
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +3 -4
- package/dist/Store/Memory.d.ts +2 -2
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +4 -4
- package/dist/Store/SQL/Pg.d.ts +4 -0
- package/dist/Store/SQL/Pg.d.ts.map +1 -0
- package/dist/Store/SQL/Pg.js +186 -0
- package/dist/Store/SQL/query.d.ts +36 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +385 -0
- package/dist/Store/SQL.d.ts +11 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +212 -0
- package/dist/Store/index.d.ts +1 -1
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +11 -1
- package/dist/Store/service.d.ts +8 -5
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +14 -6
- package/dist/adapters/SQL/Model.d.ts +2 -5
- package/dist/adapters/SQL/Model.d.ts.map +1 -1
- package/dist/adapters/SQL/Model.js +21 -13
- package/dist/adapters/ServiceBus.d.ts +6 -6
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +9 -9
- package/dist/adapters/cosmos-client.d.ts +2 -2
- package/dist/adapters/cosmos-client.d.ts.map +1 -1
- package/dist/adapters/cosmos-client.js +3 -3
- package/dist/adapters/logger.d.ts.map +1 -1
- package/dist/adapters/memQueue.d.ts +2 -2
- package/dist/adapters/memQueue.d.ts.map +1 -1
- package/dist/adapters/memQueue.js +3 -3
- package/dist/adapters/mongo-client.d.ts +2 -2
- package/dist/adapters/mongo-client.d.ts.map +1 -1
- package/dist/adapters/mongo-client.js +3 -3
- package/dist/adapters/redis-client.d.ts +3 -3
- package/dist/adapters/redis-client.d.ts.map +1 -1
- package/dist/adapters/redis-client.js +3 -3
- package/dist/api/ContextProvider.d.ts +6 -6
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +6 -6
- package/dist/api/internal/RequestContextMiddleware.d.ts +1 -1
- package/dist/api/internal/auth.d.ts +1 -1
- package/dist/api/internal/events.d.ts +2 -2
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +7 -5
- package/dist/api/layerUtils.d.ts +5 -5
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +5 -5
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.d.ts +35 -1
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +39 -1
- package/dist/api/routing/schema/jwt.d.ts +1 -1
- package/dist/api/routing/schema/jwt.d.ts.map +1 -1
- package/dist/api/routing/schema/jwt.js +1 -1
- package/dist/api/routing.d.ts +1 -5
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +3 -2
- package/dist/api/setupRequest.d.ts +6 -3
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +11 -6
- package/dist/errorReporter.d.ts +1 -1
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +1 -1
- package/dist/fileUtil.js +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/rateLimit.js +1 -1
- package/examples/query.ts +29 -25
- package/package.json +32 -18
- package/src/CUPS.ts +2 -2
- package/src/Emailer/Sendgrid.ts +1 -1
- package/src/Emailer/service.ts +2 -2
- package/src/MainFiberSet.ts +2 -2
- package/src/Model/Repository/internal/internal.ts +11 -8
- package/src/Model/Repository/makeRepo.ts +2 -2
- package/src/Operations.ts +2 -2
- package/src/OperationsRepo.ts +2 -2
- package/src/QueueMaker/SQLQueue.ts +10 -10
- package/src/QueueMaker/memQueue.ts +41 -42
- package/src/QueueMaker/sbqueue.ts +65 -62
- package/src/RequestContext.ts +4 -4
- package/src/RequestFiberSet.ts +4 -4
- package/src/Store/ContextMapContainer.ts +98 -2
- package/src/Store/Cosmos.ts +273 -207
- package/src/Store/Disk.ts +2 -3
- package/src/Store/Memory.ts +4 -6
- package/src/Store/SQL/Pg.ts +328 -0
- package/src/Store/SQL/query.ts +430 -0
- package/src/Store/SQL.ts +357 -0
- package/src/Store/index.ts +10 -0
- package/src/Store/service.ts +16 -7
- package/src/adapters/SQL/Model.ts +76 -71
- package/src/adapters/ServiceBus.ts +8 -8
- package/src/adapters/cosmos-client.ts +2 -2
- package/src/adapters/memQueue.ts +2 -2
- package/src/adapters/mongo-client.ts +2 -2
- package/src/adapters/redis-client.ts +2 -2
- package/src/api/ContextProvider.ts +11 -11
- package/src/api/internal/events.ts +7 -6
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/middleware/middleware.ts +43 -0
- package/src/api/routing/schema/jwt.ts +2 -3
- package/src/api/routing.ts +7 -6
- package/src/api/setupRequest.ts +27 -7
- package/src/errorReporter.ts +1 -1
- package/src/fileUtil.ts +1 -1
- package/src/rateLimit.ts +2 -2
- package/test/contextProvider.test.ts +5 -5
- package/test/controller.test.ts +12 -9
- package/test/dist/contextProvider.test.d.ts.map +1 -1
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/fixtures.d.ts +18 -8
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +11 -9
- package/test/dist/query.test.d.ts.map +1 -1
- package/test/dist/rawQuery.test.d.ts.map +1 -1
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/dist/sql-store.test.d.ts.map +1 -0
- package/test/fixtures.ts +10 -8
- package/test/query.test.ts +160 -14
- package/test/rawQuery.test.ts +19 -17
- package/test/requires.test.ts +6 -5
- package/test/rpc-multi-middleware.test.ts +73 -4
- package/test/sql-store.test.ts +776 -0
- package/test/validateSample.test.ts +1 -1
- package/tsconfig.json +0 -1
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Effect, type NonEmptyReadonlyArray } from "effect-app"
|
|
3
|
+
import { assertUnreachable } from "effect-app/utils"
|
|
4
|
+
import { InfraLogger } from "../../logger.js"
|
|
5
|
+
import type { FilterR, FilterResult, Ops } from "../../Model/filter/filterApi.js"
|
|
6
|
+
import { isRelationCheck } from "../codeFilter.js"
|
|
7
|
+
|
|
8
|
+
export interface SQLDialect {
|
|
9
|
+
readonly jsonExtract: (path: string) => string
|
|
10
|
+
readonly jsonExtractJson: (path: string) => string
|
|
11
|
+
readonly placeholder: (index: number) => string
|
|
12
|
+
readonly jsonArrayContains: (arrPath: string, valPlaceholder: string) => string
|
|
13
|
+
readonly jsonArrayNotContains: (arrPath: string, valPlaceholder: string) => string
|
|
14
|
+
readonly jsonArrayContainsAny: (arrPath: string, valPlaceholders: readonly string[]) => string
|
|
15
|
+
readonly jsonArrayNotContainsAny: (arrPath: string, valPlaceholders: readonly string[]) => string
|
|
16
|
+
readonly jsonArrayContainsAll: (arrPath: string, valPlaceholders: readonly string[]) => string
|
|
17
|
+
readonly jsonArrayNotContainsAll: (arrPath: string, valPlaceholders: readonly string[]) => string
|
|
18
|
+
readonly caseInsensitiveLike: (expr: string, valPlaceholder: string) => string
|
|
19
|
+
readonly caseInsensitiveNotLike: (expr: string, valPlaceholder: string) => string
|
|
20
|
+
readonly jsonColumnType: "JSON" | "JSONB"
|
|
21
|
+
readonly arrayLength: (path: string) => string
|
|
22
|
+
readonly jsonEachFrom: (arrPath: string, alias: string) => string
|
|
23
|
+
readonly jsonExtractElement: (alias: string, subPath: string) => string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const sqliteDialect: SQLDialect = {
|
|
27
|
+
jsonExtract: (path) => `json_extract(data, '$.${path}')`,
|
|
28
|
+
jsonExtractJson: (path) => `json_quote(json_extract(data, '$.${path}'))`,
|
|
29
|
+
placeholder: (_index) => "?",
|
|
30
|
+
jsonArrayContains: (arrPath, val) => `EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value = ${val})`,
|
|
31
|
+
jsonArrayNotContains: (arrPath, val) =>
|
|
32
|
+
`NOT EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value = ${val})`,
|
|
33
|
+
jsonArrayContainsAny: (arrPath, vals) =>
|
|
34
|
+
`EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value IN (${vals.join(", ")}))`,
|
|
35
|
+
jsonArrayNotContainsAny: (arrPath, vals) =>
|
|
36
|
+
`NOT EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value IN (${vals.join(", ")}))`,
|
|
37
|
+
jsonArrayContainsAll: (arrPath, vals) =>
|
|
38
|
+
vals.map((v) => `EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value = ${v})`).join(" AND "),
|
|
39
|
+
jsonArrayNotContainsAll: (arrPath, vals) =>
|
|
40
|
+
`NOT (${
|
|
41
|
+
vals.map((v) => `EXISTS(SELECT 1 FROM json_each(data, '$.${arrPath}') WHERE value = ${v})`).join(" AND ")
|
|
42
|
+
})`,
|
|
43
|
+
caseInsensitiveLike: (expr, val) => `LOWER(${expr}) LIKE LOWER(${val})`,
|
|
44
|
+
caseInsensitiveNotLike: (expr, val) => `LOWER(${expr}) NOT LIKE LOWER(${val})`,
|
|
45
|
+
jsonColumnType: "JSON",
|
|
46
|
+
arrayLength: (path) => `json_array_length(data, '$.${path}')`,
|
|
47
|
+
jsonEachFrom: (arrPath, alias) => `json_each(data, '$.${arrPath}') AS ${alias}`,
|
|
48
|
+
jsonExtractElement: (alias, subPath) => `json_extract(${alias}.value, '$.${subPath}')`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const pgDialect: SQLDialect = {
|
|
52
|
+
jsonExtract: (path) => {
|
|
53
|
+
const parts = path.split(".")
|
|
54
|
+
if (parts.length === 1) return `data->>'${parts[0]}'`
|
|
55
|
+
const last = parts.pop()!
|
|
56
|
+
return `data${parts.map((p) => `->'${p}'`).join("")}->>'${last}'`
|
|
57
|
+
},
|
|
58
|
+
jsonExtractJson: (path) => {
|
|
59
|
+
const parts = path.split(".")
|
|
60
|
+
if (parts.length === 1) return `data->'${parts[0]}'`
|
|
61
|
+
return `data${parts.map((p) => `->'${p}'`).join("")}`
|
|
62
|
+
},
|
|
63
|
+
placeholder: (index) => `$${index}`,
|
|
64
|
+
jsonArrayContains: (arrPath, val) => {
|
|
65
|
+
const parts = arrPath.split(".")
|
|
66
|
+
const jsonPath = parts.length === 1
|
|
67
|
+
? `data->'${parts[0]}'`
|
|
68
|
+
: `data${parts.map((p) => `->'${p}'`).join("")}`
|
|
69
|
+
return `${jsonPath} @> ${val}::jsonb`
|
|
70
|
+
},
|
|
71
|
+
jsonArrayNotContains: (arrPath, val) => {
|
|
72
|
+
const parts = arrPath.split(".")
|
|
73
|
+
const jsonPath = parts.length === 1
|
|
74
|
+
? `data->'${parts[0]}'`
|
|
75
|
+
: `data${parts.map((p) => `->'${p}'`).join("")}`
|
|
76
|
+
return `NOT (${jsonPath} @> ${val}::jsonb)`
|
|
77
|
+
},
|
|
78
|
+
jsonArrayContainsAny: (arrPath, vals) => {
|
|
79
|
+
const parts = arrPath.split(".")
|
|
80
|
+
const jsonPath = parts.length === 1
|
|
81
|
+
? `data->'${parts[0]}'`
|
|
82
|
+
: `data${parts.map((p) => `->'${p}'`).join("")}`
|
|
83
|
+
return `(${vals.map((v) => `${jsonPath} @> ${v}::jsonb`).join(" OR ")})`
|
|
84
|
+
},
|
|
85
|
+
jsonArrayNotContainsAny: (arrPath, vals) => {
|
|
86
|
+
const parts = arrPath.split(".")
|
|
87
|
+
const jsonPath = parts.length === 1
|
|
88
|
+
? `data->'${parts[0]}'`
|
|
89
|
+
: `data${parts.map((p) => `->'${p}'`).join("")}`
|
|
90
|
+
return `NOT (${vals.map((v) => `${jsonPath} @> ${v}::jsonb`).join(" OR ")})`
|
|
91
|
+
},
|
|
92
|
+
jsonArrayContainsAll: (arrPath, vals) => {
|
|
93
|
+
const parts = arrPath.split(".")
|
|
94
|
+
const jsonPath = parts.length === 1
|
|
95
|
+
? `data->'${parts[0]}'`
|
|
96
|
+
: `data${parts.map((p) => `->'${p}'`).join("")}`
|
|
97
|
+
return vals.map((v) => `${jsonPath} @> ${v}::jsonb`).join(" AND ")
|
|
98
|
+
},
|
|
99
|
+
jsonArrayNotContainsAll: (arrPath, vals) => {
|
|
100
|
+
const parts = arrPath.split(".")
|
|
101
|
+
const jsonPath = parts.length === 1
|
|
102
|
+
? `data->'${parts[0]}'`
|
|
103
|
+
: `data${parts.map((p) => `->'${p}'`).join("")}`
|
|
104
|
+
return `NOT (${vals.map((v) => `${jsonPath} @> ${v}::jsonb`).join(" AND ")})`
|
|
105
|
+
},
|
|
106
|
+
caseInsensitiveLike: (expr, val) => `${expr} ILIKE ${val}`,
|
|
107
|
+
caseInsensitiveNotLike: (expr, val) => `${expr} NOT ILIKE ${val}`,
|
|
108
|
+
jsonColumnType: "JSONB",
|
|
109
|
+
arrayLength: (path) => `jsonb_array_length(data->'${path}')`,
|
|
110
|
+
jsonEachFrom: (arrPath, alias) => {
|
|
111
|
+
const parts = arrPath.split(".")
|
|
112
|
+
const jsonPath = parts.length === 1
|
|
113
|
+
? `data->'${parts[0]}'`
|
|
114
|
+
: `data${parts.map((p) => `->'${p}'`).join("")}`
|
|
115
|
+
return `jsonb_array_elements(${jsonPath}) AS ${alias}`
|
|
116
|
+
},
|
|
117
|
+
jsonExtractElement: (alias, subPath) => {
|
|
118
|
+
const parts = subPath.split(".")
|
|
119
|
+
if (parts.length === 1) return `${alias}->>'${parts[0]}'`
|
|
120
|
+
const last = parts.pop()!
|
|
121
|
+
return `${alias}${parts.map((p) => `->'${p}'`).join("")}->>'${last}'`
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function logQuery(q: { sql: string; params: unknown[] }) {
|
|
126
|
+
return InfraLogger
|
|
127
|
+
.logDebug("sql query")
|
|
128
|
+
.pipe(Effect.annotateLogs({
|
|
129
|
+
query: q.sql,
|
|
130
|
+
parameters: JSON.stringify(q.params, undefined, 2)
|
|
131
|
+
}))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const dottedToJsonPath = (path: string) =>
|
|
135
|
+
path
|
|
136
|
+
.split(".")
|
|
137
|
+
.filter((p) => p !== "-1")
|
|
138
|
+
.join(".")
|
|
139
|
+
|
|
140
|
+
export function buildWhereSQLQuery(
|
|
141
|
+
dialect: SQLDialect,
|
|
142
|
+
idKey: PropertyKey,
|
|
143
|
+
filter: readonly FilterResult[],
|
|
144
|
+
tableName: string,
|
|
145
|
+
defaultValues: Record<string, unknown>,
|
|
146
|
+
select?: NonEmptyReadonlyArray<string | { key: string; subKeys: readonly string[] }>,
|
|
147
|
+
order?: NonEmptyReadonlyArray<{ key: string; direction: "ASC" | "DESC" }>,
|
|
148
|
+
skip?: number,
|
|
149
|
+
limit?: number
|
|
150
|
+
) {
|
|
151
|
+
const params: unknown[] = []
|
|
152
|
+
let paramIndex = 1
|
|
153
|
+
|
|
154
|
+
const addParam = (value: unknown): string => {
|
|
155
|
+
params.push(value)
|
|
156
|
+
return dialect.placeholder(paramIndex++)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const fieldExpr = (path: string, relation?: string): string => {
|
|
160
|
+
if (path === idKey || path === "id") return "id"
|
|
161
|
+
if (relation && path.includes(".-1.")) {
|
|
162
|
+
const subPath = path.split(".-1.")[1]!
|
|
163
|
+
if (subPath.endsWith(".length")) {
|
|
164
|
+
// TODO: array length inside relation element
|
|
165
|
+
return dialect.jsonExtractElement(`_${relation}`, subPath.slice(0, -".length".length))
|
|
166
|
+
}
|
|
167
|
+
return dialect.jsonExtractElement(`_${relation}`, subPath)
|
|
168
|
+
}
|
|
169
|
+
if (path.endsWith(".length")) {
|
|
170
|
+
const arrPath = dottedToJsonPath(path.slice(0, -".length".length))
|
|
171
|
+
return dialect.arrayLength(arrPath)
|
|
172
|
+
}
|
|
173
|
+
const jsonPath = dottedToJsonPath(path)
|
|
174
|
+
const expr = dialect.jsonExtract(jsonPath)
|
|
175
|
+
const topKey = path.split(".")[0]!
|
|
176
|
+
if (topKey in defaultValues) {
|
|
177
|
+
return `COALESCE(${expr}, ${addParam(defaultValues[topKey])})`
|
|
178
|
+
}
|
|
179
|
+
return expr
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const statement = (x: FilterR, relation?: string): string => {
|
|
183
|
+
const resolvedPath = x.path === idKey ? "id" : x.path
|
|
184
|
+
const k = fieldExpr(resolvedPath, relation)
|
|
185
|
+
|
|
186
|
+
switch (x.op) {
|
|
187
|
+
case "in": {
|
|
188
|
+
const vals = x.value as unknown as readonly unknown[]
|
|
189
|
+
const hasNull = vals.some((v) => v == null)
|
|
190
|
+
const nonNullVals = vals.filter((v) => v != null)
|
|
191
|
+
const parts: string[] = []
|
|
192
|
+
if (nonNullVals.length > 0) {
|
|
193
|
+
const placeholders = nonNullVals.map((v) => addParam(v))
|
|
194
|
+
parts.push(`${k} IN (${placeholders.join(", ")})`)
|
|
195
|
+
}
|
|
196
|
+
if (hasNull) parts.push(`${k} IS NULL`)
|
|
197
|
+
return parts.length > 1 ? `(${parts.join(" OR ")})` : parts[0] ?? "1=0"
|
|
198
|
+
}
|
|
199
|
+
case "notIn": {
|
|
200
|
+
const vals = x.value as unknown as readonly unknown[]
|
|
201
|
+
const hasNull = vals.some((v) => v == null)
|
|
202
|
+
const nonNullVals = vals.filter((v) => v != null)
|
|
203
|
+
const parts: string[] = []
|
|
204
|
+
if (nonNullVals.length > 0) {
|
|
205
|
+
const placeholders = nonNullVals.map((v) => addParam(v))
|
|
206
|
+
parts.push(`${k} NOT IN (${placeholders.join(", ")})`)
|
|
207
|
+
}
|
|
208
|
+
if (hasNull) parts.push(`${k} IS NOT NULL`)
|
|
209
|
+
return parts.length > 1 ? `(${parts.join(" AND ")})` : parts[0] ?? "1=1"
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case "includes": {
|
|
213
|
+
const arrPath = dottedToJsonPath(resolvedPath)
|
|
214
|
+
const v = addParam(x.value)
|
|
215
|
+
return dialect.jsonArrayContains(arrPath, v)
|
|
216
|
+
}
|
|
217
|
+
case "notIncludes": {
|
|
218
|
+
const arrPath = dottedToJsonPath(resolvedPath)
|
|
219
|
+
const v = addParam(x.value)
|
|
220
|
+
return dialect.jsonArrayNotContains(arrPath, v)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
case "includes-any": {
|
|
224
|
+
const arrPath = dottedToJsonPath(resolvedPath)
|
|
225
|
+
const vals = x.value as unknown as readonly unknown[]
|
|
226
|
+
const placeholders = vals.map((v) => addParam(JSON.stringify(v)))
|
|
227
|
+
return dialect.jsonArrayContainsAny(arrPath, placeholders)
|
|
228
|
+
}
|
|
229
|
+
case "notIncludes-any": {
|
|
230
|
+
const arrPath = dottedToJsonPath(resolvedPath)
|
|
231
|
+
const vals = x.value as unknown as readonly unknown[]
|
|
232
|
+
const placeholders = vals.map((v) => addParam(JSON.stringify(v)))
|
|
233
|
+
return dialect.jsonArrayNotContainsAny(arrPath, placeholders)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case "includes-all": {
|
|
237
|
+
const arrPath = dottedToJsonPath(resolvedPath)
|
|
238
|
+
const vals = x.value as unknown as readonly unknown[]
|
|
239
|
+
const placeholders = vals.map((v) => addParam(JSON.stringify(v)))
|
|
240
|
+
return dialect.jsonArrayContainsAll(arrPath, placeholders)
|
|
241
|
+
}
|
|
242
|
+
case "notIncludes-all": {
|
|
243
|
+
const arrPath = dottedToJsonPath(resolvedPath)
|
|
244
|
+
const vals = x.value as unknown as readonly unknown[]
|
|
245
|
+
const placeholders = vals.map((v) => addParam(JSON.stringify(v)))
|
|
246
|
+
return dialect.jsonArrayNotContainsAll(arrPath, placeholders)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case "contains": {
|
|
250
|
+
const v = addParam(`%${x.value}%`)
|
|
251
|
+
return dialect.caseInsensitiveLike(k, v)
|
|
252
|
+
}
|
|
253
|
+
case "notContains": {
|
|
254
|
+
const v = addParam(`%${x.value}%`)
|
|
255
|
+
return dialect.caseInsensitiveNotLike(k, v)
|
|
256
|
+
}
|
|
257
|
+
case "startsWith": {
|
|
258
|
+
const v = addParam(`${x.value}%`)
|
|
259
|
+
return dialect.caseInsensitiveLike(k, v)
|
|
260
|
+
}
|
|
261
|
+
case "notStartsWith": {
|
|
262
|
+
const v = addParam(`${x.value}%`)
|
|
263
|
+
return dialect.caseInsensitiveNotLike(k, v)
|
|
264
|
+
}
|
|
265
|
+
case "endsWith": {
|
|
266
|
+
const v = addParam(`%${x.value}`)
|
|
267
|
+
return dialect.caseInsensitiveLike(k, v)
|
|
268
|
+
}
|
|
269
|
+
case "notEndsWith": {
|
|
270
|
+
const v = addParam(`%${x.value}`)
|
|
271
|
+
return dialect.caseInsensitiveNotLike(k, v)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
case "lt": {
|
|
275
|
+
const v = addParam(x.value)
|
|
276
|
+
return `${k} < ${v}`
|
|
277
|
+
}
|
|
278
|
+
case "lte": {
|
|
279
|
+
const v = addParam(x.value)
|
|
280
|
+
return `${k} <= ${v}`
|
|
281
|
+
}
|
|
282
|
+
case "gt": {
|
|
283
|
+
const v = addParam(x.value)
|
|
284
|
+
return `${k} > ${v}`
|
|
285
|
+
}
|
|
286
|
+
case "gte": {
|
|
287
|
+
const v = addParam(x.value)
|
|
288
|
+
return `${k} >= ${v}`
|
|
289
|
+
}
|
|
290
|
+
case "neq": {
|
|
291
|
+
if (x.value === null) return `${k} IS NOT NULL`
|
|
292
|
+
const v = addParam(x.value)
|
|
293
|
+
return `${k} <> ${v}`
|
|
294
|
+
}
|
|
295
|
+
case undefined:
|
|
296
|
+
case "eq": {
|
|
297
|
+
if (x.value === null) return `${k} IS NULL`
|
|
298
|
+
const v = addParam(x.value)
|
|
299
|
+
return `${k} = ${v}`
|
|
300
|
+
}
|
|
301
|
+
default:
|
|
302
|
+
return assertUnreachable(x.op)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const flipOps = {
|
|
307
|
+
gt: "lt",
|
|
308
|
+
lt: "gt",
|
|
309
|
+
gte: "lte",
|
|
310
|
+
lte: "gte",
|
|
311
|
+
contains: "notContains",
|
|
312
|
+
notContains: "contains",
|
|
313
|
+
startsWith: "notStartsWith",
|
|
314
|
+
notStartsWith: "startsWith",
|
|
315
|
+
endsWith: "notEndsWith",
|
|
316
|
+
notEndsWith: "endsWith",
|
|
317
|
+
eq: "neq",
|
|
318
|
+
neq: "eq",
|
|
319
|
+
includes: "notIncludes",
|
|
320
|
+
notIncludes: "includes",
|
|
321
|
+
"includes-any": "notIncludes-any",
|
|
322
|
+
"notIncludes-any": "includes-any",
|
|
323
|
+
"includes-all": "notIncludes-all",
|
|
324
|
+
"notIncludes-all": "includes-all",
|
|
325
|
+
in: "notIn",
|
|
326
|
+
notIn: "in"
|
|
327
|
+
} satisfies Record<Ops, Ops>
|
|
328
|
+
|
|
329
|
+
const flippies = {
|
|
330
|
+
and: "or",
|
|
331
|
+
or: "and"
|
|
332
|
+
} satisfies Record<"and" | "or", "and" | "or">
|
|
333
|
+
|
|
334
|
+
const flip = (every: boolean) => (_: FilterResult): FilterResult =>
|
|
335
|
+
every
|
|
336
|
+
? _.t === "where" || _.t === "or" || _.t === "and"
|
|
337
|
+
? { ..._, t: _.t === "where" ? _.t : flippies[_.t], op: flipOps[_.op] }
|
|
338
|
+
: _
|
|
339
|
+
: _
|
|
340
|
+
|
|
341
|
+
const wrapRelation = (rel: string, inner: string, every: boolean): string => {
|
|
342
|
+
const from = dialect.jsonEachFrom(rel, `_${rel}`)
|
|
343
|
+
return every
|
|
344
|
+
? `NOT EXISTS(SELECT 1 FROM ${from} WHERE NOT (${inner}))`
|
|
345
|
+
: `EXISTS(SELECT 1 FROM ${from} WHERE ${inner})`
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const print = (state: readonly FilterResult[], isRelation: string | null, every: boolean): string => {
|
|
349
|
+
let s = ""
|
|
350
|
+
for (const e of state) {
|
|
351
|
+
switch (e.t) {
|
|
352
|
+
case "where":
|
|
353
|
+
s += statement(e, isRelation ?? undefined)
|
|
354
|
+
break
|
|
355
|
+
case "or":
|
|
356
|
+
s += ` OR ${statement(e, isRelation ?? undefined)}`
|
|
357
|
+
break
|
|
358
|
+
case "and":
|
|
359
|
+
s += ` AND ${statement(e, isRelation ?? undefined)}`
|
|
360
|
+
break
|
|
361
|
+
case "or-scope": {
|
|
362
|
+
if (!every) every = e.relation === "every"
|
|
363
|
+
const rel = isRelationCheck(e.result, isRelation)
|
|
364
|
+
if (rel) {
|
|
365
|
+
s += isRelation
|
|
366
|
+
? ` OR (${print(e.result.map(flip(every)), rel, every)})`
|
|
367
|
+
: ` OR ${wrapRelation(rel, print(e.result.map(flip(every)), rel, every), every)}`
|
|
368
|
+
} else {
|
|
369
|
+
s += ` OR (${print(e.result, null, every)})`
|
|
370
|
+
}
|
|
371
|
+
break
|
|
372
|
+
}
|
|
373
|
+
case "and-scope": {
|
|
374
|
+
if (!every) every = e.relation === "every"
|
|
375
|
+
const rel = isRelationCheck(e.result, isRelation)
|
|
376
|
+
if (rel) {
|
|
377
|
+
s += isRelation
|
|
378
|
+
? ` AND (${print(e.result.map(flip(every)), rel, every)})`
|
|
379
|
+
: ` AND ${wrapRelation(rel, print(e.result.map(flip(every)), rel, every), every)}`
|
|
380
|
+
} else {
|
|
381
|
+
s += ` AND (${print(e.result, null, every)})`
|
|
382
|
+
}
|
|
383
|
+
break
|
|
384
|
+
}
|
|
385
|
+
case "where-scope": {
|
|
386
|
+
if (!every) every = e.relation === "every"
|
|
387
|
+
const rel = isRelationCheck(e.result, isRelation)
|
|
388
|
+
if (rel) {
|
|
389
|
+
s += isRelation
|
|
390
|
+
? `(${print(e.result.map(flip(every)), rel, every)})`
|
|
391
|
+
: wrapRelation(rel, print(e.result.map(flip(every)), rel, every), every)
|
|
392
|
+
} else {
|
|
393
|
+
s += `(${print(e.result, null, every)})`
|
|
394
|
+
}
|
|
395
|
+
break
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return s
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const getSelectExpr = (): string => {
|
|
403
|
+
if (!select) return "id, _etag, data"
|
|
404
|
+
const fields = select.map((s) => {
|
|
405
|
+
if (typeof s === "string") {
|
|
406
|
+
if (s === idKey || s === "id") return `id`
|
|
407
|
+
if (s === "_etag") return `_etag`
|
|
408
|
+
return `${dialect.jsonExtractJson(s)} AS "${s}"`
|
|
409
|
+
}
|
|
410
|
+
return `${dialect.jsonExtractJson(s.key)} AS "${s.key}"`
|
|
411
|
+
})
|
|
412
|
+
return fields.join(", ")
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const whereClause = filter.length
|
|
416
|
+
? `WHERE ${print([{ t: "where-scope", result: filter, relation: "some" }], null, false)}`
|
|
417
|
+
: ""
|
|
418
|
+
|
|
419
|
+
const orderClause = order
|
|
420
|
+
? `ORDER BY ${order.map((_) => `${fieldExpr(_.key)} ${_.direction}`).join(", ")}`
|
|
421
|
+
: ""
|
|
422
|
+
|
|
423
|
+
const limitClause = limit !== undefined || skip !== undefined
|
|
424
|
+
? `LIMIT ${addParam(limit ?? 999999)} OFFSET ${addParam(skip ?? 0)}`
|
|
425
|
+
: ""
|
|
426
|
+
|
|
427
|
+
const sql = `SELECT ${getSelectExpr()} FROM "${tableName}" ${whereClause} ${orderClause} ${limitClause}`.trim()
|
|
428
|
+
|
|
429
|
+
return { sql, params }
|
|
430
|
+
}
|