1/**
2 * Low-level SQL statement and fragment primitives.
3 *
4 * `SqlClient` uses this module to build executable, parameterized SQL from
5 * reusable fragments. A statement can be executed, streamed, run without row
6 * transformation, or compiled to SQL text and parameters for a specific
7 * dialect. The module also contains helpers for identifiers, parameters,
8 * inserts, updates, custom dialect fragments, statement compilation, and row
9 * transformation.
10 *
11 * @stability unstable
12 * @since 4.0.0
13 */
14import { Clock } from "../Clock.ts"
15import * as Context from "../Context.ts"
16import * as Effect from "../Effect.ts"
17import * as Effectable from "../Effectable.ts"
18import type * as Fiber from "../Fiber.ts"
19import { constUndefined } from "../Function.ts"
20import * as internalEffect from "../internal/effect.ts"
21import * as InternalRecord from "../internal/record.ts"
22import { hasProperty } from "../Predicate.ts"
23import { TracerTimingEnabled } from "../References.ts"
24import * as Stream from "../Stream.ts"
25import * as Tracer from "../Tracer.ts"
26import type { Acquirer, Borrower, Connection, Row } from "./SqlConnection.ts"
27import type { SqlError } from "./SqlError.ts"
28
29const FragmentTypeId = "~effect/sql/Statement/Fragment"
30
31/**
32 * Composable SQL fragment represented as low-level segments that can be
33 * interpolated into statements.
34 *
35 * @stability unstable
36 * @category models
37 * @since 4.0.0
38 */
39export interface Fragment {
40 readonly [FragmentTypeId]: typeof FragmentTypeId
41 readonly segments: ReadonlyArray<Segment>
42}
43
44/**
45 * Constructs a SQL `Fragment` from low-level statement segments.
46 *
47 * @stability unstable
48 * @category constructors
49 * @since 4.0.0
50 */
51export const fragment = (
52 segments: ReadonlyArray<Segment>
53): Fragment => ({
54 [FragmentTypeId]: FragmentTypeId,
55 segments
56})
57
58/**
59 * Supported SQL dialect identifiers used by statement compilers.
60 *
61 * @stability unstable
62 * @category models
63 * @since 4.0.0
64 */
65export type Dialect = "sqlite" | "pg" | "mysql" | "mssql" | "clickhouse"
66
67/**
68 * Executable SQL statement that is also a `Fragment` and `Effect`, with helpers
69 * for raw execution, streaming, value rows, unprepared execution, no-transform
70 * execution, and compilation.
71 *
72 * @stability unstable
73 * @category models
74 * @since 4.0.0
75 */
76export interface Statement<A> extends Fragment, Effect.Effect<ReadonlyArray<A>, SqlError> {
77 readonly raw: Effect.Effect<unknown, SqlError>
78 readonly withoutTransform: Effect.Effect<ReadonlyArray<A>, SqlError>
79 readonly stream: Stream.Stream<A, SqlError>
80 readonly values: Effect.Effect<ReadonlyArray<ReadonlyArray<unknown>>, SqlError>
81 readonly valuesUnprepared: Effect.Effect<ReadonlyArray<ReadonlyArray<unknown>>, SqlError>
82 readonly unprepared: Effect.Effect<ReadonlyArray<A>, SqlError>
83 readonly compile: (withoutTransform?: boolean | undefined) => readonly [
84 sql: string,
85 params: ReadonlyArray<unknown>
86 ]
87}
88
89/**
90 * Hook that can rewrite or wrap a `Statement` before execution, using the
91 * current SQL constructor, fiber, and tracing span.
92 *
93 * @stability unstable
94 * @category models
95 * @since 4.0.0
96 */
97export type Transformer = (
98 self: Statement<unknown>,
99 sql: Constructor,
100 fiber: Fiber.Fiber<unknown, unknown>,
101 span: Tracer.Span
102) => Effect.Effect<Statement<unknown>>
103
104/**
105 * Context reference for an optional current SQL statement transformer applied
106 * before statement execution.
107 *
108 * @stability unstable
109 * @category services
110 * @since 4.0.0
111 */
112export const CurrentTransformer = Context.Reference<Transformer | undefined>("effect/sql/CurrentTransformer", {
113 defaultValue: constUndefined
114})
115
116/**
117 * Parents driver spans under `sql.execute` for every client in the current scope,
118 * including acquisition and stream pulls. Defaults to `false`; ignored when tracing is disabled.
119 *
120 * @stability unstable
121 * @category services
122 * @since 4.0.0
123 */
124export const SpanPropagationEnabled = Context.Reference<boolean>("effect/sql/SpanPropagationEnabled", {
125 defaultValue: () => false
126})
127
128/**
129 * Returns `true` when a value is a SQL `Fragment`.
130 *
131 * @stability unstable
132 * @category guards
133 * @since 4.0.0
134 */
135export const isFragment = (u: unknown): u is Fragment => hasProperty(u, FragmentTypeId)
136
137/**
138 * Creates a type guard for custom SQL segments with the specified custom kind.
139 *
140 * @stability unstable
141 * @category guards
142 * @since 4.0.0
143 */
144export const isCustom = <A extends Custom<any, any, any, any>>(
145 kind: A["kind"]
146) =>
147(u: unknown): u is A => hasProperty(u, "_tag") && u._tag === "Custom" && (u as any).kind === kind
148
149/**
150 * Union of low-level segment types that make up a SQL `Fragment`.
151 *
152 * @stability unstable
153 * @category models
154 * @since 4.0.0
155 */
156export type Segment =
157 | Literal
158 | Identifier
159 | Parameter
160 | ArrayHelper
161 | RecordInsertHelper
162 | RecordUpdateHelper
163 | RecordUpdateHelperSingle
164 | Custom<any, any, any, any>
165
166/**
167 * Raw SQL literal segment. The literal text is inserted directly into the
168 * compiled SQL, while optional `params` are appended as bind parameters.
169 *
170 * @stability unstable
171 * @category models
172 * @since 4.0.0
173 */
174export interface Literal {
175 readonly _tag: "Literal"
176 readonly value: string
177 readonly params?: ReadonlyArray<unknown> | undefined
178}
179
180/**
181 * Constructs a raw SQL literal segment. The literal text is not escaped, so use
182 * bound parameters for untrusted values.
183 *
184 * @stability unstable
185 * @category constructors
186 * @since 4.0.0
187 */
188export const literal = (value: string, params?: ReadonlyArray<unknown> | undefined): Literal => ({
189 _tag: "Literal",
190 value,
191 params
192})
193
194/**
195 * SQL identifier segment whose value is escaped by the active dialect compiler.
196 *
197 * @stability unstable
198 * @category models
199 * @since 4.0.0
200 */
201export interface Identifier {
202 readonly _tag: "Identifier"
203 readonly value: string
204}
205
206/**
207 * Constructs a SQL identifier segment that will be escaped by the active
208 * compiler.
209 *
210 * @stability unstable
211 * @category constructors
212 * @since 4.0.0
213 */
214export const identifier = (value: string): Identifier => ({
215 _tag: "Identifier",
216 value
217})
218
219/**
220 * Bound parameter segment whose value is emitted as a dialect-specific
221 * placeholder and bind value.
222 *
223 * @stability unstable
224 * @category models
225 * @since 4.0.0
226 */
227export interface Parameter {
228 readonly _tag: "Parameter"
229 readonly value: unknown
230}
231
232/**
233 * Constructs a bound parameter segment for a statement value.
234 *
235 * @stability unstable
236 * @category constructors
237 * @since 4.0.0
238 */
239export const parameter = (value: unknown): Parameter => ({
240 _tag: "Parameter",
241 value
242})
243
244/**
245 * Helper segment for compiling an array of values, commonly used to produce
246 * placeholder lists for `IN` clauses.
247 *
248 * @stability unstable
249 * @category models
250 * @since 4.0.0
251 */
252export interface ArrayHelper {
253 readonly _tag: "ArrayHelper"
254 readonly value: ReadonlyArray<unknown | Fragment>
255}
256
257/**
258 * Constructs an `ArrayHelper` segment for an array of values or fragments.
259 *
260 * @stability unstable
261 * @category constructors
262 * @since 4.0.0
263 */
264export const arrayHelper = (value: ReadonlyArray<unknown | Fragment>): ArrayHelper => ({
265 _tag: "ArrayHelper",
266 value
267})
268
269/**
270 * Helper segment for compiling one or more record objects into an INSERT
271 * column/value clause, with optional returning output.
272 *
273 * @stability unstable
274 * @category models
275 * @since 4.0.0
276 */
277export interface RecordInsertHelper {
278 readonly _tag: "RecordInsertHelper"
279 readonly value: ReadonlyArray<Record<string, unknown>>
280 /**
281 * @internal
282 */
283 readonly returningIdentifier: string | Fragment | undefined
284 readonly returning: (sql: string | Identifier | Fragment) => RecordInsertHelper
285}
286
287const RecordInsertHelperProto = {
288 _tag: "RecordInsertHelper" as const,
289 returning(this: RecordInsertHelper, sql: string | Identifier | Fragment) {
290 const self = Object.create(Object.getPrototypeOf(this))
291 Object.assign(self, this, {
292 returningIdentifier: typeof sql === "string" || isFragment(sql) ? sql : fragment([sql])
293 })
294 return self
295 }
296}
297
298/**
299 * Constructs a `RecordInsertHelper` from one or more row objects.
300 *
301 * @stability unstable
302 * @category constructors
303 * @since 4.0.0
304 */
305export const recordInsertHelper = (
306 value: ReadonlyArray<Record<string, unknown>>
307): RecordInsertHelper =>
308 Object.assign(Object.create(RecordInsertHelperProto), {
309 value,
310 returningIdentifier: undefined
311 })
312
313/**
314 * Helper segment for compiling multi-row update values with a table alias and
315 * optional returning output.
316 *
317 * @stability unstable
318 * @category models
319 * @since 4.0.0
320 */
321export interface RecordUpdateHelper {
322 readonly _tag: "RecordUpdateHelper"
323 readonly value: ReadonlyArray<Record<string, unknown>>
324 readonly alias: string
325 /**
326 * @internal
327 */
328 readonly returningIdentifier: string | Fragment | undefined
329 readonly returning: (sql: string | Identifier | Fragment) => RecordUpdateHelper
330}
331
332const RecordUpdateHelperProto = {
333 ...RecordInsertHelperProto,
334 _tag: "RecordUpdateHelper" as const
335}
336
337/**
338 * Constructs a `RecordUpdateHelper` for multi-row update compilation using the
339 * provided alias.
340 *
341 * @stability unstable
342 * @category constructors
343 * @since 4.0.0
344 */
345export const recordUpdateHelper = (
346 value: ReadonlyArray<Record<string, unknown>>,
347 alias: string
348): RecordUpdateHelper =>
349 Object.assign(Object.create(RecordUpdateHelperProto), {
350 value,
351 alias,
352 returningIdentifier: undefined
353 })
354
355/**
356 * Helper segment for compiling a single record into update assignments,
357 * omitting selected columns and optionally returning output.
358 *
359 * @stability unstable
360 * @category models
361 * @since 4.0.0
362 */
363export interface RecordUpdateHelperSingle {
364 readonly _tag: "RecordUpdateHelperSingle"
365 readonly value: Record<string, unknown>
366 readonly omit: ReadonlyArray<string>
367 /**
368 * @internal
369 */
370 readonly returningIdentifier: string | Fragment | undefined
371 readonly returning: (sql: string | Identifier | Fragment) => RecordUpdateHelperSingle
372}
373
374const RecordUpdateHelperSingleProto = {
375 ...RecordInsertHelperProto,
376 _tag: "RecordUpdateHelperSingle" as const
377}
378
379/**
380 * Constructs a `RecordUpdateHelperSingle` from a record and a list of columns
381 * to omit from the update.
382 *
383 * @stability unstable
384 * @category constructors
385 * @since 4.0.0
386 */
387export const recordUpdateHelperSingle = (
388 value: Record<string, unknown>,
389 omit: ReadonlyArray<string>
390): RecordUpdateHelperSingle =>
391 Object.assign(Object.create(RecordUpdateHelperSingleProto), {
392 value,
393 omit,
394 returningIdentifier: undefined
395 })
396
397/**
398 * Custom SQL segment identified by `kind` and interpreted by the compiler's
399 * `onCustom` callback.
400 *
401 * @stability unstable
402 * @category models
403 * @since 4.0.0
404 */
405export interface Custom<
406 T extends string = string,
407 A = void,
408 B = void,
409 C = void
410> {
411 readonly _tag: "Custom"
412 readonly kind: T
413 readonly paramA: A
414 readonly paramB: B
415 readonly paramC: C
416}
417
418/**
419 * Creates a constructor for custom SQL segments of a specific kind handled by
420 * the active compiler.
421 *
422 * @stability unstable
423 * @category constructors
424 * @since 4.0.0
425 */
426export const custom = <C extends Custom<any, any, any, any>>(
427 kind: C["kind"]
428) =>
429(
430 paramA: C["paramA"],
431 paramB: C["paramB"],
432 paramC: C["paramC"]
433): C => ({ _tag: "Custom", kind, paramA, paramB, paramC } as C)
434
435/**
436 * Names the primitive value categories recognized by SQL statement helpers and
437 * `primitiveKind`.
438 *
439 * @stability unstable
440 * @category models
441 * @since 4.0.0
442 */
443export type PrimitiveKind =
444 | "string"
445 | "number"
446 | "bigint"
447 | "boolean"
448 | "Date"
449 | "null"
450 | "Int8Array"
451 | "Uint8Array"
452
453/**
454 * Union of helper segment types accepted by the SQL statement constructor.
455 *
456 * @stability unstable
457 * @category models
458 * @since 4.0.0
459 */
460export type Helper =
461 | ArrayHelper
462 | RecordInsertHelper
463 | RecordUpdateHelper
464 | RecordUpdateHelperSingle
465 | Identifier
466 | Custom
467
468/**
469 * SQL tagged-template constructor and helper API for building parameterized
470 * statements, escaped identifiers, fragments, record helpers, and
471 * dialect-specific branches. Raw helpers such as `unsafe` and `literal` insert
472 * SQL text directly.
473 *
474 * @stability unstable
475 * @category models
476 * @since 4.0.0
477 */
478export interface Constructor {
479 <A extends object = Row>(
480 strings: TemplateStringsArray,
481 ...args: Array<any>
482 ): Statement<A>
483
484 (value: string): Identifier
485
486 /**
487 * Create unsafe SQL query
488 */
489 readonly unsafe: <A extends object>(
490 sql: string,
491 params?: ReadonlyArray<unknown> | undefined
492 ) => Statement<A>
493
494 readonly literal: (sql: string) => Fragment
495
496 readonly in: {
497 (value: ReadonlyArray<unknown>): ArrayHelper
498 (column: string, value: ReadonlyArray<unknown>): Fragment
499 }
500
501 readonly insert: {
502 (
503 value: ReadonlyArray<Record<string, unknown>>
504 ): RecordInsertHelper
505 (value: Record<string, unknown>): RecordInsertHelper
506 }
507
508 /** Update a single row */
509 readonly update: <A extends Record<string, unknown>>(
510 value: A,
511 omit?: ReadonlyArray<keyof A>
512 ) => RecordUpdateHelperSingle
513
514 /**
515 * Update multiple rows.
516 *
517 * **Gotchas**
518 *
519 * Not supported in sqlite.
520 */
521 readonly updateValues: (
522 value: ReadonlyArray<Record<string, unknown>>,
523 alias: string
524 ) => RecordUpdateHelper
525
526 /**
527 * Create an `AND` chain for a where clause
528 */
529 readonly and: (clauses: ReadonlyArray<string | Fragment>) => Fragment
530
531 /**
532 * Create an `OR` chain for a where clause
533 */
534 readonly or: (clauses: ReadonlyArray<string | Fragment>) => Fragment
535
536 /**
537 * Create comma seperated values, with an optional prefix.
538 *
539 * **When to use**
540 *
541 * Use when `ORDER BY` and `GROUP BY` clauses.
542 */
543 readonly csv: {
544 (values: ReadonlyArray<string | Fragment>): Fragment
545 (prefix: string, values: ReadonlyArray<string | Fragment>): Fragment
546 }
547
548 readonly join: (
549 literal: string,
550 addParens?: boolean,
551 fallback?: string
552 ) => (clauses: ReadonlyArray<string | Fragment>) => Fragment
553
554 readonly onDialect: <A, B, C, D, E>(options: {
555 readonly sqlite: () => A
556 readonly pg: () => B
557 readonly mysql: () => C
558 readonly mssql: () => D
559 readonly clickhouse: () => E
560 }) => A | B | C | D | E
561
562 readonly onDialectOrElse: <A, B = never, C = never, D = never, E = never, F = never>(options: {
563 readonly orElse: () => A
564 readonly sqlite?: () => B
565 readonly pg?: () => C
566 readonly mysql?: () => D
567 readonly mssql?: () => E
568 readonly clickhouse?: () => F
569 }) => A | B | C | D | E | F
570}
571
572/**
573 * Creates a cached SQL statement constructor from a connection acquirer,
574 * compiler, tracing attributes, and optional row transformation function.
575 *
576 * @stability unstable
577 * @category constructors
578 * @since 4.0.0
579 */
580export const make = (
581 acquirer: Acquirer,
582 compiler: Compiler,
583 spanAttributes: ReadonlyArray<readonly [string, unknown]>,
584 transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined,
585 borrower?: Borrower | undefined
586): Constructor => {
587 const cache = transformRows === undefined ? constructorCache.noTransforms : constructorCache.transforms
588 if (cache.has(acquirer)) {
589 return cache.get(acquirer)!
590 }
591 const self = Object.assign(
592 function sql(strings: unknown, ...args: Array<any>): any {
593 if (typeof strings === "string") {
594 return identifier(strings)
595 } else if (Array.isArray(strings) && "raw" in strings) {
596 return statement(
597 acquirer,
598 compiler,
599 strings as TemplateStringsArray,
600 args,
601 spanAttributes,
602 transformRows,
603 borrower
604 )
605 }
606
607 throw "absurd"
608 },
609 {
610 unsafe<A extends object = Row>(
611 sql: string,
612 params?: ReadonlyArray<unknown>
613 ) {
614 return makeUnsafe<A>(
615 [literal(sql, params)],
616 acquirer,
617 compiler,
618 spanAttributes,
619 transformRows,
620 borrower
621 )
622 },
623 literal(sql: string) {
624 return fragment([literal(sql)])
625 },
626 in: in_,
627 insert(value: any) {
628 return recordInsertHelper(
629 Array.isArray(value) ? value : [value]
630 )
631 },
632 update(value: any, omit: any) {
633 return recordUpdateHelperSingle(value, omit ?? [])
634 },
635 updateValues(value: any, alias: any) {
636 return recordUpdateHelper(value, alias)
637 },
638 and,
639 or,
640 csv,
641 join,
642 onDialect(options: Record<Dialect, any>) {
643 return options[compiler.dialect]()
644 },
645 onDialectOrElse(options: any) {
646 return options[compiler.dialect] !== undefined ? options[compiler.dialect]() : options.orElse()
647 }
648 }
649 )
650
651 cache.set(acquirer, self)
652
653 return self
654}
655
656const constructorCache = {
657 transforms: new WeakMap<Acquirer, Constructor>(),
658 noTransforms: new WeakMap<Acquirer, Constructor>()
659}
660
661/**
662 * Builds a `Statement` from template strings and arguments, preserving
663 * fragments and helper segments while converting ordinary interpolated values
664 * into bound parameters.
665 *
666 * @stability unstable
667 * @category constructors
668 * @since 4.0.0
669 */
670export const statement = <A = Row>(
671 acquirer: Acquirer,
672 compiler: Compiler,
673 strings: TemplateStringsArray,
674 args: Array<any>,
675 spanAttributes: ReadonlyArray<readonly [string, unknown]>,
676 transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined,
677 borrower?: Borrower | undefined
678): Statement<A> => {
679 const segments: Array<Segment> = strings[0].length > 0 ? [literal(strings[0])] : []
680
681 for (let i = 0; i < args.length; i++) {
682 const arg = args[i]
683
684 if (isFragment(arg)) {
685 segments.push(...arg.segments)
686 } else if (isSegment(arg)) {
687 segments.push(arg)
688 } else {
689 segments.push(parameter(arg))
690 }
691
692 if (strings[i + 1].length > 0) {
693 segments.push(literal(strings[i + 1]))
694 }
695 }
696
697 return makeUnsafe(segments, acquirer, compiler, spanAttributes, transformRows, borrower)
698}
699
700/**
701 * Creates a helper that joins SQL clauses with a literal separator, optionally
702 * wrapping multiple clauses in parentheses and using a fallback for an empty
703 * list.
704 *
705 * @stability unstable
706 * @category constructors
707 * @since 4.0.0
708 */
709export function join(lit: string, addParens = true, fallback = "") {
710 const literalStatement = literal(lit)
711 const fallbackFragment = fragment([literal(fallback)])
712
713 return (clauses: ReadonlyArray<string | Fragment>): Fragment => {
714 if (clauses.length === 0) {
715 return fallbackFragment
716 } else if (clauses.length === 1) {
717 return fragment(convertLiteralOrFragment(clauses[0]))
718 }
719
720 const segments: Array<Segment> = []
721
722 if (addParens) {
723 segments.push(literal("("))
724 }
725
726 segments.push.apply(segments, convertLiteralOrFragment(clauses[0]))
727
728 for (let i = 1; i < clauses.length; i++) {
729 segments.push(literalStatement)
730 segments.push.apply(segments, convertLiteralOrFragment(clauses[i]))
731 }
732
733 if (addParens) {
734 segments.push(literal(")"))
735 }
736
737 return fragment(segments)
738 }
739}
740
741/**
742 * Combines clauses with `AND`, parenthesizing multiple clauses and returning
743 * `1=1` when the list is empty.
744 *
745 * @stability unstable
746 * @category constructors
747 * @since 4.0.0
748 */
749export const and: (clauses: ReadonlyArray<string | Fragment>) => Fragment = join(" AND ", true, "1=1")
750
751/**
752 * Combines clauses with `OR`, parenthesizing multiple clauses and returning
753 * `1=1` when the list is empty.
754 *
755 * @stability unstable
756 * @category constructors
757 * @since 4.0.0
758 */
759export const or: (clauses: ReadonlyArray<string | Fragment>) => Fragment = join(" OR ", true, "1=1")
760
761/**
762 * Creates a comma-separated SQL fragment from values, optionally adding a
763 * prefix, and returns an empty fragment when no values are provided.
764 *
765 * @stability unstable
766 * @category constructors
767 * @since 4.0.0
768 */
769export const csv: {
770 (values: ReadonlyArray<string | Fragment>): Fragment
771 (prefix: string, values: ReadonlyArray<string | Fragment>): Fragment
772} = function(
773 ...args:
774 | [values: ReadonlyArray<string | Fragment>]
775 | [prefix: string, values: ReadonlyArray<string | Fragment>]
776) {
777 if (args[args.length - 1].length === 0) {
778 return emptyFragment
779 }
780
781 if (args.length === 1) {
782 return csvRaw(args[0])
783 }
784
785 return fragment([
786 literal(`${args[0]} `),
787 ...csvRaw(args[1]).segments
788 ])
789}
790
791const csvRaw = join(",", false)
792const emptyFragment = fragment([literal("")])
793
794/**
795 * Dialect-specific compiler that converts a SQL `Fragment` into SQL text and
796 * bind parameters, with a no-transform variant.
797 *
798 * @stability unstable
799 * @category models
800 * @since 4.0.0
801 */
802export interface Compiler {
803 readonly dialect: Dialect
804 readonly compile: (
805 statement: Fragment,
806 withoutTransform: boolean
807 ) => readonly [sql: string, params: ReadonlyArray<unknown>]
808 readonly withoutTransform: this
809}
810
811/**
812 * Callbacks used by `makeCompiler` to render dialect placeholders,
813 * identifiers, insert helpers, update helpers, and custom SQL segments.
814 *
815 * @stability unstable
816 * @category models
817 * @since 4.0.0
818 */
819export type CompilerOptions<C extends Custom<any, any, any, any> = any> = {
820 readonly dialect: Dialect
821 readonly placeholder: (index: number, value: unknown) => string
822 readonly onIdentifier: (value: string, withoutTransform: boolean) => string
823 readonly onRecordUpdate: (
824 placeholders: string,
825 alias: string,
826 columns: string,
827 values: ReadonlyArray<ReadonlyArray<unknown>>,
828 returning: readonly [sql: string, params: ReadonlyArray<unknown>] | undefined
829 ) => readonly [sql: string, params: ReadonlyArray<unknown>]
830 readonly onCustom: (
831 type: C,
832 placeholder: (u: unknown) => string,
833 withoutTransform: boolean
834 ) => readonly [sql: string, params: ReadonlyArray<unknown>]
835 readonly onInsert?: (
836 columns: ReadonlyArray<string>,
837 placeholders: string,
838 values: ReadonlyArray<ReadonlyArray<unknown>>,
839 returning: readonly [sql: string, params: ReadonlyArray<unknown>] | undefined
840 ) => readonly [sql: string, binds: ReadonlyArray<unknown>]
841 readonly onRecordUpdateSingle?: (
842 columns: ReadonlyArray<string>,
843 values: ReadonlyArray<unknown>,
844 returning: readonly [sql: string, params: ReadonlyArray<unknown>] | undefined
845 ) => readonly [sql: string, params: ReadonlyArray<unknown>]
846}
847
848/**
849 * Creates a dialect-specific SQL `Compiler` from rendering callbacks.
850 *
851 * @stability unstable
852 * @category constructors
853 * @since 4.0.0
854 */
855export const makeCompiler = <C extends Custom<any, any, any, any> = any>(
856 options: CompilerOptions<C>
857): Compiler => {
858 const self = Object.create(CompilerProto)
859 self.options = options
860 self.dialect = options.dialect
861 self.disableTransforms = false
862 self.statementCache = new WeakMap<Fragment, CompiledStatement>()
863 self.statementCacheNoTransform = new WeakMap<Fragment, CompiledStatement>()
864 return self
865}
866
867type CompiledStatement = readonly [sql: string, binds: ReadonlyArray<unknown>]
868
869interface CompilerImpl extends Compiler {
870 readonly options: CompilerOptions
871 readonly disableTransforms: boolean
872 readonly statementCache: WeakMap<Fragment, CompiledStatement>
873 readonly statementCacheNoTransform: WeakMap<Fragment, CompiledStatement>
874 compile(
875 statement: Fragment,
876 withoutTransform?: boolean,
877 placeholderOverride?: (u: unknown) => string
878 ): CompiledStatement
879}
880
881const CompilerProto = {
882 compile(
883 this: CompilerImpl,
884 statement: Fragment,
885 withoutTransform = false,
886 placeholderOverride?: (u: unknown) => string
887 ): readonly [sql: string, binds: ReadonlyArray<unknown>] {
888 const opts = this.options
889 withoutTransform = withoutTransform || this.disableTransforms
890 const cache = withoutTransform ? this.statementCacheNoTransform : this.statementCache
891 const cached = cache.get(statement)
892 if (cached !== undefined && placeholderOverride === undefined) {
893 return cached
894 }
895
896 const segments = statement.segments
897 const len = segments.length
898
899 let sql = ""
900 const binds: Array<unknown> = []
901 let placeholderCount = 0
902 const placeholder = placeholderOverride ?? ((u: unknown) => opts.placeholder(++placeholderCount, u))
903 const placeholderNoIncrement = (u: unknown) => opts.placeholder(placeholderCount, u)
904 const placeholders = makePlaceholdersArray(placeholder)
905
906 for (let i = 0; i < len; i++) {
907 const segment = segments[i]
908
909 switch (segment._tag) {
910 case "Literal": {
911 sql += segment.value
912 if (segment.params) {
913 binds.push.apply(binds, segment.params as any)
914 }
915 break
916 }
917
918 case "Identifier": {
919 sql += opts.onIdentifier(segment.value, withoutTransform)
920 break
921 }
922
923 case "Parameter": {
924 sql += placeholder(segment.value)
925 binds.push(segment.value)
926 break
927 }
928
929 case "ArrayHelper": {
930 sql += `(${placeholders(segment.value)})`
931 binds.push.apply(binds, segment.value as any)
932 break
933 }
934
935 case "RecordInsertHelper": {
936 const keys = Object.keys(segment.value[0])
937
938 if (opts.onInsert) {
939 const values: Array<ReadonlyArray<unknown>> = new Array(segment.value.length)
940 let placeholders = ""
941 for (let i = 0; i < segment.value.length; i++) {
942 const row: Array<unknown> = new Array(keys.length)
943 values[i] = row
944 placeholders += i === 0 ? "(" : ",("
945 for (let j = 0; j < keys.length; j++) {
946 const key = keys[j]
947 const value = segment.value[i][key]
948 const primitive = extractPrimitive(value, opts.onCustom, placeholderNoIncrement, withoutTransform)
949 row[j] = primitive
950 placeholders += j === 0 ? placeholder(value) : `,${placeholder(value)}`
951 }
952 placeholders += ")"
953 }
954 const [s, b] = opts.onInsert(
955 keys.map((_) => opts.onIdentifier(_, withoutTransform)),
956 placeholders,
957 values,
958 typeof segment.returningIdentifier === "string"
959 ? [segment.returningIdentifier, []]
960 : segment.returningIdentifier
961 ? this.compile(segment.returningIdentifier, withoutTransform, placeholder)
962 : undefined
963 )
964 sql += s
965 binds.push.apply(binds, b as any)
966 } else {
967 let placeholders = ""
968 for (let i = 0; i < segment.value.length; i++) {
969 placeholders += i === 0 ? "(" : ",("
970 for (let j = 0; j < keys.length; j++) {
971 const value = segment.value[i][keys[j]]
972 const primitive = extractPrimitive(value, opts.onCustom, placeholderNoIncrement, withoutTransform)
973 binds.push(primitive)
974 placeholders += j === 0 ? placeholder(value) : `,${placeholder(value)}`
975 }
976 placeholders += ")"
977 }
978 sql += `${
979 generateColumns(
980 keys,
981 opts.onIdentifier,
982 withoutTransform
983 )
984 } VALUES ${placeholders}`
985
986 if (typeof segment.returningIdentifier === "string") {
987 sql += ` RETURNING ${segment.returningIdentifier}`
988 } else if (segment.returningIdentifier) {
989 sql += " RETURNING "
990 const [s, b] = this.compile(segment.returningIdentifier, withoutTransform, placeholder)
991 sql += s
992 binds.push.apply(binds, b as any)
993 }
994 }
995 break
996 }
997
998 case "RecordUpdateHelperSingle": {
999 let keys = Object.keys(segment.value)
1000 if (segment.omit.length > 0) {
1001 keys = keys.filter((key) => !segment.omit.includes(key))
1002 }
1003 if (opts.onRecordUpdateSingle) {
1004 const [s, b] = opts.onRecordUpdateSingle(
1005 keys.map((_) => opts.onIdentifier(_, withoutTransform)),
1006 keys.map((key) =>
1007 extractPrimitive(
1008 segment.value[key],
1009 opts.onCustom,
1010 placeholderNoIncrement,
1011 withoutTransform
1012 )
1013 ),
1014 typeof segment.returningIdentifier === "string"
1015 ? [segment.returningIdentifier, []]
1016 : segment.returningIdentifier
1017 ? this.compile(segment.returningIdentifier, withoutTransform, placeholder)
1018 : undefined
1019 )
1020 sql += s
1021 binds.push.apply(binds, b as any)
1022 } else {
1023 for (let i = 0, len = keys.length; i < len; i++) {
1024 const column = opts.onIdentifier(keys[i], withoutTransform)
1025 if (i === 0) {
1026 sql += `${column} = ${placeholder(segment.value[keys[i]])}`
1027 } else {
1028 sql += `, ${column} = ${placeholder(segment.value[keys[i]])}`
1029 }
1030 binds.push(
1031 extractPrimitive(
1032 segment.value[keys[i]],
1033 opts.onCustom,
1034 placeholderNoIncrement,
1035 withoutTransform
1036 )
1037 )
1038 }
1039 if (typeof segment.returningIdentifier === "string") {
1040 if (this.dialect === "mssql") {
1041 sql += ` OUTPUT ${segment.returningIdentifier === "*" ? "INSERTED.*" : segment.returningIdentifier}`
1042 } else {
1043 sql += ` RETURNING ${segment.returningIdentifier}`
1044 }
1045 } else if (segment.returningIdentifier) {
1046 sql += this.dialect === "mssql" ? " OUTPUT " : " RETURNING "
1047 const [s, b] = this.compile(segment.returningIdentifier, withoutTransform, placeholder)
1048 sql += s
1049 binds.push.apply(binds, b as any)
1050 }
1051 }
1052 break
1053 }
1054
1055 case "RecordUpdateHelper": {
1056 const keys = Object.keys(segment.value[0])
1057 const values: Array<ReadonlyArray<unknown>> = new Array(segment.value.length)
1058 let placeholders = ""
1059 for (let i = 0; i < segment.value.length; i++) {
1060 const row: Array<unknown> = new Array(keys.length)
1061 values[i] = row
1062 placeholders += i === 0 ? "(" : ",("
1063 for (let j = 0; j < keys.length; j++) {
1064 const key = keys[j]
1065 const value = segment.value[i][key]
1066 row[j] = extractPrimitive(value, opts.onCustom, placeholderNoIncrement, withoutTransform)
1067 placeholders += j === 0 ? placeholder(value) : `,${placeholder(value)}`
1068 }
1069 placeholders += ")"
1070 }
1071 const [s, b] = opts.onRecordUpdate(
1072 placeholders,
1073 segment.alias,
1074 generateColumns(keys, opts.onIdentifier, withoutTransform),
1075 values,
1076 typeof segment.returningIdentifier === "string"
1077 ? [segment.returningIdentifier, []]
1078 : segment.returningIdentifier
1079 ? this.compile(segment.returningIdentifier, withoutTransform, placeholder)
1080 : undefined
1081 )
1082 sql += s
1083 binds.push.apply(binds, b as any)
1084 break
1085 }
1086
1087 case "Custom": {
1088 const [s, b] = opts.onCustom(segment, placeholder, withoutTransform)
1089 sql += s
1090 binds.push.apply(binds, b as any)
1091 break
1092 }
1093 }
1094 }
1095
1096 const result = [sql, binds] as const
1097 if (placeholderOverride !== undefined) {
1098 return result
1099 }
1100 cache.set(statement, result)
1101 return result
1102 },
1103
1104 get withoutTransform() {
1105 const self = Object.create(CompilerProto)
1106 Object.assign(self, this, {
1107 disableTransforms: true
1108 })
1109 return self
1110 }
1111}
1112
1113/**
1114 * Creates a SQLite compiler that uses `?` placeholders and quoted identifiers,
1115 * optionally transforming identifier names before escaping.
1116 *
1117 * @stability unstable
1118 * @category constructors
1119 * @since 4.0.0
1120 */
1121export const makeCompilerSqlite = (transform?: ((_: string) => string) | undefined): Compiler =>
1122 makeCompiler({
1123 dialect: "sqlite",
1124 placeholder(_) {
1125 return "?"
1126 },
1127 onIdentifier: transform ?
1128 function(value, withoutTransform) {
1129 return withoutTransform ? escapeSqlite(value) : escapeSqlite(transform(value))
1130 } :
1131 escapeSqlite,
1132 onRecordUpdate() {
1133 return ["", []]
1134 },
1135 onCustom() {
1136 return ["", []]
1137 }
1138 })
1139
1140/**
1141 * Creates an identifier escaping function that wraps names in the given
1142 * delimiter, doubles delimiter characters, and escapes dots between identifier
1143 * parts.
1144 *
1145 * @stability unstable
1146 * @category constructors
1147 * @since 4.0.0
1148 */
1149export function defaultEscape(c: string) {
1150 const re = new RegExp(c, "g")
1151 const double = c + c
1152 const dot = c + "." + c
1153 return function(str: string): string {
1154 return c + str.replace(re, double).replace(/\./g, dot) + c
1155 }
1156}
1157
1158/**
1159 * Classifies a JavaScript value as a SQL primitive kind, treating `undefined`
1160 * as `null` and defaulting unrecognized objects to `string`.
1161 *
1162 * @stability unstable
1163 * @category converting
1164 * @since 4.0.0
1165 */
1166export const primitiveKind = (value: unknown): PrimitiveKind => {
1167 switch (typeof value) {
1168 case "string":
1169 return "string"
1170 case "number":
1171 return "number"
1172 case "boolean":
1173 return "boolean"
1174 case "bigint":
1175 return "bigint"
1176 case "undefined":
1177 return "null"
1178 }
1179
1180 if (value === null) {
1181 return "null"
1182 } else if (value instanceof Date) {
1183 return "Date"
1184 } else if (value instanceof Uint8Array) {
1185 return "Uint8Array"
1186 } else if (value instanceof Int8Array) {
1187 return "Int8Array"
1188 }
1189
1190 return "string"
1191}
1192
1193/**
1194 * Builds value, object, and row-array transformers that rename object keys with
1195 * the supplied function and optionally recurse into nested object arrays.
1196 *
1197 * @stability unstable
1198 * @category transforming
1199 * @since 4.0.0
1200 */
1201export const defaultTransforms = (
1202 transformer: (str: string) => string,
1203 nested = true
1204) => {
1205 const transformValue = (value: any) => {
1206 if (Array.isArray(value)) {
1207 if (value.length === 0 || value[0].constructor !== Object) {
1208 return value
1209 }
1210 return array(value)
1211 } else if (value?.constructor === Object) {
1212 return transformObject(value)
1213 }
1214 return value
1215 }
1216
1217 const transformObject = (obj: Record<string, any>): any => {
1218 const newObj: Record<string, any> = {}
1219 for (const key of Object.keys(obj)) {
1220 InternalRecord.assignProperty(newObj, transformer(key), transformValue(obj[key]))
1221 }
1222 return newObj
1223 }
1224
1225 const transformArrayNested = <A extends object>(
1226 rows: ReadonlyArray<A>
1227 ): ReadonlyArray<A> => {
1228 const newRows: Array<A> = new Array(rows.length)
1229 for (let i = 0, len = rows.length; i < len; i++) {
1230 const row = rows[i]
1231 if (Array.isArray(row)) {
1232 newRows[i] = transformArrayNested(row) as any
1233 } else {
1234 const obj: any = {}
1235 for (const [key, value] of Object.entries(row)) {
1236 InternalRecord.assignProperty(obj, transformer(key), transformValue(value))
1237 }
1238 newRows[i] = obj
1239 }
1240 }
1241 return newRows
1242 }
1243
1244 const transformArray = <A extends object>(
1245 rows: ReadonlyArray<A>
1246 ): ReadonlyArray<A> => {
1247 const newRows: Array<A> = new Array(rows.length)
1248 for (let i = 0, len = rows.length; i < len; i++) {
1249 const row = rows[i]
1250 if (Array.isArray(row)) {
1251 newRows[i] = transformArray(row) as any
1252 } else {
1253 const obj: any = {}
1254 for (const [key, value] of Object.entries(row)) {
1255 InternalRecord.assignProperty(obj, transformer(key), value)
1256 }
1257 newRows[i] = obj
1258 }
1259 }
1260 return newRows
1261 }
1262
1263 const array = nested ? transformArrayNested : transformArray
1264
1265 return {
1266 value: transformValue,
1267 object: transformObject,
1268 array
1269 } as const
1270}
1271
1272// internal
1273
1274const ATTR_DB_OPERATION_NAME = "db.operation.name"
1275const ATTR_DB_QUERY_TEXT = "db.query.text"
1276
1277interface StatementImpl<A> extends Statement<A> {
1278 readonly segments: ReadonlyArray<Segment>
1279 readonly acquirer: Acquirer
1280 readonly compiler: Compiler
1281 readonly spanAttributes: ReadonlyArray<readonly [string, unknown]>
1282 readonly transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined
1283 readonly borrower: Borrower | undefined
1284
1285 withConnection<XA, E>(
1286 operation: string,
1287 f: (
1288 connection: Connection,
1289 sql: string,
1290 params: ReadonlyArray<unknown>
1291 ) => Effect.Effect<XA, E>,
1292 withoutTransform?: boolean | undefined
1293 ): Effect.Effect<XA, E | SqlError>
1294 withConnectionSpan<XA, E>(
1295 operation: string,
1296 f: (
1297 connection: Connection,
1298 sql: string,
1299 params: ReadonlyArray<unknown>
1300 ) => Effect.Effect<XA, E>,
1301 withoutTransform: boolean,
1302 span: Tracer.Span
1303 ): Effect.Effect<XA, E | SqlError>
1304}
1305
1306const makeUnsafe = <A = Row>(
1307 segments: ReadonlyArray<Segment>,
1308 acquirer: Acquirer,
1309 compiler: Compiler,
1310 spanAttributes: ReadonlyArray<readonly [string, unknown]>,
1311 transformRows: (<A extends object>(row: ReadonlyArray<A>) => ReadonlyArray<A>) | undefined,
1312 borrower: Borrower | undefined
1313): StatementImpl<A> => {
1314 const self = Object.create(StatementProto)
1315 self.segments = segments
1316 self.acquirer = acquirer
1317 self.compiler = compiler
1318 self.spanAttributes = spanAttributes
1319 self.transformRows = transformRows
1320 self.borrower = borrower
1321 return self
1322}
1323
1324// TODO: figure out why these diagnostics are emitted
1325const StatementProto: Omit<
1326 StatementImpl<any>,
1327 "segments" | "acquirer" | "compiler" | "spanAttributes" | "transformRows" | "borrower"
1328> = {
1329 [FragmentTypeId]: FragmentTypeId,
1330 withConnection<XA, E>(
1331 this: StatementImpl<any>,
1332 operation: string,
1333 f: (
1334 connection: Connection,
1335 sql: string,
1336 params: ReadonlyArray<unknown>
1337 ) => Effect.Effect<XA, E>,
1338 withoutTransform = false
1339 ): Effect.Effect<XA, E | SqlError> {
1340 return Effect.useSpan(
1341 "sql.execute",
1342 { kind: "client" },
1343 (span) =>
1344 this.withConnectionSpan(
1345 operation,
1346 f,
1347 withoutTransform,
1348 span
1349 )
1350 )
1351 },
1352 withConnectionSpan<XA, E>(
1353 this: StatementImpl<any>,
1354 operation: string,
1355 f: (
1356 connection: Connection,
1357 sql: string,
1358 params: ReadonlyArray<unknown>
1359 ) => Effect.Effect<XA, E>,
1360 withoutTransform: boolean,
1361 span: Tracer.Span
1362 ): Effect.Effect<XA, E | SqlError> {
1363 return withStatement(this, span, (statement, fiber) => {
1364 const [sql, params] = statement.compile(withoutTransform)
1365 for (const [key, value] of this.spanAttributes) {
1366 span.attribute(key, value)
1367 }
1368 span.attribute(ATTR_DB_OPERATION_NAME, operation)
1369 span.attribute(ATTR_DB_QUERY_TEXT, sql)
1370 const execute = this.borrower === undefined
1371 ? Effect.scoped(Effect.flatMap(this.acquirer, (_) => f(_, sql, params)))
1372 : this.borrower((connection: Connection) => f(connection, sql, params))
1373 return fiber.cache.tracerEnabled && fiber.getRef(SpanPropagationEnabled)
1374 ? Effect.provideService(execute, Tracer.ParentSpan, span)
1375 : execute
1376 })
1377 },
1378
1379 get withoutTransform(): Effect.Effect<ReadonlyArray<any>, SqlError> {
1380 return this.withConnection(
1381 "executeWithoutTransform",
1382 (connection, sql, params) => connection.execute(sql, params, undefined),
1383 true
1384 )
1385 },
1386
1387 get raw(): Effect.Effect<unknown, SqlError> {
1388 return this.withConnection(
1389 "executeRaw",
1390 (connection, sql, params) => connection.executeRaw(sql, params),
1391 true
1392 )
1393 },
1394
1395 get stream(): Stream.Stream<any, SqlError> {
1396 const self = this as StatementImpl<any>
1397 return Stream.unwrap(Effect.flatMap(
1398 Effect.makeSpanScoped("sql.execute", { kind: "client" }),
1399 (span) =>
1400 withStatement(self, span, (statement, fiber) => {
1401 const [sql, params] = statement.compile()
1402 for (const [key, value] of self.spanAttributes) {
1403 span.attribute(key, value)
1404 }
1405 span.attribute(ATTR_DB_OPERATION_NAME, "executeStream")
1406 span.attribute(ATTR_DB_QUERY_TEXT, sql)
1407 const acquire = Effect.map(self.acquirer, (_) => _.executeStream(sql, params, self.transformRows))
1408 return fiber.cache.tracerEnabled && fiber.getRef(SpanPropagationEnabled)
1409 ? Effect.succeed(Stream.provideService(Stream.unwrap(acquire), Tracer.ParentSpan, span))
1410 : acquire
1411 })
1412 ))
1413 },
1414
1415 get values(): Effect.Effect<
1416 ReadonlyArray<ReadonlyArray<unknown>>,
1417 SqlError
1418 > {
1419 return this.withConnection("executeValues", (connection, sql, params) => connection.executeValues(sql, params))
1420 },
1421
1422 get valuesUnprepared(): Effect.Effect<
1423 ReadonlyArray<ReadonlyArray<unknown>>,
1424 SqlError
1425 > {
1426 return this.withConnection(
1427 "executeValuesUnprepared",
1428 (connection, sql, params) => connection.executeValuesUnprepared(sql, params)
1429 )
1430 },
1431
1432 get unprepared(): Effect.Effect<ReadonlyArray<any>, SqlError> {
1433 const self = this as StatementImpl<any>
1434 return self.withConnection(
1435 "executeUnprepared",
1436 (connection, sql, params) => connection.executeUnprepared(sql, params, self.transformRows)
1437 )
1438 },
1439
1440 ...Effectable.Prototype<StatementImpl<any>>({
1441 label: "Statement",
1442 evaluate(fiber) {
1443 const span = internalEffect.makeSpanUnsafe(fiber, "sql.execute", { kind: "client" })
1444 const clock = fiber.getRef(Clock)
1445 const timingEnabled = fiber.getRef(TracerTimingEnabled)
1446 return Effect.onExit(
1447 this.withConnectionSpan(
1448 "execute",
1449 (connection, sql, params) => connection.execute(sql, params, this.transformRows),
1450 false,
1451 span
1452 ),
1453 (exit) => internalEffect.endSpan(span, exit, clock, timingEnabled)
1454 )
1455 }
1456 }),
1457
1458 compile(
1459 this: StatementImpl<any>,
1460 withoutTransform?: boolean | undefined
1461 ) {
1462 return this.compiler.compile(this, withoutTransform ?? false)
1463 },
1464 toJSON(this: StatementImpl<any>) {
1465 const [sql, params] = this.compile()
1466 return {
1467 _id: "Statement",
1468 segments: this.segments,
1469 sql,
1470 params
1471 }
1472 }
1473}
1474
1475const withStatement = <A, X, E, R>(
1476 self: StatementImpl<A>,
1477 span: Tracer.Span,
1478 f: (statement: StatementImpl<A>, fiber: Fiber.Fiber<unknown, unknown>) => Effect.Effect<X, E, R>
1479) =>
1480 Effect.withFiber<X, E, R>((fiber) => {
1481 const transform = fiber.getRef(CurrentTransformer)
1482 if (transform === undefined) {
1483 return f(self, fiber)
1484 }
1485 return Effect.flatMap(
1486 transform(
1487 self,
1488 make(self.acquirer, self.compiler, self.spanAttributes, self.transformRows, self.borrower),
1489 fiber,
1490 span
1491 ) as Effect.Effect<StatementImpl<A>>,
1492 (statement) => f(statement, fiber)
1493 )
1494 })
1495
1496const isSegment = (u: unknown): u is Segment => {
1497 if (!hasProperty(u, "_tag")) {
1498 return false
1499 }
1500 switch (u._tag) {
1501 case "Literal":
1502 case "Parameter":
1503 case "ArrayHelper":
1504 case "RecordInsertHelper":
1505 case "RecordUpdateHelper":
1506 case "RecordUpdateHelperSingle":
1507 case "Identifier":
1508 case "Custom":
1509 return true
1510 default:
1511 return false
1512 }
1513}
1514
1515function convertLiteralOrFragment(clause: string | Fragment): Array<Segment> {
1516 if (typeof clause === "string") {
1517 return [literal(clause)]
1518 }
1519 return clause.segments as Array<Segment>
1520}
1521
1522const makePlaceholdersArray = (evaluate: (u: unknown) => string) => (values: ReadonlyArray<unknown>): string => {
1523 if (values.length === 0) {
1524 return ""
1525 }
1526
1527 let result = evaluate(values[0])
1528 for (let i = 1; i < values.length; i++) {
1529 result += `,${evaluate(values[i])}`
1530 }
1531
1532 return result
1533}
1534
1535const generateColumns = (
1536 keys: ReadonlyArray<string>,
1537 escape: (_: string, withoutTransform: boolean) => string,
1538 withoutTransform: boolean
1539) => {
1540 if (keys.length === 0) {
1541 return "()"
1542 }
1543
1544 let str = `(${escape(keys[0], withoutTransform)}`
1545 for (let i = 1; i < keys.length; i++) {
1546 str += `,${escape(keys[i], withoutTransform)}`
1547 }
1548 return str + ")"
1549}
1550
1551const extractPrimitive = (
1552 value: unknown,
1553 onCustom: (
1554 type: Custom<string, unknown, unknown>,
1555 placeholder: (u: unknown) => string,
1556 withoutTransform: boolean
1557 ) => readonly [sql: string, binds: ReadonlyArray<unknown>],
1558 placeholder: (u: unknown) => string,
1559 withoutTransform: boolean
1560): unknown => {
1561 if (value === undefined) {
1562 return null
1563 } else if (isFragment(value)) {
1564 const head = value.segments[0]
1565 if (head._tag === "Custom") {
1566 const compiled = onCustom(head, placeholder, withoutTransform)
1567 return compiled[1][0] ?? null
1568 } else if (head._tag === "Parameter") {
1569 return head.value
1570 }
1571 return null
1572 }
1573 return value
1574}
1575
1576const escapeSqlite = defaultEscape("\"")
1577
1578function in_(values: ReadonlyArray<unknown>): ArrayHelper
1579function in_(column: string, values: ReadonlyArray<unknown>): Fragment
1580function in_(): Fragment | ArrayHelper {
1581 if (arguments.length === 1) {
1582 return arrayHelper(arguments[0])
1583 }
1584 const column = arguments[0]
1585 const values = arguments[1]
1586 return values.length === 0 ? neverFragment : fragment([
1587 identifier(column),
1588 literal(" IN "),
1589 arrayHelper(values)
1590 ])
1591}
1592
1593const neverFragment = fragment([literal("1=0")])