mirror of
https://github.com/oven-sh/bun
synced 2026-02-05 08:28:55 +00:00
Compare commits
27 Commits
dylan/byte
...
jarred/mys
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f569d450c | ||
|
|
220c23d85c | ||
|
|
ba1d471eaf | ||
|
|
9639615a78 | ||
|
|
a35a61c082 | ||
|
|
739c25320a | ||
|
|
7ed2266622 | ||
|
|
22c6241aaf | ||
|
|
0e239f4497 | ||
|
|
8f52c02ae6 | ||
|
|
1cb2a9601d | ||
|
|
bc559e69c3 | ||
|
|
181d349825 | ||
|
|
99ed017f48 | ||
|
|
62e451caea | ||
|
|
31d091557e | ||
|
|
bdd0fe44e7 | ||
|
|
4b82bf43eb | ||
|
|
1debc84195 | ||
|
|
7bbaa9f3df | ||
|
|
42a63a128a | ||
|
|
d1f3ceebdb | ||
|
|
028e98e18d | ||
|
|
b1f8cd623f | ||
|
|
28a7b8da76 | ||
|
|
d1159c858d | ||
|
|
f27c2942d7 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -176,4 +176,5 @@ test/js/third_party/prisma/prisma/sqlite/dev.db-journal
|
||||
|
||||
.buildkite/ci.yml
|
||||
*.sock
|
||||
scratch*.{js,ts,tsx,cjs,mjs}
|
||||
scratch*
|
||||
scratch*.{js,ts,tsx,cjs,mjs}
|
||||
|
||||
65
src/bun.js/api/mysql.classes.ts
Normal file
65
src/bun.js/api/mysql.classes.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { define } from "../../codegen/class-definitions";
|
||||
|
||||
export default [
|
||||
define({
|
||||
name: "MySQLConnection",
|
||||
construct: true,
|
||||
finalize: true,
|
||||
hasPendingActivity: true,
|
||||
configurable: false,
|
||||
klass: {
|
||||
// escapeString: {
|
||||
// fn: "escapeString",
|
||||
// },
|
||||
// escapeIdentifier: {
|
||||
// fn: "escapeIdentifier",
|
||||
// },
|
||||
},
|
||||
JSType: "0b11101110",
|
||||
proto: {
|
||||
close: {
|
||||
fn: "doClose",
|
||||
},
|
||||
flush: {
|
||||
fn: "doFlush",
|
||||
},
|
||||
connected: {
|
||||
getter: "getConnected",
|
||||
},
|
||||
ref: {
|
||||
fn: "doRef",
|
||||
},
|
||||
unref: {
|
||||
fn: "doUnref",
|
||||
},
|
||||
query: {
|
||||
fn: "createQuery",
|
||||
},
|
||||
},
|
||||
}),
|
||||
define({
|
||||
name: "MySQLQuery",
|
||||
construct: true,
|
||||
finalize: true,
|
||||
configurable: false,
|
||||
hasPendingActivity: true,
|
||||
JSType: "0b11101110",
|
||||
klass: {},
|
||||
proto: {
|
||||
run: {
|
||||
fn: "doRun",
|
||||
length: 2,
|
||||
},
|
||||
cancel: {
|
||||
fn: "doCancel",
|
||||
length: 0,
|
||||
},
|
||||
done: {
|
||||
fn: "doDone",
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
values: ["pendingValue", "columns", "binding"],
|
||||
estimatedSize: true,
|
||||
}),
|
||||
];
|
||||
@@ -246,6 +246,45 @@ static JSC::JSValue toJS(JSC::Structure* structure, DataCell* cells, unsigned co
|
||||
return object;
|
||||
}
|
||||
|
||||
extern "C" EncodedJSValue MySQL__ValueToJS(JSC::JSGlobalObject* globalObject, void* value);
|
||||
|
||||
static JSC::JSValue toJS(JSC::VM& vm, JSC::JSGlobalObject* globalObject, void** values, JSC::Structure* structure, unsigned count)
|
||||
{
|
||||
auto* object = JSC::constructEmptyObject(vm, structure);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
auto* value = values[i];
|
||||
EncodedJSValue result = MySQL__ValueToJS(globalObject, value);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
object->putDirectOffset(vm, i, JSValue::decode(result));
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
extern "C" EncodedJSValue MySQL__toJSFromRow(JSC::JSGlobalObject* globalObject, EncodedJSValue encodedStructureValue, EncodedJSValue arrayValue, void** rows, size_t cellCount)
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
auto* array = arrayValue ? jsDynamicCast<JSC::JSArray*>(JSValue::decode(arrayValue)) : nullptr;
|
||||
auto* structure = jsDynamicCast<JSC::Structure*>(JSValue::decode(encodedStructureValue));
|
||||
JSValue result = toJS(vm, globalObject, rows, structure, cellCount);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
if (array) {
|
||||
array->push(globalObject, result);
|
||||
return JSValue::encode(array);
|
||||
}
|
||||
|
||||
auto* newArray = JSC::constructEmptyArray(globalObject, static_cast<ArrayAllocationProfile*>(nullptr), cellCount);
|
||||
if (!newArray)
|
||||
return {};
|
||||
|
||||
newArray->putDirectIndex(globalObject, 0, result);
|
||||
return JSValue::encode(newArray);
|
||||
}
|
||||
|
||||
static JSC::JSValue toJS(JSC::JSArray* array, JSC::Structure* structure, DataCell* cells, unsigned count, JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
JSValue value = toJS(structure, cells, count, globalObject);
|
||||
|
||||
@@ -4086,6 +4086,18 @@ pub const JSValue = enum(i64) {
|
||||
return cppFn("coerceToDouble", .{ this, globalObject });
|
||||
}
|
||||
|
||||
pub fn coerceToDoubleCheckingErrors(
|
||||
this: JSValue,
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
) JSError!f64 {
|
||||
const num = this.coerceToDouble(globalObject);
|
||||
if (globalObject.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
pub fn coerce(this: JSValue, comptime T: type, globalThis: *JSC.JSGlobalObject) T {
|
||||
return switch (T) {
|
||||
ZigString => this.getZigString(globalThis),
|
||||
|
||||
@@ -73,6 +73,8 @@ pub const Classes = struct {
|
||||
pub const BytesInternalReadableStreamSource = JSC.WebCore.ByteStream.Source;
|
||||
pub const PostgresSQLConnection = JSC.Postgres.PostgresSQLConnection;
|
||||
pub const PostgresSQLQuery = JSC.Postgres.PostgresSQLQuery;
|
||||
pub const MySQLConnection = bun.sql.mysql.MySQLConnection;
|
||||
pub const MySQLQuery = bun.sql.mysql.MySQLQuery;
|
||||
pub const TextEncoderStreamEncoder = JSC.WebCore.TextEncoderStreamEncoder;
|
||||
pub const NativeZlib = JSC.API.NativeZlib;
|
||||
pub const NativeBrotli = JSC.API.NativeBrotli;
|
||||
|
||||
@@ -24,6 +24,7 @@ stdin_store: ?*Blob.Store = null,
|
||||
stdout_store: ?*Blob.Store = null,
|
||||
|
||||
postgresql_context: JSC.Postgres.PostgresSQLContext = .{},
|
||||
mysql_context: JSC.MySQL.MySQLContext = .{},
|
||||
|
||||
entropy_cache: ?*EntropyCache = null,
|
||||
|
||||
|
||||
@@ -4118,3 +4118,8 @@ pub inline fn isComptimeKnown(x: anytype) bool {
|
||||
pub inline fn itemOrNull(comptime T: type, slice: []const T, index: usize) ?T {
|
||||
return if (index < slice.len) slice[index] else null;
|
||||
}
|
||||
|
||||
/// Code used by the classes generator
|
||||
pub const gen_classes_lib = @import("gen_classes_lib.zig");
|
||||
|
||||
pub const sql = @import("./sql/shared_sql.zig");
|
||||
|
||||
@@ -340,19 +340,23 @@ pub const Run = struct {
|
||||
Output.prettyErrorln("Error occurred loading entry point: {s}", .{@errorName(err)});
|
||||
Output.flush();
|
||||
}
|
||||
// TODO: Do a event loop tick when we figure out how to watch the file that wasn't found
|
||||
// under hot reload mode
|
||||
vm.exit_handler.exit_code = 1;
|
||||
vm.onExit();
|
||||
if (run.any_unhandled) {
|
||||
bun.JSC.SavedSourceMap.MissingSourceMapNoteInfo.print();
|
||||
|
||||
Output.prettyErrorln(
|
||||
"<r>\n<d>{s}<r>",
|
||||
.{Global.unhandled_error_bun_version_string},
|
||||
);
|
||||
if (vm.hot_reload != .none) {
|
||||
vm.eventLoop().tick();
|
||||
vm.eventLoop().tickPossiblyForever();
|
||||
} else {
|
||||
vm.exit_handler.exit_code = 1;
|
||||
vm.onExit();
|
||||
if (run.any_unhandled) {
|
||||
bun.JSC.SavedSourceMap.MissingSourceMapNoteInfo.print();
|
||||
|
||||
Output.prettyErrorln(
|
||||
"<r>\n<d>{s}<r>",
|
||||
.{Global.unhandled_error_bun_version_string},
|
||||
);
|
||||
}
|
||||
vm.globalExit();
|
||||
}
|
||||
vm.globalExit();
|
||||
}
|
||||
|
||||
// don't run the GC if we don't actually need to
|
||||
|
||||
@@ -34,13 +34,6 @@ const _queryStatus = Symbol("status");
|
||||
const _handler = Symbol("handler");
|
||||
const PublicPromise = Promise;
|
||||
|
||||
const {
|
||||
createConnection: _createConnection,
|
||||
createQuery,
|
||||
PostgresSQLConnection,
|
||||
init,
|
||||
} = $zig("postgres.zig", "createBinding");
|
||||
|
||||
function normalizeSSLMode(value: string): SSLMode {
|
||||
if (!value) {
|
||||
return SSLMode.disable;
|
||||
@@ -181,101 +174,256 @@ class Query extends PublicPromise {
|
||||
}
|
||||
Object.defineProperty(Query, Symbol.species, { value: PublicPromise });
|
||||
Object.defineProperty(Query, Symbol.toStringTag, { value: "Query" });
|
||||
init(
|
||||
function (query, result, commandTag, count) {
|
||||
$assert(result instanceof SQLResultArray, "Invalid result array");
|
||||
if (typeof commandTag === "string") {
|
||||
if (commandTag.length > 0) {
|
||||
result.command = commandTag;
|
||||
}
|
||||
} else {
|
||||
result.command = cmds[commandTag];
|
||||
}
|
||||
|
||||
result.count = count || 0;
|
||||
|
||||
try {
|
||||
query.resolve(result);
|
||||
} catch (e) {}
|
||||
},
|
||||
function (query, reject) {
|
||||
try {
|
||||
query.reject(reject);
|
||||
} catch (e) {}
|
||||
},
|
||||
);
|
||||
|
||||
function createConnection({ hostname, port, username, password, tls, query, database, sslMode }, onConnected, onClose) {
|
||||
return _createConnection(
|
||||
hostname,
|
||||
Number(port),
|
||||
username || "",
|
||||
password || "",
|
||||
database || "",
|
||||
// > The default value for sslmode is prefer. As is shown in the table, this
|
||||
// makes no sense from a security point of view, and it only promises
|
||||
// performance overhead if possible. It is only provided as the default for
|
||||
// backward compatibility, and is not recommended in secure deployments.
|
||||
sslMode || SSLMode.disable,
|
||||
tls || null,
|
||||
query || "",
|
||||
onConnected,
|
||||
onClose,
|
||||
);
|
||||
}
|
||||
|
||||
var hasSQLArrayParameter = false;
|
||||
function normalizeStrings(strings, values) {
|
||||
hasSQLArrayParameter = false;
|
||||
if ($isJSArray(strings)) {
|
||||
const count = strings.length;
|
||||
if (count === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var out = strings[0];
|
||||
function createPostgresAdapter() {
|
||||
const {
|
||||
createConnection: _createConnection,
|
||||
createQuery,
|
||||
PostgresSQLConnection,
|
||||
init,
|
||||
} = $zig("postgres.zig", "createBinding");
|
||||
|
||||
// For now, only support insert queries with array parameters
|
||||
//
|
||||
// insert into users ${sql(users)}
|
||||
//
|
||||
if (values.length > 0 && typeof values[0] === "object" && values[0] && values[0] instanceof SQLArrayParameter) {
|
||||
if (values.length > 1) {
|
||||
throw new Error("Cannot mix array parameters with other values");
|
||||
function normalizeStrings(strings, values) {
|
||||
hasSQLArrayParameter = false;
|
||||
if ($isJSArray(strings)) {
|
||||
const count = strings.length;
|
||||
if (count === 0) {
|
||||
return "";
|
||||
}
|
||||
hasSQLArrayParameter = true;
|
||||
const { columns, value } = values[0];
|
||||
const groupCount = value.length;
|
||||
out += `values `;
|
||||
|
||||
let columnIndex = 1;
|
||||
let columnCount = columns.length;
|
||||
let lastColumnIndex = columnCount - 1;
|
||||
var out = strings[0];
|
||||
|
||||
for (var i = 0; i < groupCount; i++) {
|
||||
out += i > 0 ? `, (` : `(`;
|
||||
// For now, only support insert queries with array parameters
|
||||
//
|
||||
// insert into users ${sql(users)}
|
||||
//
|
||||
if (values.length > 0 && typeof values[0] === "object" && values[0] && values[0] instanceof SQLArrayParameter) {
|
||||
if (values.length > 1) {
|
||||
throw new Error("Cannot mix array parameters with other values");
|
||||
}
|
||||
hasSQLArrayParameter = true;
|
||||
const { columns, value } = values[0];
|
||||
const groupCount = value.length;
|
||||
out += `values `;
|
||||
|
||||
for (var j = 0; j < lastColumnIndex; j++) {
|
||||
out += `$${columnIndex++}, `;
|
||||
let columnIndex = 1;
|
||||
let columnCount = columns.length;
|
||||
let lastColumnIndex = columnCount - 1;
|
||||
|
||||
for (var i = 0; i < groupCount; i++) {
|
||||
out += i > 0 ? `, (` : `(`;
|
||||
|
||||
for (var j = 0; j < lastColumnIndex; j++) {
|
||||
out += `$${columnIndex++}, `;
|
||||
}
|
||||
|
||||
out += `$${columnIndex++})`;
|
||||
}
|
||||
|
||||
out += `$${columnIndex++})`;
|
||||
for (var i = 1; i < count; i++) {
|
||||
out += strings[i];
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
for (var i = 1; i < count; i++) {
|
||||
out += strings[i];
|
||||
out += `$${i}${strings[i]}`;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
for (var i = 1; i < count; i++) {
|
||||
out += `$${i}${strings[i]}`;
|
||||
}
|
||||
return out;
|
||||
return strings + "";
|
||||
}
|
||||
|
||||
return strings + "";
|
||||
function createConnection(
|
||||
{ hostname, port, username, password, tls, query, database, sslMode },
|
||||
onConnected,
|
||||
onClose,
|
||||
) {
|
||||
return _createConnection(
|
||||
hostname,
|
||||
Number(port),
|
||||
username || "",
|
||||
password || "",
|
||||
database || "",
|
||||
// > The default value for sslmode is prefer. As is shown in the table, this
|
||||
// makes no sense from a security point of view, and it only promises
|
||||
// performance overhead if possible. It is only provided as the default for
|
||||
// backward compatibility, and is not recommended in secure deployments.
|
||||
sslMode || SSLMode.disable,
|
||||
tls || null,
|
||||
query || "",
|
||||
onConnected,
|
||||
onClose,
|
||||
);
|
||||
}
|
||||
|
||||
function doCreateQuery(strings, values) {
|
||||
const sqlString = normalizeStrings(strings, values);
|
||||
let columns;
|
||||
if (hasSQLArrayParameter) {
|
||||
hasSQLArrayParameter = false;
|
||||
const v = values[0];
|
||||
columns = v.columns;
|
||||
values = v.value;
|
||||
}
|
||||
|
||||
return createQuery(sqlString, values, new SQLResultArray(), columns);
|
||||
}
|
||||
|
||||
init(
|
||||
function (query, result, commandTag, count) {
|
||||
$assert(result instanceof SQLResultArray, "Invalid result array");
|
||||
if (typeof commandTag === "string") {
|
||||
if (commandTag.length > 0) {
|
||||
result.command = commandTag;
|
||||
}
|
||||
} else {
|
||||
result.command = cmds[commandTag];
|
||||
}
|
||||
|
||||
result.count = count || 0;
|
||||
|
||||
try {
|
||||
query.resolve(result);
|
||||
} catch (e) {}
|
||||
},
|
||||
function (query, reject) {
|
||||
try {
|
||||
query.reject(reject);
|
||||
} catch (e) {}
|
||||
},
|
||||
);
|
||||
|
||||
return { createConnection, doCreateQuery, normalizeStrings, PostgresSQLConnection };
|
||||
}
|
||||
let postgres: ReturnType<typeof createPostgresAdapter>;
|
||||
let mysql: ReturnType<typeof createMySQLAdapter>;
|
||||
|
||||
function createMySQLAdapter() {
|
||||
function normalizeStrings(strings, values) {
|
||||
hasSQLArrayParameter = false;
|
||||
if ($isJSArray(strings)) {
|
||||
const count = strings.length;
|
||||
if (count === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var out = strings[0];
|
||||
|
||||
// For now, only support insert queries with array parameters
|
||||
//
|
||||
// insert into users ${sql(users)}
|
||||
//
|
||||
if (values.length > 0 && typeof values[0] === "object" && values[0] && values[0] instanceof SQLArrayParameter) {
|
||||
if (values.length > 1) {
|
||||
throw new Error("Cannot mix array parameters with other values");
|
||||
}
|
||||
hasSQLArrayParameter = true;
|
||||
const { columns, value } = values[0];
|
||||
const groupCount = value.length;
|
||||
out += `values `;
|
||||
|
||||
let columnCount = columns.length;
|
||||
let lastColumnIndex = columnCount - 1;
|
||||
|
||||
for (var i = 0; i < groupCount; i++) {
|
||||
out += i > 0 ? `, (` : `(`;
|
||||
|
||||
for (var j = 0; j < lastColumnIndex; j++) {
|
||||
out += `?, `;
|
||||
}
|
||||
|
||||
out += `?)`;
|
||||
}
|
||||
|
||||
for (var i = 1; i < count; i++) {
|
||||
out += strings[i];
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
for (var i = 1; i < count; i++) {
|
||||
out += `?`;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
return strings + "";
|
||||
}
|
||||
|
||||
const {
|
||||
createConnection: _createConnection,
|
||||
createQuery,
|
||||
MySQLConnection,
|
||||
init,
|
||||
} = $zig("mysql.zig", "createBinding");
|
||||
|
||||
function createConnection(
|
||||
{ hostname, port, username, password, tls, query, database, sslMode },
|
||||
onConnected,
|
||||
onClose,
|
||||
) {
|
||||
return _createConnection(
|
||||
hostname,
|
||||
Number(port),
|
||||
username || "",
|
||||
password || "",
|
||||
database || "",
|
||||
// > The default value for sslmode is prefer. As is shown in the table, this
|
||||
// makes no sense from a security point of view, and it only promises
|
||||
// performance overhead if possible. It is only provided as the default for
|
||||
// backward compatibility, and is not recommended in secure deployments.
|
||||
sslMode || SSLMode.disable,
|
||||
tls || null,
|
||||
query || "",
|
||||
onConnected,
|
||||
onClose,
|
||||
);
|
||||
}
|
||||
|
||||
function doCreateQuery(strings, values) {
|
||||
const sqlString = normalizeStrings(strings, values);
|
||||
let columns;
|
||||
if (hasSQLArrayParameter) {
|
||||
hasSQLArrayParameter = false;
|
||||
const v = values[0];
|
||||
columns = v.columns;
|
||||
values = v.value;
|
||||
}
|
||||
|
||||
return createQuery(sqlString, values, new SQLResultArray(), columns);
|
||||
}
|
||||
|
||||
init(
|
||||
function (query, result, commandTag, count) {
|
||||
$assert(result instanceof SQLResultArray, "Invalid result array");
|
||||
if (typeof commandTag === "string") {
|
||||
if (commandTag.length > 0) {
|
||||
result.command = commandTag;
|
||||
}
|
||||
} else {
|
||||
result.command = cmds[commandTag];
|
||||
}
|
||||
|
||||
result.count = count || 0;
|
||||
|
||||
try {
|
||||
query.resolve(result);
|
||||
} catch (e) {}
|
||||
},
|
||||
function (query, reject) {
|
||||
try {
|
||||
query.reject(reject);
|
||||
} catch (e) {}
|
||||
},
|
||||
);
|
||||
|
||||
return { createConnection, doCreateQuery, normalizeStrings, MySQLConnection };
|
||||
}
|
||||
|
||||
class SQLArrayParameter {
|
||||
@@ -311,20 +459,68 @@ class SQLArrayParameter {
|
||||
}
|
||||
}
|
||||
|
||||
const enum DatabaseAdapter {
|
||||
postgres = 0,
|
||||
mysql = 1,
|
||||
}
|
||||
|
||||
const databaseAdapterPort: Record<DatabaseAdapter, number> = {
|
||||
[DatabaseAdapter.postgres]: 5432,
|
||||
[DatabaseAdapter.mysql]: 3306,
|
||||
};
|
||||
|
||||
function getAdapter(adapter: DatabaseAdapter) {
|
||||
if (adapter === DatabaseAdapter.postgres) {
|
||||
if (!postgres) {
|
||||
postgres = createPostgresAdapter();
|
||||
}
|
||||
return postgres;
|
||||
}
|
||||
|
||||
if (adapter === DatabaseAdapter.mysql) {
|
||||
if (!mysql) {
|
||||
mysql = createMySQLAdapter();
|
||||
}
|
||||
return mysql;
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported adapter: ${adapter}`);
|
||||
}
|
||||
|
||||
function loadOptions(o) {
|
||||
var hostname, port, username, password, database, tls, url, query, adapter;
|
||||
const env = Bun.env;
|
||||
var sslMode: SSLMode = SSLMode.disable;
|
||||
|
||||
if (o === undefined || (typeof o === "string" && o.length === 0)) {
|
||||
let urlString = env.POSTGRES_URL || env.DATABASE_URL || env.PGURL || env.PG_URL;
|
||||
if (!urlString) {
|
||||
urlString = env.TLS_POSTGRES_DATABASE_URL || env.TLS_DATABASE_URL;
|
||||
let urlString = env.TLS_POSTGRES_DATABASE_URL;
|
||||
if (urlString) {
|
||||
adapter = DatabaseAdapter.postgres;
|
||||
} else {
|
||||
urlString = env.TLS_MYSQL_DATABASE_URL || env.TLS_MARIADB_DATABASE_URL;
|
||||
if (urlString) {
|
||||
sslMode = SSLMode.require;
|
||||
adapter = DatabaseAdapter.mysql;
|
||||
}
|
||||
}
|
||||
|
||||
if (urlString) {
|
||||
sslMode = SSLMode.require;
|
||||
} else {
|
||||
urlString ||= env.POSTGRES_URL || env.PGURL || env.PG_URL;
|
||||
if (urlString) {
|
||||
adapter = DatabaseAdapter.postgres;
|
||||
} else {
|
||||
urlString ||= env.MYSQL_URL || env.MARIADB_URL;
|
||||
if (urlString) {
|
||||
adapter = DatabaseAdapter.mysql;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!urlString) {
|
||||
urlString = env.DATABASE_URL;
|
||||
}
|
||||
|
||||
if (urlString) {
|
||||
url = new URL(urlString);
|
||||
o = {};
|
||||
@@ -349,10 +545,11 @@ function loadOptions(o) {
|
||||
url = new URL(o);
|
||||
}
|
||||
|
||||
let protocol;
|
||||
if (url) {
|
||||
({ hostname, port, username, password, protocol: adapter } = o = url);
|
||||
if (adapter[adapter.length - 1] === ":") {
|
||||
adapter = adapter.slice(0, -1);
|
||||
({ hostname, port, username, password, protocol } = o = url);
|
||||
if (protocol[protocol.length - 1] === ":") {
|
||||
protocol = protocol.slice(0, -1);
|
||||
}
|
||||
|
||||
const queryObject = url.searchParams.toJSON();
|
||||
@@ -367,13 +564,36 @@ function loadOptions(o) {
|
||||
query = query.trim();
|
||||
}
|
||||
|
||||
protocol ||= o.protocol || "postgres";
|
||||
if (protocol === "postgresql") {
|
||||
protocol = "postgres";
|
||||
} else if (protocol === "mariadb") {
|
||||
protocol = "mysql";
|
||||
}
|
||||
|
||||
if (protocol === "mysql") {
|
||||
adapter = DatabaseAdapter.mysql;
|
||||
} else if (protocol === "postgres") {
|
||||
adapter = DatabaseAdapter.postgres;
|
||||
} else if (protocol && !(protocol === "postgres" || protocol === "mysql")) {
|
||||
throw new TypeError(`Unsupported protocol: ${protocol}. Only "postgres" and "mysql" are supported`);
|
||||
}
|
||||
|
||||
hostname ||= o.hostname || o.host || env.PGHOST || "localhost";
|
||||
port ||= Number(o.port || env.PGPORT || 5432);
|
||||
username ||= o.username || o.user || env.PGUSERNAME || env.PGUSER || env.USER || env.USERNAME || "postgres";
|
||||
port ||= Number(
|
||||
o.port || (adapter === DatabaseAdapter.postgres ? env.PGPORT : env.MYSQL_PORT) || databaseAdapterPort[adapter],
|
||||
);
|
||||
username ||=
|
||||
o.username ||
|
||||
o.user ||
|
||||
env.PGUSERNAME ||
|
||||
env.PGUSER ||
|
||||
env.USER ||
|
||||
env.USERNAME ||
|
||||
(protocol === "postgres" ? "postgres" : "root");
|
||||
database ||= o.database || o.db || (url?.pathname ?? "").slice(1) || env.PGDATABASE || username;
|
||||
password ||= o.password || o.pass || env.PGPASSWORD || "";
|
||||
tls ||= o.tls || o.ssl;
|
||||
adapter ||= o.adapter || "postgres";
|
||||
|
||||
if (sslMode !== SSLMode.disable && !tls?.serverName) {
|
||||
if (hostname) {
|
||||
@@ -394,11 +614,7 @@ function loadOptions(o) {
|
||||
throw new Error(`Invalid port: ${port}`);
|
||||
}
|
||||
|
||||
if (adapter && !(adapter === "postgres" || adapter === "postgresql")) {
|
||||
throw new Error(`Unsupported adapter: ${adapter}. Only \"postgres\" is supported for now`);
|
||||
}
|
||||
|
||||
return { hostname, port, username, password, database, tls, query, sslMode };
|
||||
return { hostname, port, username, password, database, tls, query, sslMode, adapter };
|
||||
}
|
||||
|
||||
function SQL(o) {
|
||||
@@ -409,6 +625,8 @@ function SQL(o) {
|
||||
onConnect: any[] = [],
|
||||
connectionInfo = loadOptions(o);
|
||||
|
||||
var { createConnection, doCreateQuery } = getAdapter(connectionInfo.adapter);
|
||||
|
||||
function connectedHandler(query, handle, err) {
|
||||
if (err) {
|
||||
return query.reject(err);
|
||||
@@ -450,19 +668,6 @@ function SQL(o) {
|
||||
onConnected(err, undefined);
|
||||
}
|
||||
|
||||
function doCreateQuery(strings, values) {
|
||||
const sqlString = normalizeStrings(strings, values);
|
||||
let columns;
|
||||
if (hasSQLArrayParameter) {
|
||||
hasSQLArrayParameter = false;
|
||||
const v = values[0];
|
||||
columns = v.columns;
|
||||
values = v.value;
|
||||
}
|
||||
|
||||
return createQuery(sqlString, values, new SQLResultArray(), columns);
|
||||
}
|
||||
|
||||
function connectedSQL(strings, values) {
|
||||
return new Query(doCreateQuery(strings, values), connectedHandler);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ pub const API = struct {
|
||||
pub const NativeBrotli = @import("./bun.js/node/node_zlib_binding.zig").SNativeBrotli;
|
||||
};
|
||||
pub const Postgres = @import("./sql/postgres.zig");
|
||||
pub const MySQL = @import("./sql/mysql.zig");
|
||||
pub const DNS = @import("./bun.js/api/bun/dns_resolver.zig");
|
||||
pub const FFI = @import("./bun.js/api/ffi.zig").FFI;
|
||||
pub const Node = struct {
|
||||
|
||||
62
src/sql/SocketMonitor.zig
Normal file
62
src/sql/SocketMonitor.zig
Normal file
@@ -0,0 +1,62 @@
|
||||
const std = @import("std");
|
||||
const debug = @import("./postgres.zig").debug;
|
||||
const DebugSocketMonitorWriter = struct {
|
||||
var file: std.fs.File = undefined;
|
||||
var enabled = false;
|
||||
var check = std.once(load);
|
||||
pub fn write(data: []const u8) void {
|
||||
file.writeAll(data) catch {};
|
||||
}
|
||||
|
||||
fn load() void {
|
||||
if (bun.getenvZAnyCase("BUN_POSTGRES_SOCKET_MONITOR")) |monitor| {
|
||||
enabled = true;
|
||||
file = std.fs.cwd().createFile(monitor, .{ .truncate = true }) catch {
|
||||
enabled = false;
|
||||
return;
|
||||
};
|
||||
debug("writing to {s}", .{monitor});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const DebugSocketMonitorReader = struct {
|
||||
var file: std.fs.File = undefined;
|
||||
var enabled = false;
|
||||
var check = std.once(load);
|
||||
|
||||
fn load() void {
|
||||
if (bun.getenvZAnyCase("BUN_POSTGRES_SOCKET_MONITOR_READER")) |monitor| {
|
||||
enabled = true;
|
||||
file = std.fs.cwd().createFile(monitor, .{ .truncate = true }) catch {
|
||||
enabled = false;
|
||||
return;
|
||||
};
|
||||
debug("duplicating reads to {s}", .{monitor});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(data: []const u8) void {
|
||||
file.writeAll(data) catch {};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn write(data: []const u8) void {
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
DebugSocketMonitorWriter.check.call();
|
||||
if (DebugSocketMonitorWriter.enabled) {
|
||||
DebugSocketMonitorWriter.write(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(data: []const u8) void {
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
DebugSocketMonitorReader.check.call();
|
||||
if (DebugSocketMonitorReader.enabled) {
|
||||
DebugSocketMonitorReader.write(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bun = @import("root").bun;
|
||||
2040
src/sql/mysql.zig
Normal file
2040
src/sql/mysql.zig
Normal file
File diff suppressed because it is too large
Load Diff
1620
src/sql/mysql/mysql_protocol.zig
Normal file
1620
src/sql/mysql/mysql_protocol.zig
Normal file
File diff suppressed because it is too large
Load Diff
967
src/sql/mysql/mysql_types.zig
Normal file
967
src/sql/mysql/mysql_types.zig
Normal file
@@ -0,0 +1,967 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const mysql = bun.JSC.MySQL;
|
||||
const Data = bun.sql.Data;
|
||||
const protocol = @This();
|
||||
const MySQLInt32 = mysql.MySQLInt32;
|
||||
const MySQLInt16 = mysql.MySQLInt16;
|
||||
const String = bun.String;
|
||||
const debug = mysql.debug;
|
||||
const JSValue = bun.JSC.JSValue;
|
||||
const JSC = bun.JSC;
|
||||
|
||||
pub const CharacterSet = enum(u8) {
|
||||
big5_chinese_ci = 1,
|
||||
latin2_czech_cs = 2,
|
||||
dec8_swedish_ci = 3,
|
||||
cp850_general_ci = 4,
|
||||
latin1_german1_ci = 5,
|
||||
hp8_english_ci = 6,
|
||||
koi8r_general_ci = 7,
|
||||
latin1_swedish_ci = 8,
|
||||
latin2_general_ci = 9,
|
||||
swe7_swedish_ci = 10,
|
||||
ascii_general_ci = 11,
|
||||
ujis_japanese_ci = 12,
|
||||
sjis_japanese_ci = 13,
|
||||
cp1251_bulgarian_ci = 14,
|
||||
latin1_danish_ci = 15,
|
||||
hebrew_general_ci = 16,
|
||||
tis620_thai_ci = 18,
|
||||
euckr_korean_ci = 19,
|
||||
latin7_estonian_cs = 20,
|
||||
latin2_hungarian_ci = 21,
|
||||
koi8u_general_ci = 22,
|
||||
cp1251_ukrainian_ci = 23,
|
||||
gb2312_chinese_ci = 24,
|
||||
greek_general_ci = 25,
|
||||
cp1250_general_ci = 26,
|
||||
latin2_croatian_ci = 27,
|
||||
gbk_chinese_ci = 28,
|
||||
cp1257_lithuanian_ci = 29,
|
||||
latin5_turkish_ci = 30,
|
||||
latin1_german2_ci = 31,
|
||||
armscii8_general_ci = 32,
|
||||
utf8mb3_general_ci = 33,
|
||||
cp1250_czech_cs = 34,
|
||||
ucs2_general_ci = 35,
|
||||
cp866_general_ci = 36,
|
||||
keybcs2_general_ci = 37,
|
||||
macce_general_ci = 38,
|
||||
macroman_general_ci = 39,
|
||||
cp852_general_ci = 40,
|
||||
latin7_general_ci = 41,
|
||||
latin7_general_cs = 42,
|
||||
macce_bin = 43,
|
||||
cp1250_croatian_ci = 44,
|
||||
utf8mb4_general_ci = 45,
|
||||
utf8mb4_bin = 46,
|
||||
latin1_bin = 47,
|
||||
latin1_general_ci = 48,
|
||||
latin1_general_cs = 49,
|
||||
cp1251_bin = 50,
|
||||
cp1251_general_ci = 51,
|
||||
cp1251_general_cs = 52,
|
||||
macroman_bin = 53,
|
||||
utf16_general_ci = 54,
|
||||
utf16_bin = 55,
|
||||
utf16le_general_ci = 56,
|
||||
cp1256_general_ci = 57,
|
||||
cp1257_bin = 58,
|
||||
cp1257_general_ci = 59,
|
||||
utf32_general_ci = 60,
|
||||
utf32_bin = 61,
|
||||
utf16le_bin = 62,
|
||||
binary = 63,
|
||||
armscii8_bin = 64,
|
||||
ascii_bin = 65,
|
||||
cp1250_bin = 66,
|
||||
cp1256_bin = 67,
|
||||
cp866_bin = 68,
|
||||
dec8_bin = 69,
|
||||
greek_bin = 70,
|
||||
hebrew_bin = 71,
|
||||
hp8_bin = 72,
|
||||
keybcs2_bin = 73,
|
||||
koi8r_bin = 74,
|
||||
koi8u_bin = 75,
|
||||
utf8mb3_tolower_ci = 76,
|
||||
latin2_bin = 77,
|
||||
latin5_bin = 78,
|
||||
latin7_bin = 79,
|
||||
cp850_bin = 80,
|
||||
cp852_bin = 81,
|
||||
swe7_bin = 82,
|
||||
utf8mb3_bin = 83,
|
||||
big5_bin = 84,
|
||||
euckr_bin = 85,
|
||||
gb2312_bin = 86,
|
||||
gbk_bin = 87,
|
||||
sjis_bin = 88,
|
||||
tis620_bin = 89,
|
||||
ucs2_bin = 90,
|
||||
ujis_bin = 91,
|
||||
geostd8_general_ci = 92,
|
||||
geostd8_bin = 93,
|
||||
latin1_spanish_ci = 94,
|
||||
cp932_japanese_ci = 95,
|
||||
cp932_bin = 96,
|
||||
eucjpms_japanese_ci = 97,
|
||||
eucjpms_bin = 98,
|
||||
cp1250_polish_ci = 99,
|
||||
utf16_unicode_ci = 101,
|
||||
utf16_icelandic_ci = 102,
|
||||
utf16_latvian_ci = 103,
|
||||
utf16_romanian_ci = 104,
|
||||
utf16_slovenian_ci = 105,
|
||||
utf16_polish_ci = 106,
|
||||
utf16_estonian_ci = 107,
|
||||
utf16_spanish_ci = 108,
|
||||
utf16_swedish_ci = 109,
|
||||
utf16_turkish_ci = 110,
|
||||
utf16_czech_ci = 111,
|
||||
utf16_danish_ci = 112,
|
||||
utf16_lithuanian_ci = 113,
|
||||
utf16_slovak_ci = 114,
|
||||
utf16_spanish2_ci = 115,
|
||||
utf16_roman_ci = 116,
|
||||
utf16_persian_ci = 117,
|
||||
utf16_esperanto_ci = 118,
|
||||
utf16_hungarian_ci = 119,
|
||||
utf16_sinhala_ci = 120,
|
||||
utf16_german2_ci = 121,
|
||||
utf16_croatian_ci = 122,
|
||||
utf16_unicode_520_ci = 123,
|
||||
utf16_vietnamese_ci = 124,
|
||||
ucs2_unicode_ci = 128,
|
||||
ucs2_icelandic_ci = 129,
|
||||
ucs2_latvian_ci = 130,
|
||||
ucs2_romanian_ci = 131,
|
||||
ucs2_slovenian_ci = 132,
|
||||
ucs2_polish_ci = 133,
|
||||
ucs2_estonian_ci = 134,
|
||||
ucs2_spanish_ci = 135,
|
||||
ucs2_swedish_ci = 136,
|
||||
ucs2_turkish_ci = 137,
|
||||
ucs2_czech_ci = 138,
|
||||
ucs2_danish_ci = 139,
|
||||
ucs2_lithuanian_ci = 140,
|
||||
ucs2_slovak_ci = 141,
|
||||
ucs2_spanish2_ci = 142,
|
||||
ucs2_roman_ci = 143,
|
||||
ucs2_persian_ci = 144,
|
||||
ucs2_esperanto_ci = 145,
|
||||
ucs2_hungarian_ci = 146,
|
||||
ucs2_sinhala_ci = 147,
|
||||
ucs2_german2_ci = 148,
|
||||
ucs2_croatian_ci = 149,
|
||||
ucs2_unicode_520_ci = 150,
|
||||
ucs2_vietnamese_ci = 151,
|
||||
ucs2_general_mysql500_ci = 159,
|
||||
utf32_unicode_ci = 160,
|
||||
utf32_icelandic_ci = 161,
|
||||
utf32_latvian_ci = 162,
|
||||
utf32_romanian_ci = 163,
|
||||
utf32_slovenian_ci = 164,
|
||||
utf32_polish_ci = 165,
|
||||
utf32_estonian_ci = 166,
|
||||
utf32_spanish_ci = 167,
|
||||
utf32_swedish_ci = 168,
|
||||
utf32_turkish_ci = 169,
|
||||
utf32_czech_ci = 170,
|
||||
utf32_danish_ci = 171,
|
||||
utf32_lithuanian_ci = 172,
|
||||
utf32_slovak_ci = 173,
|
||||
utf32_spanish2_ci = 174,
|
||||
utf32_roman_ci = 175,
|
||||
utf32_persian_ci = 176,
|
||||
utf32_esperanto_ci = 177,
|
||||
utf32_hungarian_ci = 178,
|
||||
utf32_sinhala_ci = 179,
|
||||
utf32_german2_ci = 180,
|
||||
utf32_croatian_ci = 181,
|
||||
utf32_unicode_520_ci = 182,
|
||||
utf32_vietnamese_ci = 183,
|
||||
utf8mb3_unicode_ci = 192,
|
||||
utf8mb3_icelandic_ci = 193,
|
||||
utf8mb3_latvian_ci = 194,
|
||||
utf8mb3_romanian_ci = 195,
|
||||
utf8mb3_slovenian_ci = 196,
|
||||
utf8mb3_polish_ci = 197,
|
||||
utf8mb3_estonian_ci = 198,
|
||||
utf8mb3_spanish_ci = 199,
|
||||
utf8mb3_swedish_ci = 200,
|
||||
utf8mb3_turkish_ci = 201,
|
||||
utf8mb3_czech_ci = 202,
|
||||
utf8mb3_danish_ci = 203,
|
||||
utf8mb3_lithuanian_ci = 204,
|
||||
utf8mb3_slovak_ci = 205,
|
||||
utf8mb3_spanish2_ci = 206,
|
||||
utf8mb3_roman_ci = 207,
|
||||
utf8mb3_persian_ci = 208,
|
||||
utf8mb3_esperanto_ci = 209,
|
||||
utf8mb3_hungarian_ci = 210,
|
||||
utf8mb3_sinhala_ci = 211,
|
||||
utf8mb3_german2_ci = 212,
|
||||
utf8mb3_croatian_ci = 213,
|
||||
utf8mb3_unicode_520_ci = 214,
|
||||
utf8mb3_vietnamese_ci = 215,
|
||||
utf8mb3_general_mysql500_ci = 223,
|
||||
utf8mb4_unicode_ci = 224,
|
||||
utf8mb4_icelandic_ci = 225,
|
||||
utf8mb4_latvian_ci = 226,
|
||||
utf8mb4_romanian_ci = 227,
|
||||
utf8mb4_slovenian_ci = 228,
|
||||
utf8mb4_polish_ci = 229,
|
||||
utf8mb4_estonian_ci = 230,
|
||||
utf8mb4_spanish_ci = 231,
|
||||
utf8mb4_swedish_ci = 232,
|
||||
utf8mb4_turkish_ci = 233,
|
||||
utf8mb4_czech_ci = 234,
|
||||
utf8mb4_danish_ci = 235,
|
||||
utf8mb4_lithuanian_ci = 236,
|
||||
utf8mb4_slovak_ci = 237,
|
||||
utf8mb4_spanish2_ci = 238,
|
||||
utf8mb4_roman_ci = 239,
|
||||
utf8mb4_persian_ci = 240,
|
||||
utf8mb4_esperanto_ci = 241,
|
||||
utf8mb4_hungarian_ci = 242,
|
||||
utf8mb4_sinhala_ci = 243,
|
||||
utf8mb4_german2_ci = 244,
|
||||
utf8mb4_croatian_ci = 245,
|
||||
utf8mb4_unicode_520_ci = 246,
|
||||
utf8mb4_vietnamese_ci = 247,
|
||||
gb18030_chinese_ci = 248,
|
||||
gb18030_bin = 249,
|
||||
gb18030_unicode_520_ci = 250,
|
||||
_,
|
||||
|
||||
pub const default = CharacterSet.utf8mb4_general_ci;
|
||||
|
||||
pub fn label(this: CharacterSet) []const u8 {
|
||||
if (@intFromEnum(this) < 100 and @intFromEnum(this) > 0) {
|
||||
return @tagName(this);
|
||||
}
|
||||
|
||||
return "(unknown)";
|
||||
}
|
||||
};
|
||||
|
||||
// MySQL field types
|
||||
// https://dev.mysql.com/doc/dev/mysql-server/latest/binary__log__types_8h.html#a8935f33b06a3a88ba403c63acd806920
|
||||
pub const FieldType = enum(u8) {
|
||||
MYSQL_TYPE_DECIMAL = 0x00,
|
||||
MYSQL_TYPE_TINY = 0x01,
|
||||
MYSQL_TYPE_SHORT = 0x02,
|
||||
MYSQL_TYPE_LONG = 0x03,
|
||||
MYSQL_TYPE_FLOAT = 0x04,
|
||||
MYSQL_TYPE_DOUBLE = 0x05,
|
||||
MYSQL_TYPE_NULL = 0x06,
|
||||
MYSQL_TYPE_TIMESTAMP = 0x07,
|
||||
MYSQL_TYPE_LONGLONG = 0x08,
|
||||
MYSQL_TYPE_INT24 = 0x09,
|
||||
MYSQL_TYPE_DATE = 0x0a,
|
||||
MYSQL_TYPE_TIME = 0x0b,
|
||||
MYSQL_TYPE_DATETIME = 0x0c,
|
||||
MYSQL_TYPE_YEAR = 0x0d,
|
||||
MYSQL_TYPE_NEWDATE = 0x0e,
|
||||
MYSQL_TYPE_VARCHAR = 0x0f,
|
||||
MYSQL_TYPE_BIT = 0x10,
|
||||
MYSQL_TYPE_TIMESTAMP2 = 0x11,
|
||||
MYSQL_TYPE_DATETIME2 = 0x12,
|
||||
MYSQL_TYPE_TIME2 = 0x13,
|
||||
MYSQL_TYPE_JSON = 0xf5,
|
||||
MYSQL_TYPE_NEWDECIMAL = 0xf6,
|
||||
MYSQL_TYPE_ENUM = 0xf7,
|
||||
MYSQL_TYPE_SET = 0xf8,
|
||||
MYSQL_TYPE_TINY_BLOB = 0xf9,
|
||||
MYSQL_TYPE_MEDIUM_BLOB = 0xfa,
|
||||
MYSQL_TYPE_LONG_BLOB = 0xfb,
|
||||
MYSQL_TYPE_BLOB = 0xfc,
|
||||
MYSQL_TYPE_VAR_STRING = 0xfd,
|
||||
MYSQL_TYPE_STRING = 0xfe,
|
||||
MYSQL_TYPE_GEOMETRY = 0xff,
|
||||
_,
|
||||
|
||||
pub fn fromJS(globalObject: *JSC.JSGlobalObject, value: JSValue) bun.JSError!FieldType {
|
||||
if (value.isEmptyOrUndefinedOrNull()) {
|
||||
return .MYSQL_TYPE_NULL;
|
||||
}
|
||||
|
||||
if (value.isCell()) {
|
||||
const tag = value.jsType();
|
||||
if (tag.isStringLike()) {
|
||||
return .MYSQL_TYPE_VARCHAR;
|
||||
}
|
||||
|
||||
if (tag == .JSDate) {
|
||||
return .MYSQL_TYPE_DATETIME;
|
||||
}
|
||||
|
||||
if (tag.isTypedArray()) {
|
||||
return .MYSQL_TYPE_BLOB;
|
||||
}
|
||||
|
||||
if (tag == .HeapBigInt) {
|
||||
return .MYSQL_TYPE_LONGLONG;
|
||||
}
|
||||
|
||||
if (tag.isArrayLike() and value.getLength(globalObject) > 0) {
|
||||
return FieldType.fromJS(globalObject, value.getIndex(globalObject, 0));
|
||||
}
|
||||
if (globalObject.hasException()) return error.JSError;
|
||||
|
||||
// Ban these types:
|
||||
if (tag == .NumberObject) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
if (tag == .BooleanObject) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
// It's something internal
|
||||
if (!tag.isIndexable()) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
// We will JSON.stringify anything else.
|
||||
if (tag.isObject()) {
|
||||
return .MYSQL_TYPE_JSON;
|
||||
}
|
||||
}
|
||||
|
||||
if (value.isAnyInt()) {
|
||||
const int = value.toInt64();
|
||||
if (int >= std.math.minInt(i32) and int <= std.math.maxInt(i32)) {
|
||||
return .MYSQL_TYPE_LONG;
|
||||
}
|
||||
|
||||
return .MYSQL_TYPE_LONGLONG;
|
||||
}
|
||||
|
||||
if (value.isNumber()) {
|
||||
return .MYSQL_TYPE_DOUBLE;
|
||||
}
|
||||
|
||||
if (value.isBoolean()) {
|
||||
return .MYSQL_TYPE_TINY;
|
||||
}
|
||||
|
||||
return .MYSQL_TYPE_VARCHAR;
|
||||
}
|
||||
|
||||
pub fn isBinaryFormatSupported(this: FieldType) bool {
|
||||
return switch (this) {
|
||||
.MYSQL_TYPE_TINY,
|
||||
.MYSQL_TYPE_SHORT,
|
||||
.MYSQL_TYPE_LONG,
|
||||
.MYSQL_TYPE_FLOAT,
|
||||
.MYSQL_TYPE_DOUBLE,
|
||||
.MYSQL_TYPE_LONGLONG,
|
||||
.MYSQL_TYPE_TIME,
|
||||
.MYSQL_TYPE_DATE,
|
||||
.MYSQL_TYPE_DATETIME,
|
||||
.MYSQL_TYPE_TIMESTAMP,
|
||||
.MYSQL_TYPE_TINY_BLOB,
|
||||
.MYSQL_TYPE_MEDIUM_BLOB,
|
||||
.MYSQL_TYPE_LONG_BLOB,
|
||||
.MYSQL_TYPE_BLOB,
|
||||
.MYSQL_TYPE_STRING,
|
||||
.MYSQL_TYPE_VARCHAR,
|
||||
.MYSQL_TYPE_VAR_STRING,
|
||||
.MYSQL_TYPE_JSON,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toJSType(this: FieldType) JSValue.JSType {
|
||||
return switch (this) {
|
||||
.MYSQL_TYPE_TINY,
|
||||
.MYSQL_TYPE_SHORT,
|
||||
.MYSQL_TYPE_LONG,
|
||||
.MYSQL_TYPE_INT24,
|
||||
.MYSQL_TYPE_YEAR,
|
||||
=> .NumberObject,
|
||||
|
||||
.MYSQL_TYPE_LONGLONG => .BigInt64Array,
|
||||
.MYSQL_TYPE_FLOAT,
|
||||
.MYSQL_TYPE_DOUBLE,
|
||||
.MYSQL_TYPE_DECIMAL,
|
||||
.MYSQL_TYPE_NEWDECIMAL,
|
||||
=> .Float64Array,
|
||||
|
||||
.MYSQL_TYPE_NULL => .Null,
|
||||
.MYSQL_TYPE_JSON => .Object,
|
||||
.MYSQL_TYPE_TIMESTAMP,
|
||||
.MYSQL_TYPE_DATETIME,
|
||||
.MYSQL_TYPE_DATE,
|
||||
.MYSQL_TYPE_TIME,
|
||||
=> .JSDate,
|
||||
|
||||
else => .String,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Add this near the top of the file
|
||||
const BoundedArray = std.BoundedArray;
|
||||
pub const Value = union(enum) {
|
||||
null,
|
||||
bool: bool,
|
||||
short: i16,
|
||||
ushort: u16,
|
||||
int: i32,
|
||||
uint: u32,
|
||||
long: i64,
|
||||
ulong: u64,
|
||||
float: f32,
|
||||
double: f64,
|
||||
|
||||
string: JSC.ZigString.Slice,
|
||||
string_data: Data,
|
||||
bytes: JSC.ZigString.Slice,
|
||||
bytes_data: Data,
|
||||
date: DateTime,
|
||||
timestamp: Timestamp,
|
||||
time: Time,
|
||||
decimal: Decimal,
|
||||
|
||||
pub fn deinit(this: *Value, allocator: std.mem.Allocator) void {
|
||||
switch (this.*) {
|
||||
inline .string, .bytes => |*slice| slice.deinit(),
|
||||
inline .string_data, .bytes_data => |*data| data.deinit(),
|
||||
.decimal => |*decimal| decimal.deinit(allocator),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toData(
|
||||
this: *const Value,
|
||||
field_type: FieldType,
|
||||
) !Data {
|
||||
var buffer: [15]u8 = undefined; // Large enough for all fixed-size types
|
||||
var stream = std.io.fixedBufferStream(&buffer);
|
||||
var writer = stream.writer();
|
||||
switch (this.*) {
|
||||
.null => return Data{ .empty = {} },
|
||||
.bool => |b| try writer.writeByte(if (b) 1 else 0),
|
||||
.short => |s| try writer.writeInt(i16, s, .little),
|
||||
.ushort => |s| try writer.writeInt(u16, s, .little),
|
||||
.int => |i| try writer.writeInt(i32, i, .little),
|
||||
.uint => |i| try writer.writeInt(u32, i, .little),
|
||||
.long => |l| try writer.writeInt(i64, l, .little),
|
||||
.ulong => |l| try writer.writeInt(u64, l, .little),
|
||||
.float => |f| try writer.writeInt(u32, @bitCast(f), .little),
|
||||
.double => |d| try writer.writeInt(u64, @bitCast(d), .little),
|
||||
inline .date, .timestamp, .time => |d| {
|
||||
stream.pos = d.toBinary(field_type, &buffer);
|
||||
},
|
||||
.decimal => |dec| return try dec.toBinary(field_type),
|
||||
.string_data, .bytes_data => |data| return data,
|
||||
.string, .bytes => |slice| return if (slice.len > 0)
|
||||
Data{ .temporary = slice.slice() }
|
||||
else
|
||||
Data{ .empty = {} },
|
||||
}
|
||||
|
||||
return try Data.create(buffer[0..stream.pos], bun.default_allocator);
|
||||
}
|
||||
|
||||
pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject, field_type: FieldType, unsigned: bool) !Value {
|
||||
// TODO: Handle unsigned
|
||||
_ = unsigned; // autofix
|
||||
|
||||
return switch (field_type) {
|
||||
.MYSQL_TYPE_TINY => Value{ .bool = value.toBoolean() },
|
||||
.MYSQL_TYPE_SHORT => Value{ .short = globalObject.validateIntegerRange(value, i16, 0, .{ .min = std.math.minInt(i16), .max = std.math.maxInt(i16) }) orelse return error.JSError },
|
||||
.MYSQL_TYPE_LONG => Value{ .int = globalObject.validateIntegerRange(value, i32, 0, .{ .min = std.math.minInt(i32), .max = std.math.maxInt(i32) }) orelse return error.JSError },
|
||||
.MYSQL_TYPE_LONGLONG => Value{ .long = globalObject.validateIntegerRange(value, i64, 0, .{ .min = std.math.minInt(i64), .max = std.math.maxInt(i64) }) orelse return error.JSError },
|
||||
.MYSQL_TYPE_FLOAT => Value{ .float = @floatCast(try value.coerceToDoubleCheckingErrors(globalObject)) },
|
||||
.MYSQL_TYPE_DOUBLE => Value{ .double = try value.coerceToDoubleCheckingErrors(globalObject) },
|
||||
.MYSQL_TYPE_TIME => Value{ .time = try Time.fromJS(value, globalObject) },
|
||||
.MYSQL_TYPE_DATE => Value{ .date = try DateTime.fromJS(value, globalObject) },
|
||||
.MYSQL_TYPE_DATETIME => Value{ .date = try DateTime.fromJS(value, globalObject) },
|
||||
.MYSQL_TYPE_TIMESTAMP => Value{ .timestamp = try Timestamp.fromJS(value, globalObject) },
|
||||
.MYSQL_TYPE_TINY_BLOB, .MYSQL_TYPE_MEDIUM_BLOB, .MYSQL_TYPE_LONG_BLOB, .MYSQL_TYPE_BLOB => {
|
||||
if (value.asArrayBuffer(globalObject)) |array_buffer| {
|
||||
return Value{ .bytes = JSC.ZigString.Slice.fromUTF8NeverFree(array_buffer.slice()) };
|
||||
}
|
||||
|
||||
if (value.as(JSC.WebCore.Blob)) |blob| {
|
||||
if (blob.needsToReadFile()) {
|
||||
return globalObject.throwInvalidArguments("File blobs are not supported", .{});
|
||||
}
|
||||
return Value{ .bytes = JSC.ZigString.Slice.fromUTF8NeverFree(blob.sharedView()) };
|
||||
}
|
||||
|
||||
if (value.isString()) {
|
||||
const str = bun.String.tryFromJS(value, globalObject) orelse return error.JSError;
|
||||
defer str.deref();
|
||||
return Value{ .string = str.toUTF8(bun.default_allocator) };
|
||||
}
|
||||
|
||||
return globalObject.throwInvalidArguments("Expected a string, blob, or array buffer", .{});
|
||||
},
|
||||
|
||||
.MYSQL_TYPE_JSON => {
|
||||
var str: bun.String = bun.String.empty;
|
||||
value.jsonStringify(globalObject, 0, &str);
|
||||
if (globalObject.hasException()) return error.JSError;
|
||||
defer str.deref();
|
||||
return Value{ .string = str.toUTF8(bun.default_allocator) };
|
||||
},
|
||||
|
||||
// .MYSQL_TYPE_VARCHAR, .MYSQL_TYPE_VAR_STRING, .MYSQL_TYPE_STRING => {
|
||||
else => {
|
||||
const str = bun.String.tryFromJS(value, globalObject) orelse return error.JSError;
|
||||
defer str.deref();
|
||||
return Value{ .string = str.toUTF8(bun.default_allocator) };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub const Timestamp = struct {
|
||||
seconds: u32,
|
||||
microseconds: u24,
|
||||
|
||||
pub fn fromData(data: *const Data) !Timestamp {
|
||||
return fromBinary(data.slice());
|
||||
}
|
||||
|
||||
pub fn fromBinary(val: []const u8) Timestamp {
|
||||
return .{
|
||||
// Bytes 0-3: [seconds] (32-bit little-endian unsigned integer)
|
||||
// Number of seconds since Unix epoch
|
||||
.seconds = std.mem.readInt(u32, val[0..4], .little),
|
||||
// Bytes 4-6: [microseconds] (24-bit little-endian unsigned integer)
|
||||
.microseconds = if (val.len == 7) std.mem.readInt(u24, val[4..7], .little) else 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fromUnixTimestamp(timestamp: i64) Timestamp {
|
||||
const timestamp_u64: u64 = @intCast(@max(timestamp, 0));
|
||||
return .{
|
||||
.seconds = @truncate(timestamp_u64),
|
||||
.microseconds = @truncate(@mod(timestamp_u64, 1_000_000)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fromJS(value: JSValue, globalObject: *JSC.JSGlobalObject) !Timestamp {
|
||||
if (value.isDate()) {
|
||||
const ts = @divFloor(@as(i64, @intFromFloat(value.getUnixTimestamp())), 1000);
|
||||
return Timestamp.fromUnixTimestamp(ts);
|
||||
}
|
||||
|
||||
if (value.isNumber()) {
|
||||
const double = value.asNumber();
|
||||
return Timestamp.fromUnixTimestamp(@intFromFloat(double));
|
||||
}
|
||||
|
||||
return globalObject.throwInvalidArguments("Expected a date or number", .{});
|
||||
}
|
||||
|
||||
pub fn toUnixTimestamp(this: Timestamp) f64 {
|
||||
return @as(f64, @floatFromInt(this.seconds)) + @as(f64, @floatFromInt(this.microseconds)) / 1_000_000;
|
||||
}
|
||||
|
||||
pub fn toJS(this: Timestamp, globalObject: *JSC.JSGlobalObject) JSValue {
|
||||
const timestamp: f64 = @floatCast(this.toUnixTimestamp());
|
||||
return JSValue.fromDateNumber(globalObject, timestamp * 1000);
|
||||
}
|
||||
|
||||
pub fn toBinary(this: *const Timestamp, field_type: FieldType, buffer: []u8) u8 {
|
||||
std.mem.writeInt(u32, buffer[0..4], this.seconds, .little);
|
||||
std.mem.writeInt(u24, buffer[4..7], this.microseconds, .little);
|
||||
return switch (field_type) {
|
||||
// [4 bytes] - unix timestamp as uint32_t LE
|
||||
.MYSQL_TYPE_TIMESTAMP => 4,
|
||||
// [7 bytes] - unix timestamp as uint32_t LE + microseconds as uint24_t LE
|
||||
.MYSQL_TYPE_TIMESTAMP2 => 7,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const DateTime = struct {
|
||||
year: u16 = 0,
|
||||
month: u8 = 0,
|
||||
day: u8 = 0,
|
||||
hour: u8 = 0,
|
||||
minute: u8 = 0,
|
||||
second: u8 = 0,
|
||||
microsecond: u32 = 0,
|
||||
|
||||
pub fn fromData(data: *const Data) !DateTime {
|
||||
return fromBinary(data.slice());
|
||||
}
|
||||
|
||||
pub fn fromBinaryDate(val: []const u8) DateTime {
|
||||
return .{
|
||||
.year = std.mem.readInt(u16, val[0..2], .little),
|
||||
.month = val[2],
|
||||
.day = val[3],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fromBinary(val: []const u8) DateTime {
|
||||
switch (val.len) {
|
||||
4 => {
|
||||
// Byte 1: [year LSB] (8 bits of year)
|
||||
// Byte 2: [year MSB] (8 bits of year)
|
||||
// Byte 3: [month] (8-bit unsigned integer, 1-12)
|
||||
// Byte 4: [day] (8-bit unsigned integer, 1-31)
|
||||
return .{
|
||||
.year = std.mem.readInt(u16, val[0..2], .little),
|
||||
.month = val[2],
|
||||
.day = val[3],
|
||||
};
|
||||
},
|
||||
7 => {
|
||||
// Byte 1: [year LSB] (8 bits of year)
|
||||
// Byte 2: [year MSB] (8 bits of year)
|
||||
// Byte 3: [month] (8-bit unsigned integer, 1-12)
|
||||
// Byte 4: [day] (8-bit unsigned integer, 1-31)
|
||||
// Byte 5: [hour] (8-bit unsigned integer, 0-23)
|
||||
// Byte 6: [minute] (8-bit unsigned integer, 0-59)
|
||||
// Byte 7: [second] (8-bit unsigned integer, 0-59)
|
||||
return .{
|
||||
.year = std.mem.readInt(u16, val[0..2], .little),
|
||||
.month = val[3],
|
||||
.day = val[4],
|
||||
.hour = val[5],
|
||||
.minute = val[6],
|
||||
.second = val[7],
|
||||
};
|
||||
},
|
||||
11 => {
|
||||
// Byte 1: [year LSB] (8 bits of year)
|
||||
// Byte 2: [year MSB] (8 bits of year)
|
||||
// Byte 3: [month] (8-bit unsigned integer, 1-12)
|
||||
// Byte 4: [day] (8-bit unsigned integer, 1-31)
|
||||
// Byte 5: [hour] (8-bit unsigned integer, 0-23)
|
||||
// Byte 6: [minute] (8-bit unsigned integer, 0-59)
|
||||
// Byte 7: [second] (8-bit unsigned integer, 0-59)
|
||||
// Byte 8-11: [microseconds] (32-bit little-endian unsigned integer
|
||||
return .{
|
||||
.year = std.mem.readInt(u16, val[0..2], .little),
|
||||
.month = val[3],
|
||||
.day = val[4],
|
||||
.hour = val[5],
|
||||
.minute = val[6],
|
||||
.second = val[7],
|
||||
.microsecond = std.mem.readInt(u32, val[8..12], .little),
|
||||
};
|
||||
},
|
||||
else => bun.Output.panic("Invalid datetime length: {d}", .{val.len}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toBinary(this: *const DateTime, field_type: FieldType, buffer: []u8) u8 {
|
||||
switch (field_type) {
|
||||
.MYSQL_TYPE_YEAR => {
|
||||
std.mem.writeInt(u16, buffer[0..2], this.year, .little);
|
||||
return 2;
|
||||
},
|
||||
.MYSQL_TYPE_DATE => {
|
||||
std.mem.writeInt(u16, buffer[0..2], this.year, .little);
|
||||
buffer[2] = this.month;
|
||||
buffer[3] = this.day;
|
||||
return 4;
|
||||
},
|
||||
.MYSQL_TYPE_DATETIME => {
|
||||
std.mem.writeInt(u16, buffer[0..2], this.year, .little);
|
||||
buffer[2] = this.month;
|
||||
buffer[3] = this.day;
|
||||
buffer[4] = this.hour;
|
||||
buffer[5] = this.minute;
|
||||
buffer[6] = this.second;
|
||||
if (this.microsecond == 0) {
|
||||
return 7;
|
||||
} else {
|
||||
std.mem.writeInt(u32, buffer[7..11], this.microsecond, .little);
|
||||
return 11;
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toUnixTimestamp(this: *const DateTime) i64 {
|
||||
// Convert to Unix timestamp (seconds since 1970-01-01)
|
||||
var ts: i64 = 0;
|
||||
const days = gregorianDays(this.year, this.month, this.day);
|
||||
ts += days * 86400;
|
||||
ts += @as(i64, this.hour) * 3600;
|
||||
ts += @as(i64, this.minute) * 60;
|
||||
ts += this.second;
|
||||
return ts;
|
||||
}
|
||||
|
||||
pub fn fromUnixTimestamp(timestamp: i64) DateTime {
|
||||
var ts = timestamp;
|
||||
const days = @divFloor(ts, 86400);
|
||||
ts = @mod(ts, 86400);
|
||||
|
||||
const hour = @divFloor(ts, 3600);
|
||||
ts = @mod(ts, 3600);
|
||||
|
||||
const minute = @divFloor(ts, 60);
|
||||
const second = @mod(ts, 60);
|
||||
|
||||
const date = gregorianDate(@intCast(days));
|
||||
return .{
|
||||
.year = date.year,
|
||||
.month = date.month,
|
||||
.day = date.day,
|
||||
.hour = @intCast(hour),
|
||||
.minute = @intCast(minute),
|
||||
.second = @intCast(second),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toJS(this: DateTime, globalObject: *JSC.JSGlobalObject) JSValue {
|
||||
const ts = this.toUnixTimestamp();
|
||||
return JSValue.fromDateNumber(globalObject, @floatFromInt(ts * 1000));
|
||||
}
|
||||
|
||||
pub fn fromJS(value: JSValue, globalObject: *JSC.JSGlobalObject) !DateTime {
|
||||
if (value.isDate()) {
|
||||
const ts = @divFloor(@as(i64, @intFromFloat(value.getUnixTimestamp())), 1000);
|
||||
return DateTime.fromUnixTimestamp(ts);
|
||||
}
|
||||
|
||||
if (value.isNumber()) {
|
||||
const double = value.asNumber();
|
||||
return DateTime.fromUnixTimestamp(@intFromFloat(double));
|
||||
}
|
||||
|
||||
return globalObject.throwInvalidArguments("Expected a date or number", .{});
|
||||
}
|
||||
};
|
||||
|
||||
pub const Time = struct {
|
||||
negative: bool = false,
|
||||
days: u32 = 0,
|
||||
hours: u8 = 0,
|
||||
minutes: u8 = 0,
|
||||
seconds: u8 = 0,
|
||||
microseconds: u32 = 0,
|
||||
|
||||
pub fn fromJS(value: JSValue, globalObject: *JSC.JSGlobalObject) !Time {
|
||||
if (value.isDate()) {
|
||||
const ts = @divFloor(@as(i64, @intFromFloat(value.getUnixTimestamp())), 1000);
|
||||
return Time.fromUnixTimestamp(ts);
|
||||
} else if (value.isAnyInt()) {
|
||||
const int = value.toInt64();
|
||||
return Time.fromUnixTimestamp(int);
|
||||
} else {
|
||||
return globalObject.throwInvalidArguments("Expected a date or number", .{});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fromUnixTimestamp(timestamp: i64) Time {
|
||||
return .{
|
||||
.negative = timestamp < 0,
|
||||
.days = @intCast(@divFloor(timestamp, 86400)),
|
||||
.hours = @intCast(@divFloor(@mod(timestamp, 86400), 3600)),
|
||||
.minutes = @intCast(@divFloor(@mod(timestamp, 3600), 60)),
|
||||
.seconds = @intCast(@mod(timestamp, 60)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toUnixTimestamp(this: *const Time) i64 {
|
||||
var total_ms: i64 = 0;
|
||||
total_ms +|= @as(i64, this.days) *| 86400000;
|
||||
total_ms +|= @as(i64, this.hours) *| 3600000;
|
||||
total_ms +|= @as(i64, this.minutes) *| 60000;
|
||||
total_ms +|= @as(i64, this.seconds) *| 1000;
|
||||
total_ms +|= @divFloor(this.microseconds, 1000);
|
||||
return total_ms;
|
||||
}
|
||||
|
||||
pub fn fromData(data: *const Data) !Time {
|
||||
return fromBinary(data.slice());
|
||||
}
|
||||
|
||||
pub fn fromBinary(val: []const u8) Time {
|
||||
if (val.len == 0) {
|
||||
return Time{};
|
||||
}
|
||||
|
||||
var time = Time{};
|
||||
const length = val[0];
|
||||
|
||||
if (length >= 8) {
|
||||
time.negative = val[1] != 0;
|
||||
time.days = std.mem.readInt(u32, val[2..6], .little);
|
||||
time.hours = val[6];
|
||||
time.minutes = val[7];
|
||||
time.seconds = val[8];
|
||||
}
|
||||
|
||||
if (length > 8) {
|
||||
time.microseconds = std.mem.readInt(u32, val[9..13], .little);
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
pub fn toJS(this: Time, globalObject: *JSC.JSGlobalObject) JSValue {
|
||||
_ = globalObject; // autofix
|
||||
var total_ms: i64 = 0;
|
||||
total_ms +|= @as(i64, this.days) * 86400000;
|
||||
total_ms +|= @as(i64, this.hours) * 3600000;
|
||||
total_ms +|= @as(i64, this.minutes) * 60000;
|
||||
total_ms +|= @as(i64, this.seconds) * 1000;
|
||||
total_ms +|= @divFloor(this.microseconds, 1000);
|
||||
|
||||
if (this.negative) {
|
||||
total_ms = -total_ms;
|
||||
}
|
||||
|
||||
return JSValue.jsDoubleNumber(@floatFromInt(total_ms));
|
||||
}
|
||||
|
||||
pub fn toBinary(this: *const Time, field_type: FieldType, buffer: []u8) u8 {
|
||||
switch (field_type) {
|
||||
.MYSQL_TYPE_TIME, .MYSQL_TYPE_TIME2 => {
|
||||
buffer[1] = if (this.negative) 1 else 0;
|
||||
std.mem.writeInt(u32, buffer[2..6], this.days, .little);
|
||||
buffer[6] = this.hours;
|
||||
buffer[7] = this.minutes;
|
||||
buffer[8] = this.seconds;
|
||||
if (this.microseconds == 0) {
|
||||
buffer[0] = 8; // length
|
||||
return 9;
|
||||
} else {
|
||||
buffer[0] = 12; // length
|
||||
std.mem.writeInt(u32, buffer[9..][0..4], this.microseconds, .little);
|
||||
return 12;
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Decimal = struct {
|
||||
// MySQL DECIMAL is stored as a sequence of base-10 digits
|
||||
digits: []const u8,
|
||||
scale: u8,
|
||||
negative: bool,
|
||||
|
||||
pub fn deinit(this: *Decimal, allocator: std.mem.Allocator) void {
|
||||
allocator.free(this.digits);
|
||||
}
|
||||
|
||||
pub fn toJS(this: Decimal, globalObject: *JSC.JSGlobalObject) JSValue {
|
||||
var stack = std.heap.stackFallback(64, bun.default_allocator);
|
||||
var str = std.ArrayList(u8).init(stack.get());
|
||||
defer str.deinit();
|
||||
|
||||
if (this.negative) {
|
||||
str.append('-') catch return JSValue.jsNumber(0);
|
||||
}
|
||||
|
||||
const decimal_pos = this.digits.len - this.scale;
|
||||
for (this.digits, 0..) |digit, i| {
|
||||
if (i == decimal_pos and this.scale > 0) {
|
||||
str.append('.') catch return JSValue.jsNumber(0);
|
||||
}
|
||||
str.append(digit + '0') catch return JSValue.jsNumber(0);
|
||||
}
|
||||
|
||||
var js_str = bun.String.createUTF8(str.items);
|
||||
return js_str.transferToJS(globalObject);
|
||||
}
|
||||
|
||||
pub fn toBinary(this: Decimal, field_type: FieldType) !Data {
|
||||
_ = this; // autofix
|
||||
_ = field_type; // autofix
|
||||
bun.todoPanic(@src(), "Decimal.toBinary not implemented", .{});
|
||||
}
|
||||
|
||||
pub fn fromData(data: *const Data) !Decimal {
|
||||
return fromBinary(data.slice());
|
||||
}
|
||||
|
||||
pub fn fromBinary(val: []const u8) Decimal {
|
||||
_ = val; // autofix
|
||||
// TODO: handle overflow
|
||||
bun.todoPanic(@src(), "Decimal.fromBinary not implemented", .{});
|
||||
}
|
||||
};
|
||||
|
||||
pub fn toJS(this: *const Value, globalObject: *JSC.JSGlobalObject) JSValue {
|
||||
return switch (this.*) {
|
||||
.null => JSValue.jsNull(),
|
||||
.bool => |b| JSValue.jsBoolean(b),
|
||||
inline .string, .string_data => |*str| {
|
||||
var out = bun.String.createUTF8(str.slice());
|
||||
return out.transferToJS(globalObject);
|
||||
},
|
||||
inline .bytes, .bytes_data => |*data| JSC.ArrayBuffer.createBuffer(globalObject, data.slice()),
|
||||
inline .long, .int, .float, .double, .short, .ushort, .uint, .ulong => |t| JSValue.jsNumber(t),
|
||||
inline .timestamp, .date, .time, .decimal => |*d| d.toJS(globalObject),
|
||||
};
|
||||
}
|
||||
|
||||
export fn MySQL__ValueToJS(globalObject: *JSC.JSGlobalObject, value: *Value) JSValue {
|
||||
return value.toJS(globalObject);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper functions for date calculations
|
||||
fn isLeapYear(year: u16) bool {
|
||||
return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0;
|
||||
}
|
||||
|
||||
fn daysInMonth(year: u16, month: u8) u8 {
|
||||
const days = [_]u8{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
if (month == 2 and isLeapYear(year)) {
|
||||
return 29;
|
||||
}
|
||||
return days[month - 1];
|
||||
}
|
||||
|
||||
fn gregorianDays(year: u16, month: u8, day: u8) i32 {
|
||||
// Calculate days since 1970-01-01
|
||||
const y = @as(i32, year) - 1970;
|
||||
var days: i32 = y * 365 + @divFloor(y, 4) - @divFloor(y, 100) + @divFloor(y, 400);
|
||||
|
||||
var m = month;
|
||||
while (m > 1) : (m -= 1) {
|
||||
days += daysInMonth(year, m - 1);
|
||||
}
|
||||
|
||||
return days + day - 1;
|
||||
}
|
||||
|
||||
const Date = struct {
|
||||
year: u16,
|
||||
month: u8,
|
||||
day: u8,
|
||||
};
|
||||
|
||||
fn gregorianDate(days: i32) Date {
|
||||
// Convert days since 1970-01-01 to year/month/day
|
||||
var d = days;
|
||||
var y: u16 = 1970;
|
||||
|
||||
while (d >= 365 + @as(u16, @intFromBool(isLeapYear(y)))) : (y += 1) {
|
||||
d -= 365 + @as(u16, @intFromBool(isLeapYear(y)));
|
||||
}
|
||||
|
||||
var m: u8 = 1;
|
||||
while (d >= daysInMonth(y, m)) : (m += 1) {
|
||||
d -= daysInMonth(y, m);
|
||||
}
|
||||
|
||||
return .{
|
||||
.year = y,
|
||||
.month = m,
|
||||
.day = @intCast(d + 1),
|
||||
};
|
||||
}
|
||||
@@ -21,134 +21,14 @@ pub const SSLMode = enum(u8) {
|
||||
verify_ca = 3,
|
||||
verify_full = 4,
|
||||
};
|
||||
|
||||
pub const Data = union(enum) {
|
||||
owned: bun.ByteList,
|
||||
temporary: []const u8,
|
||||
empty: void,
|
||||
|
||||
pub fn toOwned(this: @This()) !bun.ByteList {
|
||||
return switch (this) {
|
||||
.owned => this.owned,
|
||||
.temporary => bun.ByteList.init(try bun.default_allocator.dupe(u8, this.temporary)),
|
||||
.empty => bun.ByteList.init(&.{}),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(this: *@This()) void {
|
||||
switch (this.*) {
|
||||
.owned => this.owned.deinitWithAllocator(bun.default_allocator),
|
||||
.temporary => {},
|
||||
.empty => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero bytes before deinit
|
||||
/// Generally, for security reasons.
|
||||
pub fn zdeinit(this: *@This()) void {
|
||||
switch (this.*) {
|
||||
.owned => {
|
||||
|
||||
// Zero bytes before deinit
|
||||
@memset(this.owned.slice(), 0);
|
||||
|
||||
this.owned.deinitWithAllocator(bun.default_allocator);
|
||||
},
|
||||
.temporary => {},
|
||||
.empty => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice(this: @This()) []const u8 {
|
||||
return switch (this) {
|
||||
.owned => this.owned.slice(),
|
||||
.temporary => this.temporary,
|
||||
.empty => "",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn substring(this: @This(), start_index: usize, end_index: usize) Data {
|
||||
return switch (this) {
|
||||
.owned => .{ .temporary = this.owned.slice()[start_index..end_index] },
|
||||
.temporary => .{ .temporary = this.temporary[start_index..end_index] },
|
||||
.empty => .{ .empty = {} },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sliceZ(this: @This()) [:0]const u8 {
|
||||
return switch (this) {
|
||||
.owned => this.owned.slice()[0..this.owned.len :0],
|
||||
.temporary => this.temporary[0..this.temporary.len :0],
|
||||
.empty => "",
|
||||
};
|
||||
}
|
||||
};
|
||||
pub const Data = sql.Data;
|
||||
pub const protocol = @import("./postgres/postgres_protocol.zig");
|
||||
pub const types = @import("./postgres/postgres_types.zig");
|
||||
|
||||
const sql = @import("./shared_sql.zig");
|
||||
const Socket = uws.AnySocket;
|
||||
const PreparedStatementsMap = std.HashMapUnmanaged(u64, *PostgresSQLStatement, bun.IdentityContext(u64), 80);
|
||||
|
||||
const SocketMonitor = struct {
|
||||
const DebugSocketMonitorWriter = struct {
|
||||
var file: std.fs.File = undefined;
|
||||
var enabled = false;
|
||||
var check = std.once(load);
|
||||
pub fn write(data: []const u8) void {
|
||||
file.writeAll(data) catch {};
|
||||
}
|
||||
|
||||
fn load() void {
|
||||
if (bun.getenvZAnyCase("BUN_POSTGRES_SOCKET_MONITOR")) |monitor| {
|
||||
enabled = true;
|
||||
file = std.fs.cwd().createFile(monitor, .{ .truncate = true }) catch {
|
||||
enabled = false;
|
||||
return;
|
||||
};
|
||||
debug("writing to {s}", .{monitor});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const DebugSocketMonitorReader = struct {
|
||||
var file: std.fs.File = undefined;
|
||||
var enabled = false;
|
||||
var check = std.once(load);
|
||||
|
||||
fn load() void {
|
||||
if (bun.getenvZAnyCase("BUN_POSTGRES_SOCKET_MONITOR_READER")) |monitor| {
|
||||
enabled = true;
|
||||
file = std.fs.cwd().createFile(monitor, .{ .truncate = true }) catch {
|
||||
enabled = false;
|
||||
return;
|
||||
};
|
||||
debug("duplicating reads to {s}", .{monitor});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(data: []const u8) void {
|
||||
file.writeAll(data) catch {};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn write(data: []const u8) void {
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
DebugSocketMonitorWriter.check.call();
|
||||
if (DebugSocketMonitorWriter.enabled) {
|
||||
DebugSocketMonitorWriter.write(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(data: []const u8) void {
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
DebugSocketMonitorReader.check.call();
|
||||
if (DebugSocketMonitorReader.enabled) {
|
||||
DebugSocketMonitorReader.write(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const SocketMonitor = @import("./SocketMonitor.zig");
|
||||
const QueryBindingIterator = sql.QueryBindingIterator;
|
||||
|
||||
pub const PostgresSQLContext = struct {
|
||||
tcp: ?*uws.SocketContext = null,
|
||||
@@ -415,7 +295,8 @@ pub const PostgresSQLQuery = struct {
|
||||
|
||||
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*PostgresSQLQuery {
|
||||
_ = callframe;
|
||||
return globalThis.throw2("PostgresSQLQuery cannot be constructed directly", .{});
|
||||
globalThis.ERR_ILLEGAL_CONSTRUCTOR("PostgresSQLQuery cannot be constructed directly", .{}).throw();
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
pub fn estimatedSize(this: *PostgresSQLQuery) usize {
|
||||
@@ -1479,7 +1360,11 @@ pub const PostgresSQLConnection = struct {
|
||||
.password = password,
|
||||
.options = options,
|
||||
.options_buf = options_buf,
|
||||
.socket = undefined,
|
||||
.socket = .{
|
||||
.SocketTCP = .{
|
||||
.socket = .{ .detached = {} },
|
||||
},
|
||||
},
|
||||
.requests = PostgresRequest.Queue.init(bun.default_allocator),
|
||||
.statements = PreparedStatementsMap{},
|
||||
.tls_config = tls_config,
|
||||
@@ -1737,7 +1622,7 @@ pub const PostgresSQLConnection = struct {
|
||||
|
||||
pub const Value = extern union {
|
||||
null: u8,
|
||||
string: bun.WTF.StringImpl,
|
||||
string: ?bun.WTF.StringImpl,
|
||||
float8: f64,
|
||||
int4: i32,
|
||||
int8: i64,
|
||||
@@ -1782,7 +1667,9 @@ pub const PostgresSQLConnection = struct {
|
||||
|
||||
switch (this.tag) {
|
||||
.string => {
|
||||
this.value.string.deref();
|
||||
if (this.value.string != null) {
|
||||
this.value.string.?.deref();
|
||||
}
|
||||
},
|
||||
.json => {
|
||||
this.value.json.deref();
|
||||
@@ -2563,124 +2450,6 @@ pub const PostgresSQLStatement = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const QueryBindingIterator = union(enum) {
|
||||
array: JSC.JSArrayIterator,
|
||||
objects: ObjectIterator,
|
||||
|
||||
pub fn init(array: JSValue, columns: JSValue, globalObject: *JSC.JSGlobalObject) QueryBindingIterator {
|
||||
if (columns.isEmptyOrUndefinedOrNull()) {
|
||||
return .{ .array = JSC.JSArrayIterator.init(array, globalObject) };
|
||||
}
|
||||
|
||||
return .{
|
||||
.objects = .{
|
||||
.array = array,
|
||||
.columns = columns,
|
||||
.globalObject = globalObject,
|
||||
.columns_count = columns.getLength(globalObject),
|
||||
.array_length = array.getLength(globalObject),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub const ObjectIterator = struct {
|
||||
array: JSValue,
|
||||
columns: JSValue = .zero,
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
cell_i: usize = 0,
|
||||
row_i: usize = 0,
|
||||
current_row: JSC.JSValue = .zero,
|
||||
columns_count: usize = 0,
|
||||
array_length: usize = 0,
|
||||
any_failed: bool = false,
|
||||
|
||||
pub fn next(this: *ObjectIterator) ?JSC.JSValue {
|
||||
if (this.row_i >= this.array_length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cell_i = this.cell_i;
|
||||
this.cell_i += 1;
|
||||
const row_i = this.row_i;
|
||||
|
||||
const globalObject = this.globalObject;
|
||||
|
||||
if (this.current_row == .zero) {
|
||||
this.current_row = JSC.JSObject.getIndex(this.array, globalObject, @intCast(row_i));
|
||||
if (this.current_row.isEmptyOrUndefinedOrNull()) {
|
||||
if (!globalObject.hasException())
|
||||
globalObject.throw("Expected a row to be returned at index {d}", .{row_i});
|
||||
this.any_failed = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
defer {
|
||||
if (this.cell_i >= this.columns_count) {
|
||||
this.cell_i = 0;
|
||||
this.current_row = .zero;
|
||||
this.row_i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const property = JSC.JSObject.getIndex(this.columns, globalObject, @intCast(cell_i));
|
||||
if (property == .zero or property == .undefined) {
|
||||
if (!globalObject.hasException())
|
||||
globalObject.throw("Expected a column at index {d} in row {d}", .{ cell_i, row_i });
|
||||
this.any_failed = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = this.current_row.getOwnByValue(globalObject, property);
|
||||
if (value == .zero or value == .undefined) {
|
||||
if (!globalObject.hasException())
|
||||
globalObject.throw("Expected a value at index {d} in row {d}", .{ cell_i, row_i });
|
||||
this.any_failed = true;
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn next(this: *QueryBindingIterator) ?JSC.JSValue {
|
||||
return switch (this.*) {
|
||||
.array => |*iter| iter.next(),
|
||||
.objects => |*iter| iter.next(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn anyFailed(this: *const QueryBindingIterator) bool {
|
||||
return switch (this.*) {
|
||||
.array => false,
|
||||
.objects => |*iter| iter.any_failed,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn to(this: *QueryBindingIterator, index: u32) void {
|
||||
switch (this.*) {
|
||||
.array => |*iter| iter.i = index,
|
||||
.objects => |*iter| {
|
||||
iter.cell_i = index % iter.columns_count;
|
||||
iter.row_i = index / iter.columns_count;
|
||||
iter.current_row = .zero;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(this: *QueryBindingIterator) void {
|
||||
switch (this.*) {
|
||||
.array => |*iter| {
|
||||
iter.i = 0;
|
||||
},
|
||||
.objects => |*iter| {
|
||||
iter.cell_i = 0;
|
||||
iter.row_i = 0;
|
||||
iter.current_row = .zero;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Signature = struct {
|
||||
fields: []const int4,
|
||||
name: []const u8,
|
||||
|
||||
209
src/sql/shared_sql.zig
Normal file
209
src/sql/shared_sql.zig
Normal file
@@ -0,0 +1,209 @@
|
||||
const JSC = bun.JSC;
|
||||
const bun = @import("root").bun;
|
||||
const JSValue = JSC.JSValue;
|
||||
const std = @import("std");
|
||||
|
||||
pub const QueryBindingIterator = union(enum) {
|
||||
array: JSC.JSArrayIterator,
|
||||
objects: ObjectIterator,
|
||||
|
||||
pub fn init(array: JSValue, columns: JSValue, globalObject: *JSC.JSGlobalObject) QueryBindingIterator {
|
||||
if (columns.isEmptyOrUndefinedOrNull()) {
|
||||
return .{ .array = JSC.JSArrayIterator.init(array, globalObject) };
|
||||
}
|
||||
|
||||
return .{
|
||||
.objects = .{
|
||||
.array = array,
|
||||
.columns = columns,
|
||||
.globalObject = globalObject,
|
||||
.columns_count = columns.getLength(globalObject),
|
||||
.array_length = array.getLength(globalObject),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub const ObjectIterator = struct {
|
||||
array: JSValue,
|
||||
columns: JSValue = .zero,
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
cell_i: usize = 0,
|
||||
row_i: usize = 0,
|
||||
current_row: JSC.JSValue = .zero,
|
||||
columns_count: usize = 0,
|
||||
array_length: usize = 0,
|
||||
any_failed: bool = false,
|
||||
|
||||
pub fn next(this: *ObjectIterator) ?JSC.JSValue {
|
||||
if (this.row_i >= this.array_length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cell_i = this.cell_i;
|
||||
this.cell_i += 1;
|
||||
const row_i = this.row_i;
|
||||
|
||||
const globalObject = this.globalObject;
|
||||
|
||||
if (this.current_row == .zero) {
|
||||
this.current_row = JSC.JSObject.getIndex(this.array, globalObject, @intCast(row_i));
|
||||
if (this.current_row.isEmptyOrUndefinedOrNull()) {
|
||||
if (!globalObject.hasException())
|
||||
globalObject.throw("Expected a row to be returned at index {d}", .{row_i});
|
||||
this.any_failed = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
defer {
|
||||
if (this.cell_i >= this.columns_count) {
|
||||
this.cell_i = 0;
|
||||
this.current_row = .zero;
|
||||
this.row_i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const property = JSC.JSObject.getIndex(this.columns, globalObject, @intCast(cell_i));
|
||||
if (property == .zero or property == .undefined) {
|
||||
if (!globalObject.hasException())
|
||||
globalObject.throw("Expected a column at index {d} in row {d}", .{ cell_i, row_i });
|
||||
this.any_failed = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = this.current_row.getOwnByValue(globalObject, property);
|
||||
if (value == .zero or value == .undefined) {
|
||||
if (!globalObject.hasException())
|
||||
globalObject.throw("Expected a value at index {d} in row {d}", .{ cell_i, row_i });
|
||||
this.any_failed = true;
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn next(this: *QueryBindingIterator) ?JSC.JSValue {
|
||||
return switch (this.*) {
|
||||
.array => |*iter| iter.next(),
|
||||
.objects => |*iter| iter.next(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn anyFailed(this: *const QueryBindingIterator) bool {
|
||||
return switch (this.*) {
|
||||
.array => false,
|
||||
.objects => |*iter| iter.any_failed,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn to(this: *QueryBindingIterator, index: u32) void {
|
||||
switch (this.*) {
|
||||
.array => |*iter| iter.i = index,
|
||||
.objects => |*iter| {
|
||||
iter.cell_i = index % iter.columns_count;
|
||||
iter.row_i = index / iter.columns_count;
|
||||
iter.current_row = .zero;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(this: *QueryBindingIterator) void {
|
||||
switch (this.*) {
|
||||
.array => |*iter| {
|
||||
iter.i = 0;
|
||||
},
|
||||
.objects => |*iter| {
|
||||
iter.cell_i = 0;
|
||||
iter.row_i = 0;
|
||||
iter.current_row = .zero;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Represents data that can be either owned or temporary
|
||||
pub const Data = union(enum) {
|
||||
owned: bun.ByteList,
|
||||
temporary: []const u8,
|
||||
inline_storage: std.BoundedArray(u8, 15),
|
||||
empty: void,
|
||||
|
||||
pub fn create(possibly_inline_bytes: []const u8, allocator: std.mem.Allocator) !Data {
|
||||
if (possibly_inline_bytes.len == 0) {
|
||||
return .{ .empty = {} };
|
||||
}
|
||||
|
||||
if (possibly_inline_bytes.len <= 15) {
|
||||
var inline_storage = std.BoundedArray(u8, 15){};
|
||||
@memcpy(inline_storage.buffer[0..possibly_inline_bytes.len], possibly_inline_bytes);
|
||||
inline_storage.len = @truncate(possibly_inline_bytes.len);
|
||||
return .{ .inline_storage = inline_storage };
|
||||
}
|
||||
return .{ .owned = bun.ByteList.init(try allocator.dupe(u8, possibly_inline_bytes)) };
|
||||
}
|
||||
|
||||
pub fn toOwned(this: @This()) !bun.ByteList {
|
||||
return switch (this) {
|
||||
.owned => this.owned,
|
||||
.temporary => bun.ByteList.init(try bun.default_allocator.dupe(u8, this.temporary)),
|
||||
.empty => bun.ByteList.init(&.{}),
|
||||
.inline_storage => bun.ByteList.init(try bun.default_allocator.dupe(u8, this.inline_storage.slice())),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(this: *@This()) void {
|
||||
switch (this.*) {
|
||||
.owned => this.owned.deinitWithAllocator(bun.default_allocator),
|
||||
.temporary => {},
|
||||
.empty => {},
|
||||
.inline_storage => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero bytes before deinit
|
||||
/// Generally, for security reasons.
|
||||
pub fn zdeinit(this: *@This()) void {
|
||||
switch (this.*) {
|
||||
.owned => {
|
||||
|
||||
// Zero bytes before deinit
|
||||
@memset(this.owned.slice(), 0);
|
||||
|
||||
this.owned.deinitWithAllocator(bun.default_allocator);
|
||||
},
|
||||
.temporary => {},
|
||||
.empty => {},
|
||||
.inline_storage => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice(this: *const @This()) []const u8 {
|
||||
return switch (this.*) {
|
||||
.owned => this.owned.slice(),
|
||||
.temporary => this.temporary,
|
||||
.empty => "",
|
||||
.inline_storage => this.inline_storage.slice(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn substring(this: *const @This(), start_index: usize, end_index: usize) Data {
|
||||
return switch (this.*) {
|
||||
.owned => .{ .temporary = this.owned.slice()[start_index..end_index] },
|
||||
.temporary => .{ .temporary = this.temporary[start_index..end_index] },
|
||||
.empty => .{ .empty = {} },
|
||||
.inline_storage => .{ .temporary = this.inline_storage.slice()[start_index..end_index] },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sliceZ(this: *const @This()) [:0]const u8 {
|
||||
return switch (this.*) {
|
||||
.owned => this.owned.slice()[0..this.owned.len :0],
|
||||
.temporary => this.temporary[0..this.temporary.len :0],
|
||||
.empty => "",
|
||||
.inline_storage => this.inline_storage.slice()[0..this.inline_storage.len :0],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const postgres = @import("./postgres.zig");
|
||||
pub const mysql = @import("./mysql.zig");
|
||||
Reference in New Issue
Block a user