EdgeAIG's picture
download
raw
26.2 kB
/**
* Low-level SQL statement and fragment primitives.
*
* `SqlClient` uses this module to build executable, parameterized SQL from
* reusable fragments. A statement can be executed, streamed, run without row
* transformation, or compiled to SQL text and parameters for a specific
* dialect. The module also contains helpers for identifiers, parameters,
* inserts, updates, custom dialect fragments, statement compilation, and row
* transformation.
*
* @since 4.0.0
*/
import { Clock } from "../../Clock.js";
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import * as Effectable from "../../Effectable.js";
import { constUndefined } from "../../Function.js";
import * as internalEffect from "../../internal/effect.js";
import { hasProperty } from "../../Predicate.js";
import { TracerTimingEnabled } from "../../References.js";
import * as Stream from "../../Stream.js";
const FragmentTypeId = "~effect/sql/Fragment";
/**
* Constructs a SQL `Fragment` from low-level statement segments.
*
* @category constructors
* @since 4.0.0
*/
export const fragment = segments => ({
[FragmentTypeId]: FragmentTypeId,
segments
});
/**
* Context reference for an optional current SQL statement transformer applied
* before statement execution.
*
* @category transformer
* @since 4.0.0
*/
export const CurrentTransformer = /*#__PURE__*/Context.Reference("effect/sql/CurrentTransformer", {
defaultValue: constUndefined
});
/**
* Returns `true` when a value is a SQL `Fragment`.
*
* @category guards
* @since 4.0.0
*/
export const isFragment = u => hasProperty(u, FragmentTypeId);
/**
* Creates a type guard for custom SQL segments with the specified custom kind.
*
* @category guards
* @since 4.0.0
*/
export const isCustom = kind => u => hasProperty(u, "_tag") && u._tag === "Custom" && u.kind === kind;
/**
* Constructs a raw SQL literal segment. The literal text is not escaped, so use
* bound parameters for untrusted values.
*
* @category constructors
* @since 4.0.0
*/
export const literal = (value, params) => ({
_tag: "Literal",
value,
params
});
/**
* Constructs a SQL identifier segment that will be escaped by the active
* compiler.
*
* @category constructors
* @since 4.0.0
*/
export const identifier = value => ({
_tag: "Identifier",
value
});
/**
* Constructs a bound parameter segment for a statement value.
*
* @category constructors
* @since 4.0.0
*/
export const parameter = value => ({
_tag: "Parameter",
value
});
/**
* Constructs an `ArrayHelper` segment for an array of values or fragments.
*
* @category constructors
* @since 4.0.0
*/
export const arrayHelper = value => ({
_tag: "ArrayHelper",
value
});
const RecordInsertHelperProto = {
_tag: "RecordInsertHelper",
returning(sql) {
const self = Object.create(Object.getPrototypeOf(this));
Object.assign(self, this, {
returningIdentifier: sql
});
return self;
}
};
/**
* Constructs a `RecordInsertHelper` from one or more row objects.
*
* @category constructors
* @since 4.0.0
*/
export const recordInsertHelper = value => Object.assign(Object.create(RecordInsertHelperProto), {
value,
returningIdentifier: undefined
});
const RecordUpdateHelperProto = {
...RecordInsertHelperProto,
_tag: "RecordUpdateHelper"
};
/**
* Constructs a `RecordUpdateHelper` for multi-row update compilation using the
* provided alias.
*
* @category constructors
* @since 4.0.0
*/
export const recordUpdateHelper = (value, alias) => Object.assign(Object.create(RecordUpdateHelperProto), {
value,
alias,
returningIdentifier: undefined
});
const RecordUpdateHelperSingleProto = {
...RecordInsertHelperProto,
_tag: "RecordUpdateHelperSingle"
};
/**
* Constructs a `RecordUpdateHelperSingle` from a record and a list of columns
* to omit from the update.
*
* @category constructors
* @since 4.0.0
*/
export const recordUpdateHelperSingle = (value, omit) => Object.assign(Object.create(RecordUpdateHelperSingleProto), {
value,
omit,
returningIdentifier: undefined
});
/**
* Creates a constructor for custom SQL segments of a specific kind handled by
* the active compiler.
*
* @category constructors
* @since 4.0.0
*/
export const custom = kind => (paramA, paramB, paramC) => ({
_tag: "Custom",
kind,
paramA,
paramB,
paramC
});
/**
* Creates a cached SQL statement constructor from a connection acquirer,
* compiler, tracing attributes, and optional row transformation function.
*
* @category constructors
* @since 4.0.0
*/
export const make = (acquirer, compiler, spanAttributes, transformRows) => {
const cache = transformRows === undefined ? constructorCache.noTransforms : constructorCache.transforms;
if (cache.has(acquirer)) {
return cache.get(acquirer);
}
const self = Object.assign(function sql(strings, ...args) {
if (typeof strings === "string") {
return identifier(strings);
} else if (Array.isArray(strings) && "raw" in strings) {
return statement(acquirer, compiler, strings, args, spanAttributes, transformRows);
}
throw "absurd";
}, {
unsafe(sql, params) {
return makeUnsafe([literal(sql, params)], acquirer, compiler, spanAttributes, transformRows);
},
literal(sql) {
return fragment([literal(sql)]);
},
in: in_,
insert(value) {
return recordInsertHelper(Array.isArray(value) ? value : [value]);
},
update(value, omit) {
return recordUpdateHelperSingle(value, omit ?? []);
},
updateValues(value, alias) {
return recordUpdateHelper(value, alias);
},
and,
or,
csv,
join,
onDialect(options) {
return options[compiler.dialect]();
},
onDialectOrElse(options) {
return options[compiler.dialect] !== undefined ? options[compiler.dialect]() : options.orElse();
}
});
cache.set(acquirer, self);
return self;
};
const constructorCache = {
transforms: /*#__PURE__*/new WeakMap(),
noTransforms: /*#__PURE__*/new WeakMap()
};
/**
* Builds a `Statement` from template strings and arguments, preserving
* fragments and helper segments while converting ordinary interpolated values
* into bound parameters.
*
* @category constructors
* @since 4.0.0
*/
export const statement = (acquirer, compiler, strings, args, spanAttributes, transformRows) => {
const segments = strings[0].length > 0 ? [literal(strings[0])] : [];
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (isFragment(arg)) {
segments.push(...arg.segments);
} else if (isSegment(arg)) {
segments.push(arg);
} else {
segments.push(parameter(arg));
}
if (strings[i + 1].length > 0) {
segments.push(literal(strings[i + 1]));
}
}
return makeUnsafe(segments, acquirer, compiler, spanAttributes, transformRows);
};
/**
* Creates a helper that joins SQL clauses with a literal separator, optionally
* wrapping multiple clauses in parentheses and using a fallback for an empty
* list.
*
* @category constructors
* @since 4.0.0
*/
export function join(lit, addParens = true, fallback = "") {
const literalStatement = literal(lit);
const fallbackFragment = fragment([literal(fallback)]);
return clauses => {
if (clauses.length === 0) {
return fallbackFragment;
} else if (clauses.length === 1) {
return fragment(convertLiteralOrFragment(clauses[0]));
}
const segments = [];
if (addParens) {
segments.push(literal("("));
}
segments.push.apply(segments, convertLiteralOrFragment(clauses[0]));
for (let i = 1; i < clauses.length; i++) {
segments.push(literalStatement);
segments.push.apply(segments, convertLiteralOrFragment(clauses[i]));
}
if (addParens) {
segments.push(literal(")"));
}
return fragment(segments);
};
}
/**
* Combines clauses with `AND`, parenthesizing multiple clauses and returning
* `1=1` when the list is empty.
*
* @category constructors
* @since 4.0.0
*/
export const and = /*#__PURE__*/join(" AND ", true, "1=1");
/**
* Combines clauses with `OR`, parenthesizing multiple clauses and returning
* `1=1` when the list is empty.
*
* @category constructors
* @since 4.0.0
*/
export const or = /*#__PURE__*/join(" OR ", true, "1=1");
/**
* Creates a comma-separated SQL fragment from values, optionally adding a
* prefix, and returns an empty fragment when no values are provided.
*
* @category constructors
* @since 4.0.0
*/
export const csv = function (...args) {
if (args[args.length - 1].length === 0) {
return emptyFragment;
}
if (args.length === 1) {
return csvRaw(args[0]);
}
return fragment([literal(`${args[0]} `), ...csvRaw(args[1]).segments]);
};
const csvRaw = /*#__PURE__*/join(",", false);
const emptyFragment = /*#__PURE__*/fragment([/*#__PURE__*/literal("")]);
/**
* Creates a dialect-specific SQL `Compiler` from rendering callbacks.
*
* @category compiler
* @since 4.0.0
*/
export const makeCompiler = options => {
const self = Object.create(CompilerProto);
self.options = options;
self.dialect = options.dialect;
self.disableTransforms = false;
return self;
};
const statementCacheSymbol = /*#__PURE__*/Symbol.for("effect/unstable/sql/Statement/statementCache");
const statementCacheNoTransformSymbol = /*#__PURE__*/Symbol.for("effect/unstable/sql/Statement/statementCacheNoTransform");
const CompilerProto = {
compile(statement, withoutTransform = false, placeholderOverride) {
const opts = this.options;
withoutTransform = withoutTransform || this.disableTransforms;
const cacheSymbol = withoutTransform ? statementCacheNoTransformSymbol : statementCacheSymbol;
if (cacheSymbol in statement) {
return statement[cacheSymbol];
}
const segments = statement.segments;
const len = segments.length;
let sql = "";
const binds = [];
let placeholderCount = 0;
const placeholder = placeholderOverride ?? (u => opts.placeholder(++placeholderCount, u));
const placeholderNoIncrement = u => opts.placeholder(placeholderCount, u);
const placeholders = makePlaceholdersArray(placeholder);
for (let i = 0; i < len; i++) {
const segment = segments[i];
switch (segment._tag) {
case "Literal":
{
sql += segment.value;
if (segment.params) {
binds.push.apply(binds, segment.params);
}
break;
}
case "Identifier":
{
sql += opts.onIdentifier(segment.value, withoutTransform);
break;
}
case "Parameter":
{
sql += placeholder(segment.value);
binds.push(segment.value);
break;
}
case "ArrayHelper":
{
sql += `(${placeholders(segment.value)})`;
binds.push.apply(binds, segment.value);
break;
}
case "RecordInsertHelper":
{
const keys = Object.keys(segment.value[0]);
if (opts.onInsert) {
const values = new Array(segment.value.length);
let placeholders = "";
for (let i = 0; i < segment.value.length; i++) {
const row = new Array(keys.length);
values[i] = row;
placeholders += i === 0 ? "(" : ",(";
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
const value = segment.value[i][key];
const primitive = extractPrimitive(value, opts.onCustom, placeholderNoIncrement, withoutTransform);
row[j] = primitive;
placeholders += j === 0 ? placeholder(value) : `,${placeholder(value)}`;
}
placeholders += ")";
}
const [s, b] = opts.onInsert(keys.map(_ => opts.onIdentifier(_, withoutTransform)), placeholders, values, typeof segment.returningIdentifier === "string" ? [segment.returningIdentifier, []] : segment.returningIdentifier ? this.compile(segment.returningIdentifier, withoutTransform, placeholder) : undefined);
sql += s;
binds.push.apply(binds, b);
} else {
let placeholders = "";
for (let i = 0; i < segment.value.length; i++) {
placeholders += i === 0 ? "(" : ",(";
for (let j = 0; j < keys.length; j++) {
const value = segment.value[i][keys[j]];
const primitive = extractPrimitive(value, opts.onCustom, placeholderNoIncrement, withoutTransform);
binds.push(primitive);
placeholders += j === 0 ? placeholder(value) : `,${placeholder(value)}`;
}
placeholders += ")";
}
sql += `${generateColumns(keys, opts.onIdentifier, withoutTransform)} VALUES ${placeholders}`;
if (typeof segment.returningIdentifier === "string") {
sql += ` RETURNING ${segment.returningIdentifier}`;
} else if (segment.returningIdentifier) {
sql += " RETURNING ";
const [s, b] = this.compile(segment.returningIdentifier, withoutTransform, placeholder);
sql += s;
binds.push.apply(binds, b);
}
}
break;
}
case "RecordUpdateHelperSingle":
{
let keys = Object.keys(segment.value);
if (segment.omit.length > 0) {
keys = keys.filter(key => !segment.omit.includes(key));
}
if (opts.onRecordUpdateSingle) {
const [s, b] = opts.onRecordUpdateSingle(keys.map(_ => opts.onIdentifier(_, withoutTransform)), keys.map(key => extractPrimitive(segment.value[key], opts.onCustom, placeholderNoIncrement, withoutTransform)), typeof segment.returningIdentifier === "string" ? [segment.returningIdentifier, []] : segment.returningIdentifier ? this.compile(segment.returningIdentifier, withoutTransform, placeholder) : undefined);
sql += s;
binds.push.apply(binds, b);
} else {
for (let i = 0, len = keys.length; i < len; i++) {
const column = opts.onIdentifier(keys[i], withoutTransform);
if (i === 0) {
sql += `${column} = ${placeholder(segment.value[keys[i]])}`;
} else {
sql += `, ${column} = ${placeholder(segment.value[keys[i]])}`;
}
binds.push(extractPrimitive(segment.value[keys[i]], opts.onCustom, placeholderNoIncrement, withoutTransform));
}
if (typeof segment.returningIdentifier === "string") {
if (this.dialect === "mssql") {
sql += ` OUTPUT ${segment.returningIdentifier === "*" ? "INSERTED.*" : segment.returningIdentifier}`;
} else {
sql += ` RETURNING ${segment.returningIdentifier}`;
}
} else if (segment.returningIdentifier) {
sql += this.dialect === "mssql" ? " OUTPUT " : " RETURNING ";
const [s, b] = this.compile(segment.returningIdentifier, withoutTransform, placeholder);
sql += s;
binds.push.apply(binds, b);
}
}
break;
}
case "RecordUpdateHelper":
{
const keys = Object.keys(segment.value[0]);
const values = new Array(segment.value.length);
let placeholders = "";
for (let i = 0; i < segment.value.length; i++) {
const row = new Array(keys.length);
values[i] = row;
placeholders += i === 0 ? "(" : ",(";
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
const value = segment.value[i][key];
row[j] = extractPrimitive(value, opts.onCustom, placeholderNoIncrement, withoutTransform);
placeholders += j === 0 ? placeholder(value) : `,${placeholder(value)}`;
}
placeholders += ")";
}
const [s, b] = opts.onRecordUpdate(placeholders, segment.alias, generateColumns(keys, opts.onIdentifier, withoutTransform), values, typeof segment.returningIdentifier === "string" ? [segment.returningIdentifier, []] : segment.returningIdentifier ? this.compile(segment.returningIdentifier, withoutTransform, placeholder) : undefined);
sql += s;
binds.push.apply(binds, b);
break;
}
case "Custom":
{
const [s, b] = opts.onCustom(segment, placeholder, withoutTransform);
sql += s;
binds.push.apply(binds, b);
break;
}
}
}
const result = [sql, binds];
if (placeholderOverride !== undefined) {
return result;
}
return statement[cacheSymbol] = result;
},
get withoutTransform() {
const self = Object.create(CompilerProto);
Object.assign(self, this, {
disableTransforms: true
});
return self;
}
};
/**
* Creates a SQLite compiler that uses `?` placeholders and quoted identifiers,
* optionally transforming identifier names before escaping.
*
* @category compiler
* @since 4.0.0
*/
export const makeCompilerSqlite = transform => makeCompiler({
dialect: "sqlite",
placeholder(_) {
return "?";
},
onIdentifier: transform ? function (value, withoutTransform) {
return withoutTransform ? escapeSqlite(value) : escapeSqlite(transform(value));
} : escapeSqlite,
onRecordUpdate() {
return ["", []];
},
onCustom() {
return ["", []];
}
});
/**
* Creates an identifier escaping function that wraps names in the given
* delimiter, doubles delimiter characters, and escapes dots between identifier
* parts.
*
* @category constructors
* @since 4.0.0
*/
export function defaultEscape(c) {
const re = new RegExp(c, "g");
const double = c + c;
const dot = c + "." + c;
return function (str) {
return c + str.replace(re, double).replace(/\./g, dot) + c;
};
}
/**
* Classifies a JavaScript value as a SQL primitive kind, treating `undefined`
* as `null` and defaulting unrecognized objects to `string`.
*
* @category predicates
* @since 4.0.0
*/
export const primitiveKind = value => {
switch (typeof value) {
case "string":
return "string";
case "number":
return "number";
case "boolean":
return "boolean";
case "bigint":
return "bigint";
case "undefined":
return "null";
}
if (value === null) {
return "null";
} else if (value instanceof Date) {
return "Date";
} else if (value instanceof Uint8Array) {
return "Uint8Array";
} else if (value instanceof Int8Array) {
return "Int8Array";
}
return "string";
};
/**
* Builds value, object, and row-array transformers that rename object keys with
* the supplied function and optionally recurse into nested object arrays.
*
* @category transforming
* @since 4.0.0
*/
export const defaultTransforms = (transformer, nested = true) => {
const transformValue = value => {
if (Array.isArray(value)) {
if (value.length === 0 || value[0].constructor !== Object) {
return value;
}
return array(value);
} else if (value?.constructor === Object) {
return transformObject(value);
}
return value;
};
const transformObject = obj => {
const newObj = {};
for (const key in obj) {
newObj[transformer(key)] = transformValue(obj[key]);
}
return newObj;
};
const transformArrayNested = rows => {
const newRows = new Array(rows.length);
for (let i = 0, len = rows.length; i < len; i++) {
const row = rows[i];
if (Array.isArray(row)) {
newRows[i] = transformArrayNested(row);
} else {
const obj = {};
for (const key in row) {
obj[transformer(key)] = transformValue(row[key]);
}
newRows[i] = obj;
}
}
return newRows;
};
const transformArray = rows => {
const newRows = new Array(rows.length);
for (let i = 0, len = rows.length; i < len; i++) {
const row = rows[i];
if (Array.isArray(row)) {
newRows[i] = transformArray(row);
} else {
const obj = {};
for (const key in row) {
obj[transformer(key)] = row[key];
}
newRows[i] = obj;
}
}
return newRows;
};
const array = nested ? transformArrayNested : transformArray;
return {
value: transformValue,
object: transformObject,
array
};
};
// internal
const ATTR_DB_OPERATION_NAME = "db.operation.name";
const ATTR_DB_QUERY_TEXT = "db.query.text";
const makeUnsafe = (segments, acquirer, compiler, spanAttributes, transformRows) => {
const self = Object.create(StatementProto);
self.segments = segments;
self.acquirer = acquirer;
self.compiler = compiler;
self.spanAttributes = spanAttributes;
self.transformRows = transformRows;
return self;
};
// TODO: figure out why these diagnostics are emitted
const StatementProto = {
... /*#__PURE__*/Effectable.Prototype({
label: "Statement",
evaluate(fiber) {
const span = internalEffect.makeSpanUnsafe(fiber, "sql.execute", {
kind: "client"
});
const clock = fiber.getRef(Clock);
const timingEnabled = fiber.getRef(TracerTimingEnabled);
return Effect.onExit(this.withConnectionSpan("execute", (connection, sql, params) => connection.execute(sql, params, this.transformRows), false, span), exit => internalEffect.endSpan(span, exit, clock, timingEnabled));
}
}),
[FragmentTypeId]: FragmentTypeId,
withConnection(operation, f, withoutTransform = false) {
return Effect.useSpan("sql.execute", {
kind: "client"
}, span => this.withConnectionSpan(operation, f, withoutTransform, span));
},
withConnectionSpan(operation, f, withoutTransform, span) {
return withStatement(this, span, statement => {
const [sql, params] = statement.compile(withoutTransform);
for (const [key, value] of this.spanAttributes) {
span.attribute(key, value);
}
span.attribute(ATTR_DB_OPERATION_NAME, operation);
span.attribute(ATTR_DB_QUERY_TEXT, sql);
return Effect.scoped(Effect.flatMap(this.acquirer, _ => f(_, sql, params)));
});
},
get withoutTransform() {
return this.withConnection("executeWithoutTransform", (connection, sql, params) => connection.execute(sql, params, undefined), true);
},
get raw() {
return this.withConnection("executeRaw", (connection, sql, params) => connection.executeRaw(sql, params), true);
},
get stream() {
const self = this;
return Stream.unwrap(Effect.flatMap(Effect.makeSpanScoped("sql.execute", {
kind: "client"
}), span => withStatement(self, span, statement => {
const [sql, params] = statement.compile();
for (const [key, value] of self.spanAttributes) {
span.attribute(key, value);
}
span.attribute(ATTR_DB_OPERATION_NAME, "executeStream");
span.attribute(ATTR_DB_QUERY_TEXT, sql);
return Effect.map(self.acquirer, _ => _.executeStream(sql, params, self.transformRows));
})));
},
get values() {
return this.withConnection("executeValues", (connection, sql, params) => connection.executeValues(sql, params));
},
get unprepared() {
const self = this;
return self.withConnection("executeUnprepared", (connection, sql, params) => connection.executeUnprepared(sql, params, self.transformRows));
},
compile(withoutTransform) {
return this.compiler.compile(this, withoutTransform ?? false);
},
toJSON() {
const [sql, params] = this.compile();
return {
_id: "Statement",
segments: this.segments,
sql,
params
};
}
};
const withStatement = (self, span, f) => Effect.withFiber(fiber => {
const transform = fiber.getRef(CurrentTransformer);
if (transform === undefined) {
return f(self);
}
return Effect.flatMap(transform(self, make(self.acquirer, self.compiler, self.spanAttributes, self.transformRows), fiber, span), f);
});
const isSegment = u => {
if (!hasProperty(u, "_tag")) {
return false;
}
switch (u._tag) {
case "Literal":
case "Parameter":
case "ArrayHelper":
case "RecordInsertHelper":
case "RecordUpdateHelper":
case "RecordUpdateHelperSingle":
case "Identifier":
case "Custom":
return true;
default:
return false;
}
};
function convertLiteralOrFragment(clause) {
if (typeof clause === "string") {
return [literal(clause)];
}
return clause.segments;
}
const makePlaceholdersArray = evaluate => values => {
if (values.length === 0) {
return "";
}
let result = evaluate(values[0]);
for (let i = 1; i < values.length; i++) {
result += `,${evaluate(values[i])}`;
}
return result;
};
const generateColumns = (keys, escape, withoutTransform) => {
if (keys.length === 0) {
return "()";
}
let str = `(${escape(keys[0], withoutTransform)}`;
for (let i = 1; i < keys.length; i++) {
str += `,${escape(keys[i], withoutTransform)}`;
}
return str + ")";
};
const extractPrimitive = (value, onCustom, placeholder, withoutTransform) => {
if (value === undefined) {
return null;
} else if (isFragment(value)) {
const head = value.segments[0];
if (head._tag === "Custom") {
const compiled = onCustom(head, placeholder, withoutTransform);
return compiled[1][0] ?? null;
} else if (head._tag === "Parameter") {
return head.value;
}
return null;
}
return value;
};
const escapeSqlite = /*#__PURE__*/defaultEscape("\"");
function in_() {
if (arguments.length === 1) {
return arrayHelper(arguments[0]);
}
const column = arguments[0];
const values = arguments[1];
return values.length === 0 ? neverFragment : fragment([identifier(column), literal(" IN "), arrayHelper(values)]);
}
const neverFragment = /*#__PURE__*/fragment([/*#__PURE__*/literal("1=0")]);
//# sourceMappingURL=Statement.js.map

Xet Storage Details

Size:
26.2 kB
·
Xet hash:
c0547151f3c15e4450695337e6b38e9730b4d0aec1fde0ab0bf47b7b474a92b9

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.