Compare commits
16
Commits
0420d50f15
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4097a5e5d | ||
|
|
e8f630ae98 | ||
|
|
b8e9ab07f2 | ||
|
|
44ec3d4c11 | ||
|
|
90fcd3867d | ||
|
|
4d85a69505 | ||
|
|
e9f0730405 | ||
|
|
c97bedc271 | ||
|
|
4f203ce4e7 | ||
|
|
000d40b4cb | ||
|
|
c40d8596c7 | ||
|
|
e51f524443 | ||
|
|
7ded5e949d | ||
|
|
42805c4a69 | ||
|
|
9357cd404a | ||
|
|
e1038ec8ea |
@@ -34,3 +34,5 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
/test
|
||||
.vscode
|
||||
.dump
|
||||
|
||||
/.bun-mariadb
|
||||
@@ -86,7 +86,7 @@ bun add github:moduletrace/bun-mariadb
|
||||
Connection settings are read from the environment (not the config file):
|
||||
|
||||
| Variable | Required | Description |
|
||||
| --------------------------------- | -------- | ------------------------------------ |
|
||||
| --------------------------------- | -------- | ---------------------------------- |
|
||||
| `BUN_MARIADB_SERVER_HOST` | Yes | MariaDB host |
|
||||
| `BUN_MARIADB_SERVER_USERNAME` | Yes | Database user |
|
||||
| `BUN_MARIADB_SERVER_PASSWORD` | Yes | Database password |
|
||||
@@ -135,9 +135,22 @@ const schema: BUN_MARIADB_DatabaseSchemaType = {
|
||||
{
|
||||
tableName: "users",
|
||||
fields: [
|
||||
{ fieldName: "first_name", dataType: "VARCHAR", integerLength: 255 },
|
||||
{ fieldName: "last_name", dataType: "VARCHAR", integerLength: 255 },
|
||||
{ fieldName: "email", dataType: "VARCHAR", integerLength: 255, unique: true },
|
||||
{
|
||||
fieldName: "first_name",
|
||||
dataType: "VARCHAR",
|
||||
dataLength: 255,
|
||||
},
|
||||
{
|
||||
fieldName: "last_name",
|
||||
dataType: "VARCHAR",
|
||||
dataLength: 255,
|
||||
},
|
||||
{
|
||||
fieldName: "email",
|
||||
dataType: "VARCHAR",
|
||||
dataLength: 255,
|
||||
unique: true,
|
||||
},
|
||||
{ fieldName: "bio", dataType: "LONGTEXT", html: true },
|
||||
],
|
||||
},
|
||||
@@ -188,7 +201,7 @@ await BunMariaDB.delete({ table: "users", targetId: 1 });
|
||||
The config file must be named `bun-mariadb.config.ts` and placed at the project root.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ------------------- | -------- | -------- | --------------------------------------------------------------------------- |
|
||||
| -------------------- | -------- | -------- | ----------------------------------------------------------------------------- |
|
||||
| `db_name` | `string` | Yes | MariaDB database name |
|
||||
| `db_dir` | `string` | Yes | Directory for schema, types, and local artifacts (relative to project root) |
|
||||
| `db_backup_dir` | `string` | No | Backup directory name, relative to `db_dir` (default: `.backups`) |
|
||||
@@ -228,7 +241,6 @@ interface BUN_MARIADB_TableSchemaType {
|
||||
parentTableName?: string; // inherit / merge fields from another table
|
||||
tableNameOld?: string; // rename: old name triggers ALTER TABLE RENAME
|
||||
collation?: "utf8mb4_bin" | "utf8mb4_unicode_520_ci";
|
||||
isVector?: boolean; // mark as vector-oriented table
|
||||
}
|
||||
```
|
||||
|
||||
@@ -238,12 +250,38 @@ interface BUN_MARIADB_TableSchemaType {
|
||||
type BUN_MARIADB_FieldSchemaType = {
|
||||
fieldName?: string;
|
||||
dataType:
|
||||
| "CHAR" | "VARCHAR" | "TEXT" | "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT"
|
||||
| "TINYINT" | "SMALLINT" | "MEDIUMINT" | "INT" | "BIGINT"
|
||||
| "FLOAT" | "DOUBLE" | "DECIMAL"
|
||||
| "BINARY" | "VARBINARY" | "BLOB" | "TINYBLOB" | "MEDIUMBLOB" | "LONGBLOB"
|
||||
| "DATE" | "TIME" | "DATETIME" | "TIMESTAMP" | "YEAR"
|
||||
| "BOOLEAN" | "UUID" | "JSON" | "INET6" | "ENUM" | "SET" | "VECTOR";
|
||||
| "CHAR"
|
||||
| "VARCHAR"
|
||||
| "TEXT"
|
||||
| "TINYTEXT"
|
||||
| "MEDIUMTEXT"
|
||||
| "LONGTEXT"
|
||||
| "TINYINT"
|
||||
| "SMALLINT"
|
||||
| "MEDIUMINT"
|
||||
| "INT"
|
||||
| "BIGINT"
|
||||
| "FLOAT"
|
||||
| "DOUBLE"
|
||||
| "DECIMAL"
|
||||
| "BINARY"
|
||||
| "VARBINARY"
|
||||
| "BLOB"
|
||||
| "TINYBLOB"
|
||||
| "MEDIUMBLOB"
|
||||
| "LONGBLOB"
|
||||
| "DATE"
|
||||
| "TIME"
|
||||
| "DATETIME"
|
||||
| "TIMESTAMP"
|
||||
| "YEAR"
|
||||
| "BOOLEAN"
|
||||
| "UUID"
|
||||
| "JSON"
|
||||
| "INET6"
|
||||
| "ENUM"
|
||||
| "SET"
|
||||
| "VECTOR";
|
||||
primaryKey?: boolean;
|
||||
autoIncrement?: boolean;
|
||||
notNullValue?: boolean;
|
||||
@@ -253,7 +291,7 @@ type BUN_MARIADB_FieldSchemaType = {
|
||||
onUpdate?: string;
|
||||
onUpdateLiteral?: string;
|
||||
foreignKey?: BUN_MARIADB_ForeignKeyType;
|
||||
integerLength?: string | number; // e.g. VARCHAR length
|
||||
dataLength?: string | number; // e.g. VARCHAR length
|
||||
decimals?: string | number; // DECIMAL scale
|
||||
options?: (string | number)[]; // ENUM / SET values
|
||||
isVector?: boolean; // native VECTOR column
|
||||
@@ -369,7 +407,7 @@ bunx bun-mariadb export [options]
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | --------------------------------------------------------------------------- |
|
||||
| ---------------- | -------------------------------------------------------------------------- |
|
||||
| `-o`, `--output` | Output archive path (`.tar.gz` or `.zip`). Defaults to `{db_dir}/.exports` |
|
||||
| `-f`, `--format` | When `--output` is omitted: `tar.gz` (default) or `zip` |
|
||||
|
||||
@@ -401,7 +439,7 @@ bunx bun-mariadb import [file] [options]
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | --------------------------------------------------------------------------- |
|
||||
| --------------- | --------------------------------------------------------------------- |
|
||||
| `[file]` | Path to a `.sql` dump or export archive (`.tar.gz` / `.tar` / `.zip`) |
|
||||
| `--sql-only` | Archive only: restore SQL, do not overwrite `schema.ts` |
|
||||
| `--schema-only` | Archive only: write `schema.ts`, do not restore SQL |
|
||||
@@ -642,7 +680,7 @@ type ServerQueryParam<T> = {
|
||||
### Equality Operators
|
||||
|
||||
| Equality | SQL Equivalent |
|
||||
| ----------------------- | ------------------------------------------------------ |
|
||||
| ----------------------- | ----------------------------- |
|
||||
| `EQUAL` (default) | `=` |
|
||||
| `NOT EQUAL` | `!=` |
|
||||
| `LIKE` | `LIKE '%value%'` |
|
||||
@@ -704,7 +742,6 @@ MariaDB native `VECTOR(n)` columns and `VECTOR INDEX` are supported (MariaDB 11.
|
||||
```ts
|
||||
{
|
||||
tableName: "documents",
|
||||
isVector: true,
|
||||
fields: [
|
||||
{
|
||||
fieldName: "embedding",
|
||||
@@ -715,7 +752,7 @@ MariaDB native `VECTOR(n)` columns and `VECTOR INDEX` are supported (MariaDB 11.
|
||||
{
|
||||
fieldName: "title",
|
||||
dataType: "VARCHAR",
|
||||
integerLength: 255,
|
||||
dataLength: 255,
|
||||
},
|
||||
],
|
||||
indexes: [
|
||||
@@ -826,7 +863,7 @@ const res = await BunMariaDB.select<BUN_MARIADB_MY_APP_USERS>({
|
||||
Every table automatically receives:
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------ | ------------------------------- | -------------------------------------- |
|
||||
| ------------ | -------------------------------------------- | -------------------------------------- |
|
||||
| `id` | `BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL` | Unique row identifier |
|
||||
| `created_at` | `BIGINT` | Unix timestamp set on insert |
|
||||
| `updated_at` | `BIGINT` | Unix timestamp updated on every update |
|
||||
@@ -865,7 +902,6 @@ bun-mariadb/
|
||||
│ │ ├── create-db-schema.ts
|
||||
│ │ ├── create-table.ts
|
||||
│ │ ├── update-table.ts
|
||||
│ │ ├── recreate-table.ts
|
||||
│ │ ├── sync-indexes.ts
|
||||
│ │ └── ...
|
||||
│ ├── types/
|
||||
|
||||
Vendored
+2
-1
@@ -17,4 +17,5 @@ declare const BunMariaDB: {
|
||||
};
|
||||
};
|
||||
export default BunMariaDB;
|
||||
export type { BunMariaDBConfig, BUN_MARIADB_DatabaseSchemaType, BUN_MARIADB_TableSchemaType, BUN_MARIADB_FieldSchemaType, BUN_MARIADB_IndexSchemaType, BUN_MARIADB_UniqueConstraintSchemaType, BUN_MARIADB_ForeignKeyType, DBResponseObject, ServerQueryParam, } from "./types";
|
||||
export type * from "./types";
|
||||
export { UsersOmitedFields, MariaDBCollations, MariaDBCharsets, TextFieldTypesArray, BUN_MARIADB_DATATYPES, MariaDBIndexTypes, ServerQueryOperators, ServerQueryEqualities, SQlComparisons, DataCrudRequestMethods, DataCrudRequestMethodsLowerCase, DsqlCrudActions, QueryFields, DockerComposeServices, IndexTypes, DefaultFields, RequiredENVs, } from "./types";
|
||||
|
||||
Vendored
+1
@@ -19,3 +19,4 @@ const BunMariaDB = {
|
||||
},
|
||||
};
|
||||
export default BunMariaDB;
|
||||
export { UsersOmitedFields, MariaDBCollations, MariaDBCharsets, TextFieldTypesArray, BUN_MARIADB_DATATYPES, MariaDBIndexTypes, ServerQueryOperators, ServerQueryEqualities, SQlComparisons, DataCrudRequestMethods, DataCrudRequestMethodsLowerCase, DsqlCrudActions, QueryFields, DockerComposeServices, IndexTypes, DefaultFields, RequiredENVs, } from "./types";
|
||||
|
||||
Vendored
+1
@@ -49,6 +49,7 @@ export default async function dbHandler({ query, values, config }) {
|
||||
single_res: res_array?.[0],
|
||||
insert_return,
|
||||
count,
|
||||
db_res: res,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
|
||||
Vendored
+3
-2
@@ -12,7 +12,7 @@ export default async function DbDelete({ table, query, targetId, config, }) {
|
||||
finalQuery = _.merge(finalQuery, {
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
value: Number(targetId),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -41,8 +41,9 @@ export default async function DbDelete({ table, query, targetId, config, }) {
|
||||
});
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database delete failed",
|
||||
...res,
|
||||
success: false,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
|
||||
Vendored
+2
-1
@@ -38,8 +38,9 @@ export default async function DbInsert({ table, data, update_on_duplicate, confi
|
||||
});
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database insert failed",
|
||||
...res,
|
||||
success: false,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
|
||||
Vendored
+3
-2
@@ -12,7 +12,7 @@ export default async function DbSelect({ table, query, count, targetId, config,
|
||||
finalQuery = _.merge(finalQuery, {
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
value: Number(targetId),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -28,8 +28,9 @@ export default async function DbSelect({ table, query, count, targetId, config,
|
||||
});
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database select failed",
|
||||
...res,
|
||||
success: false,
|
||||
debug: {
|
||||
sqlObj,
|
||||
sql: sqlObj.string,
|
||||
|
||||
Vendored
+6
-2
@@ -9,8 +9,9 @@ export default async function DbSQL({ sql, values }) {
|
||||
});
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database query failed",
|
||||
...res,
|
||||
success: false,
|
||||
debug: {
|
||||
sqlObj: {
|
||||
sql: trimmedSql,
|
||||
@@ -24,7 +25,10 @@ export default async function DbSQL({ sql, values }) {
|
||||
const single_res = isSelect ? payload?.[0] : res.single_res;
|
||||
const singleRaw = res.single_res;
|
||||
return {
|
||||
success: true,
|
||||
...res,
|
||||
success: isSelect
|
||||
? Boolean(single_res) || Boolean(payload?.[0])
|
||||
: true,
|
||||
payload,
|
||||
single_res,
|
||||
debug: {
|
||||
|
||||
Vendored
+7
-22
@@ -13,7 +13,7 @@ export default async function DbUpdate({ table, data, query, targetId, config, }
|
||||
finalQuery = _.merge(finalQuery, {
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
value: Number(targetId),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -60,41 +60,26 @@ export default async function DbUpdate({ table, data, query, targetId, config, }
|
||||
values: values,
|
||||
config,
|
||||
});
|
||||
if (res.error) {
|
||||
return res;
|
||||
}
|
||||
sqlObj.string = sql;
|
||||
sqlObj.values = values;
|
||||
let updated_sql = ``;
|
||||
let updated_sql_values = [];
|
||||
updated_sql += `SELECT * FROM ${quoteIdentifier(table)} ${whereClause}`;
|
||||
updated_sql_values = [...updated_sql_values, ...sqlQueryObj.values];
|
||||
updated_sql += ` AND `;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (!key)
|
||||
continue;
|
||||
if (key == "updated_at")
|
||||
continue;
|
||||
const isLast = i == keys.length - 1;
|
||||
updated_sql += ` ${quoteIdentifier(key)}=?`;
|
||||
updated_sql_values.push(finalData[key] ?? null);
|
||||
if (!isLast) {
|
||||
updated_sql += ` AND `;
|
||||
}
|
||||
}
|
||||
updated_sql_values = [...sqlQueryObj.values];
|
||||
const updated_res = await dbHandler({
|
||||
query: updated_sql,
|
||||
values: updated_sql_values,
|
||||
config,
|
||||
});
|
||||
const affected_rows = updated_res.payload?.length;
|
||||
return {
|
||||
...res,
|
||||
success: Boolean(affected_rows),
|
||||
insert_return: {
|
||||
affected_rows,
|
||||
},
|
||||
...updated_res,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
db_res: res.db_res,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
|
||||
Vendored
+1
-1
@@ -15,7 +15,7 @@ export default async function createTable({ table, config, }) {
|
||||
if (field.primaryKey && field.fieldName) {
|
||||
primaryKeys.push(field.fieldName);
|
||||
}
|
||||
if (field.foreignKey && !table.isVector) {
|
||||
if (field.foreignKey) {
|
||||
foreignKeys.push(buildForeignKeyConstraint(field, table.tableName));
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import type { BunMariaDBConfig } from "../../types";
|
||||
export type ColumnInfoRow = {
|
||||
name: string;
|
||||
type: string;
|
||||
comment?: string;
|
||||
isNullable: boolean;
|
||||
columnDefault: string | null;
|
||||
extra: string;
|
||||
};
|
||||
export default function getTableColumnsGemini({ tableName, config, }: {
|
||||
tableName: string;
|
||||
config?: BunMariaDBConfig;
|
||||
}): Promise<ColumnInfoRow[]>;
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { querySchemaRows } from "./run-schema-query";
|
||||
import schemaCondition from "./schema-condition";
|
||||
export default async function getTableColumnsGemini({ tableName, config, }) {
|
||||
const schemaCond = schemaCondition(config);
|
||||
const rows = await querySchemaRows({
|
||||
query: `
|
||||
SELECT
|
||||
c.COLUMN_NAME,
|
||||
c.COLUMN_TYPE,
|
||||
c.COLUMN_COMMENT,
|
||||
c.IS_NULLABLE,
|
||||
c.COLUMN_DEFAULT,
|
||||
c.EXTRA,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.CHECK_CONSTRAINTS cc
|
||||
WHERE cc.CONSTRAINT_SCHEMA = c.TABLE_SCHEMA
|
||||
AND cc.TABLE_NAME = c.TABLE_NAME
|
||||
AND cc.CHECK_CLAUSE LIKE CONCAT('%json_valid(\`', c.COLUMN_NAME, '\`)%')
|
||||
) AS IS_JSON
|
||||
FROM information_schema.COLUMNS c
|
||||
WHERE ${schemaCond.where.replace(/\bTABLE_SCHEMA\b/g, "c.TABLE_SCHEMA")}
|
||||
AND c.TABLE_NAME = ?
|
||||
ORDER BY c.ORDINAL_POSITION
|
||||
`,
|
||||
values: [...schemaCond.values, tableName],
|
||||
config,
|
||||
});
|
||||
return rows.map((row) => {
|
||||
const isJson = Number(row.IS_JSON) === 1;
|
||||
// Normalize longtext with a json_valid constraint to "json"
|
||||
let resolvedType = row.COLUMN_TYPE;
|
||||
if (isJson && row.COLUMN_TYPE.toLowerCase() === "longtext") {
|
||||
resolvedType = "json";
|
||||
}
|
||||
return {
|
||||
name: row.COLUMN_NAME,
|
||||
type: resolvedType,
|
||||
comment: row.COLUMN_COMMENT,
|
||||
isNullable: String(row.IS_NULLABLE).toUpperCase() === "YES",
|
||||
columnDefault: row.COLUMN_DEFAULT,
|
||||
extra: row.EXTRA || "",
|
||||
};
|
||||
});
|
||||
}
|
||||
Vendored
+16
-17
@@ -7,9 +7,9 @@ export default function mapDataType(field) {
|
||||
}
|
||||
switch (dataType) {
|
||||
case "CHAR":
|
||||
return `CHAR(${field.integerLength || 255})`;
|
||||
return `CHAR(${field.dataLength || 255})`;
|
||||
case "VARCHAR":
|
||||
return `VARCHAR(${field.integerLength || 255})`;
|
||||
return `VARCHAR(${field.dataLength || 255})`;
|
||||
case "TEXT":
|
||||
return "TEXT";
|
||||
case "TINYTEXT":
|
||||
@@ -19,36 +19,34 @@ export default function mapDataType(field) {
|
||||
case "LONGTEXT":
|
||||
return "LONGTEXT";
|
||||
case "TINYINT":
|
||||
return field.integerLength
|
||||
? `TINYINT(${field.integerLength})`
|
||||
return field.dataLength
|
||||
? `TINYINT(${field.dataLength})`
|
||||
: "TINYINT";
|
||||
case "SMALLINT":
|
||||
return field.integerLength
|
||||
? `SMALLINT(${field.integerLength})`
|
||||
return field.dataLength
|
||||
? `SMALLINT(${field.dataLength})`
|
||||
: "SMALLINT";
|
||||
case "MEDIUMINT":
|
||||
return field.integerLength
|
||||
? `MEDIUMINT(${field.integerLength})`
|
||||
return field.dataLength
|
||||
? `MEDIUMINT(${field.dataLength})`
|
||||
: "MEDIUMINT";
|
||||
case "INT":
|
||||
return field.integerLength ? `INT(${field.integerLength})` : "INT";
|
||||
return field.dataLength ? `INT(${field.dataLength})` : "INT";
|
||||
case "BIGINT":
|
||||
return field.integerLength
|
||||
? `BIGINT(${field.integerLength})`
|
||||
: "BIGINT";
|
||||
return field.dataLength ? `BIGINT(${field.dataLength})` : "BIGINT";
|
||||
case "FLOAT":
|
||||
return "FLOAT";
|
||||
case "DOUBLE":
|
||||
return "DOUBLE";
|
||||
case "DECIMAL":
|
||||
if (field.integerLength && field.decimals) {
|
||||
return `DECIMAL(${field.integerLength}, ${field.decimals})`;
|
||||
if (field.dataLength && field.decimals) {
|
||||
return `DECIMAL(${field.dataLength}, ${field.decimals})`;
|
||||
}
|
||||
return "DECIMAL(10,2)";
|
||||
case "BINARY":
|
||||
return `BINARY(${field.integerLength || 1})`;
|
||||
return `BINARY(${field.dataLength || 1})`;
|
||||
case "VARBINARY":
|
||||
return `VARBINARY(${field.integerLength || 255})`;
|
||||
return `VARBINARY(${field.dataLength || 255})`;
|
||||
case "BLOB":
|
||||
return "BLOB";
|
||||
case "TINYBLOB":
|
||||
@@ -68,11 +66,12 @@ export default function mapDataType(field) {
|
||||
case "YEAR":
|
||||
return "YEAR";
|
||||
case "UUID":
|
||||
return "CHAR(36)"; // MariaDB does not have a native UUID type
|
||||
return "UUID";
|
||||
case "JSON":
|
||||
return "JSON";
|
||||
case "INET6":
|
||||
return "INET6";
|
||||
case "BOOL":
|
||||
case "BOOLEAN":
|
||||
return "TINYINT(1)";
|
||||
case "ENUM": {
|
||||
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
import type { BUN_MARIADB_TableSchemaType, BunMariaDBConfig } from "../../types";
|
||||
/**
|
||||
* Full table rebuild. For `isVector` tables this drops and recreates in place
|
||||
* (preserving rows when possible). For regular tables it uses a temp-table swap.
|
||||
*/
|
||||
export default function recreateTable({ table, config, }: {
|
||||
table: BUN_MARIADB_TableSchemaType;
|
||||
config?: BunMariaDBConfig;
|
||||
}): Promise<void>;
|
||||
Vendored
-111
@@ -1,111 +0,0 @@
|
||||
import createTable from "./create-table";
|
||||
import getTableColumns from "./get-table-columns";
|
||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||
import schemaCondition from "./schema-condition";
|
||||
async function checkIfTableExists({ tableName, config, }) {
|
||||
const schemaCond = schemaCondition(config);
|
||||
const rows = await querySchemaRows({
|
||||
query: `SELECT 1 AS \`table_exists\` FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_NAME = ? LIMIT 1`,
|
||||
values: [...schemaCond.values, tableName],
|
||||
config,
|
||||
});
|
||||
return Boolean(rows[0]?.table_exists);
|
||||
}
|
||||
/**
|
||||
* Full table rebuild. For `isVector` tables this drops and recreates in place
|
||||
* (preserving rows when possible). For regular tables it uses a temp-table swap.
|
||||
*/
|
||||
export default async function recreateTable({ table, config, }) {
|
||||
const doesTableExist = await checkIfTableExists({
|
||||
tableName: table.tableName,
|
||||
config,
|
||||
});
|
||||
if (!doesTableExist) {
|
||||
await createTable({ table, config });
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Vector tables: drop + recreate + reinsert (MariaDB VECTOR INDEX / dim
|
||||
* changes are not reliably alterable in place).
|
||||
*/
|
||||
if (table.isVector) {
|
||||
console.log(`Recreating vector table: ${table.tableName}`);
|
||||
const existingRows = await querySchemaRows({
|
||||
query: `SELECT * FROM ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config });
|
||||
try {
|
||||
await runSchemaQuery({
|
||||
query: `DROP TABLE IF EXISTS ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
await createTable({ table, config });
|
||||
}
|
||||
finally {
|
||||
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config });
|
||||
}
|
||||
if (existingRows.length > 0) {
|
||||
const schemaFieldNames = new Set((table.fields || [])
|
||||
.map((f) => f.fieldName)
|
||||
.filter((n) => Boolean(n)));
|
||||
for (const row of existingRows) {
|
||||
const columns = Object.keys(row).filter((c) => schemaFieldNames.has(c));
|
||||
if (columns.length === 0)
|
||||
continue;
|
||||
const placeholders = columns.map(() => "?").join(", ");
|
||||
const columnList = columns
|
||||
.map((c) => MariaDBQuoteGen(c))
|
||||
.join(", ");
|
||||
await runSchemaQuery({
|
||||
query: `INSERT INTO ${MariaDBQuoteGen(table.tableName)} (${columnList}) VALUES (${placeholders})`,
|
||||
values: columns.map((c) => row[c] ?? null),
|
||||
config,
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
const tempTableName = `${table.tableName}_temp_${Date.now()}`;
|
||||
const backupOldTableName = `${table.tableName}_old_${Date.now()}`;
|
||||
const existingColumns = await getTableColumns({
|
||||
tableName: table.tableName,
|
||||
config,
|
||||
});
|
||||
const columnsToKeep = (table.fields || [])
|
||||
.filter((field) => existingColumns.some((column) => column.name === field.fieldName))
|
||||
.map((field) => field.fieldName)
|
||||
.filter((fieldName) => Boolean(fieldName));
|
||||
await createTable({
|
||||
table: { ...table, tableName: tempTableName },
|
||||
config,
|
||||
});
|
||||
if (columnsToKeep.length > 0) {
|
||||
const columnList = columnsToKeep
|
||||
.map((column) => MariaDBQuoteGen(column))
|
||||
.join(", ");
|
||||
await runSchemaQuery({
|
||||
query: `INSERT INTO ${MariaDBQuoteGen(tempTableName)} (${columnList}) SELECT ${columnList} FROM ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
}
|
||||
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config });
|
||||
try {
|
||||
await runSchemaQuery({
|
||||
query: `RENAME TABLE ${MariaDBQuoteGen(table.tableName)} TO ${MariaDBQuoteGen(backupOldTableName)}`,
|
||||
config,
|
||||
});
|
||||
await runSchemaQuery({
|
||||
query: `RENAME TABLE ${MariaDBQuoteGen(tempTableName)} TO ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
await runSchemaQuery({
|
||||
query: `DROP TABLE ${MariaDBQuoteGen(backupOldTableName)}`,
|
||||
config,
|
||||
});
|
||||
}
|
||||
finally {
|
||||
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config });
|
||||
}
|
||||
}
|
||||
Vendored
-1
@@ -23,7 +23,6 @@ export default function resolveTable(table, db_schema) {
|
||||
tableName: table.tableName,
|
||||
tableDescription: table.tableDescription || parentTable.tableDescription,
|
||||
collation: table.collation || parentTable.collation,
|
||||
isVector: table.isVector !== undefined ? table.isVector : parentTable.isVector,
|
||||
fields: Array.from(mergedFieldsMap.values()),
|
||||
indexes: _.uniqBy([...(parentTable.indexes || []), ...(table.indexes || [])], "indexName"),
|
||||
uniqueConstraints: [
|
||||
|
||||
Vendored
-3
@@ -15,9 +15,6 @@ function rulesMatch(live, desired) {
|
||||
ruleIsCascade(live.updateRule) === desired.cascadeUpdate);
|
||||
}
|
||||
export function grabDesiredForeignKeys(table) {
|
||||
if (table.isVector) {
|
||||
return [];
|
||||
}
|
||||
const desired = [];
|
||||
for (const field of table.fields || []) {
|
||||
const fk = field.foreignKey;
|
||||
|
||||
Vendored
-2
@@ -6,8 +6,6 @@ import { grabDesiredUniqueConstraints } from "./sync-unique-constraints";
|
||||
function isVectorIndexDef(index, table) {
|
||||
if (index.indexType === "VECTOR")
|
||||
return true;
|
||||
if (table.isVector)
|
||||
return true;
|
||||
const firstFieldName = index.indexTableFields?.[0];
|
||||
if (!firstFieldName)
|
||||
return false;
|
||||
|
||||
Vendored
+62
-21
@@ -1,10 +1,10 @@
|
||||
import buildColumnDefinition, { fieldRequiresNotNull, } from "./build-column-definition";
|
||||
import createTable from "./create-table";
|
||||
import getTableColumns, {} from "./get-table-columns";
|
||||
import getTableColumnsGemini from "./get-table-columns-gemnini";
|
||||
import isVectorField from "./is-vector-field";
|
||||
import mapDataType from "./map-data-types";
|
||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||
import recreateTable from "./recreate-table";
|
||||
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||
import schemaCondition from "./schema-condition";
|
||||
import { dropForeignKeysOnColumns } from "./sync-foreign-keys";
|
||||
@@ -111,7 +111,9 @@ function columnAttributesDiverged(live, field) {
|
||||
if (wantsOnUpdate && hasOnUpdate) {
|
||||
const liveOnUpdate = normalizeDefault((live.extra.match(/on update\s+(.+)/i) || [])[1] || "");
|
||||
const expectedOu = normalizeDefault(wantsOnUpdate);
|
||||
if (liveOnUpdate && expectedOu && !defaultsMatch(liveOnUpdate, expectedOu)) {
|
||||
if (liveOnUpdate &&
|
||||
expectedOu &&
|
||||
!defaultsMatch(liveOnUpdate, expectedOu)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -141,8 +143,52 @@ async function dropColumn({ tableName, fieldName, config, }) {
|
||||
config,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Drop + re-add a column (values discarded). Used when VECTOR dimensions change —
|
||||
* MODIFY cannot resize VECTOR, and a full table rebuild is unnecessary.
|
||||
*/
|
||||
async function recreateColumn({ tableName, field, config, }) {
|
||||
if (!field.fieldName)
|
||||
return;
|
||||
console.log(`Recreating column: ${tableName}.${field.fieldName} (values will be cleared)`);
|
||||
await dropForeignKeysOnColumns({
|
||||
tableName,
|
||||
columns: [field.fieldName],
|
||||
config,
|
||||
});
|
||||
const schemaCond = schemaCondition(config);
|
||||
const indexRows = await querySchemaRows({
|
||||
query: `SELECT INDEX_NAME, COLUMN_NAME FROM information_schema.STATISTICS WHERE ${schemaCond.where} AND TABLE_NAME = ? AND INDEX_NAME <> 'PRIMARY'`,
|
||||
values: [...schemaCond.values, tableName],
|
||||
config,
|
||||
});
|
||||
const indexesToDrop = new Set();
|
||||
for (const row of indexRows) {
|
||||
if (row.COLUMN_NAME === field.fieldName) {
|
||||
indexesToDrop.add(row.INDEX_NAME);
|
||||
}
|
||||
}
|
||||
for (const indexName of indexesToDrop) {
|
||||
console.log(`Dropping index ${indexName} because column ${field.fieldName} is being recreated`);
|
||||
try {
|
||||
await runSchemaQuery({
|
||||
query: `DROP INDEX ${MariaDBQuoteGen(indexName)} ON ${MariaDBQuoteGen(tableName)}`,
|
||||
config,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
if (String(err?.message || "").includes("needed in a foreign key constraint")) {
|
||||
console.warn(`Skipping drop of index ${indexName}: required by a foreign key constraint`);
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
await dropColumn({ tableName, fieldName: field.fieldName, config });
|
||||
await addColumn({ tableName, field, config });
|
||||
}
|
||||
export default async function updateTable({ table, config, }) {
|
||||
const existingColumns = await getTableColumns({
|
||||
const existingColumns = await getTableColumnsGemini({
|
||||
tableName: table.tableName,
|
||||
config,
|
||||
});
|
||||
@@ -154,8 +200,8 @@ export default async function updateTable({ table, config, }) {
|
||||
const codeFieldsMap = new Map((table.fields || []).map((f) => [f.fieldName, f]));
|
||||
const fieldsToAdd = [];
|
||||
const fieldsToModify = [];
|
||||
const fieldsToRecreate = [];
|
||||
const fieldsToDrop = [];
|
||||
let needsVectorRecreate = false;
|
||||
for (const field of table.fields || []) {
|
||||
if (!field.fieldName)
|
||||
continue;
|
||||
@@ -164,11 +210,14 @@ export default async function updateTable({ table, config, }) {
|
||||
fieldsToAdd.push(field);
|
||||
}
|
||||
else {
|
||||
let typeDiverged = !columnTypesMatch(liveField.type, mapDataType(field));
|
||||
let mapped_data_type = mapDataType(field);
|
||||
let typeDiverged = !columnTypesMatch(liveField.type, mapped_data_type);
|
||||
if (isVectorField(field)) {
|
||||
typeDiverged = vectorTypeDiverged(liveField.type, liveField.comment || "", field);
|
||||
if (typeDiverged) {
|
||||
needsVectorRecreate = true;
|
||||
// VECTOR dimensions / storage cannot be MODIFYed — drop + re-add column
|
||||
fieldsToRecreate.push(field);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const attrsDiverged = !typeDiverged && columnAttributesDiverged(liveField, field);
|
||||
@@ -177,12 +226,6 @@ export default async function updateTable({ table, config, }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Vector dimension / storage type changes → full rebuild automatically
|
||||
if (needsVectorRecreate) {
|
||||
console.log(`Vector column change detected on \`${table.tableName}\`; recreating table`);
|
||||
await recreateTable({ table, config });
|
||||
return;
|
||||
}
|
||||
for (const col of existingColumns) {
|
||||
if (!codeFieldsMap.has(col.name)) {
|
||||
fieldsToDrop.push(col.name);
|
||||
@@ -190,6 +233,7 @@ export default async function updateTable({ table, config, }) {
|
||||
}
|
||||
if (fieldsToAdd.length === 0 &&
|
||||
fieldsToModify.length === 0 &&
|
||||
fieldsToRecreate.length === 0 &&
|
||||
fieldsToDrop.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -257,17 +301,14 @@ export default async function updateTable({ table, config, }) {
|
||||
await addColumn({ tableName: table.tableName, field, config });
|
||||
}
|
||||
for (const field of fieldsToModify) {
|
||||
try {
|
||||
await modifyColumn({ tableName: table.tableName, field, config });
|
||||
}
|
||||
catch (err) {
|
||||
if (isVectorField(field)) {
|
||||
console.warn(`[Vector Resize] Re-aligning dimension spaces natively via safe migration schema rebuild.`);
|
||||
await recreateTable({ table, config });
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
for (const field of fieldsToRecreate) {
|
||||
await recreateColumn({
|
||||
tableName: table.tableName,
|
||||
field,
|
||||
config,
|
||||
});
|
||||
}
|
||||
for (const fieldName of fieldsToDrop) {
|
||||
await dropColumn({ tableName: table.tableName, fieldName, config });
|
||||
|
||||
Vendored
+6
-6
@@ -74,10 +74,6 @@ export interface BUN_MARIADB_TableSchemaType {
|
||||
*/
|
||||
childTableDbId?: string | number;
|
||||
collation?: (typeof MariaDBCollations)[number];
|
||||
/**
|
||||
* If this is a vector-oriented table (native MariaDB VECTOR columns/indexes)
|
||||
*/
|
||||
isVector?: boolean;
|
||||
}
|
||||
/**
|
||||
* Reference object used to link a table to one of its child tables.
|
||||
@@ -213,7 +209,10 @@ export type BUN_MARIADB_FieldSchemaType = {
|
||||
onDelete?: string;
|
||||
onDeleteLiteral?: string;
|
||||
cssFiles?: string[];
|
||||
integerLength?: string | number;
|
||||
/**
|
||||
* Datatype length. Eg 255 for VARCHAR
|
||||
*/
|
||||
dataLength?: string | number;
|
||||
decimals?: string | number;
|
||||
code?: boolean;
|
||||
options?: (string | number)[];
|
||||
@@ -830,7 +829,7 @@ export type ServerQueryParamsJoin<Table extends string = string, Field extends o
|
||||
joinType: "INNER JOIN" | "JOIN" | "LEFT JOIN" | "RIGHT JOIN";
|
||||
alias?: string;
|
||||
tableName: Table;
|
||||
match?: ServerQueryParamsJoinMatchObject<Field> | ServerQueryParamsJoinMatchObject<Field>[];
|
||||
match?: ServerQueryParamsJoinMatchObject<Field> | (ServerQueryParamsJoinMatchObject<Field> | undefined)[];
|
||||
selectFields?: (keyof Field | SelectFieldObject<Field>)[];
|
||||
omitFields?: (keyof Field | {
|
||||
field: keyof Field;
|
||||
@@ -1411,6 +1410,7 @@ export type DBResponseObject<T extends {
|
||||
msg?: string;
|
||||
debug?: any;
|
||||
count?: number;
|
||||
db_res?: any;
|
||||
};
|
||||
export type DBInsertReturn = {
|
||||
count?: number;
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
import { isUndefined } from "lodash";
|
||||
import _, { isUndefined } from "lodash";
|
||||
import sqlGenGrabConcatStr from "./sql-generator-grab-concat-str";
|
||||
import sqlGenGenJoinStr from "./sql-generator-gen-join-str";
|
||||
import sqlGenGrabSelectFieldSQL from "./sql-generator-grab-select-field-sql";
|
||||
@@ -162,6 +162,7 @@ export default function sqlGenGenQueryStr(params) {
|
||||
if (Array.isArray(join.match)) {
|
||||
return ("(" +
|
||||
join.match
|
||||
.filter((mtch) => !_.isUndefined(mtch))
|
||||
.map((mtch) => {
|
||||
const { str, values } = sqlGenGenJoinStr({
|
||||
mtch,
|
||||
|
||||
Vendored
+1
-1
@@ -31,7 +31,7 @@ export default function sqlInsertGenerator({ tableName, data, dbFullName, }) {
|
||||
: value
|
||||
? value
|
||||
: null;
|
||||
if (!finalValue) {
|
||||
if (!finalValue && typeof value !== "number") {
|
||||
queryValues.push(null);
|
||||
return "?";
|
||||
}
|
||||
|
||||
+6
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@moduletrace/bun-mariadb",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.19",
|
||||
"description": "Schema-driven MariaDB manager for Bun",
|
||||
"author": "Benjamin Toby",
|
||||
"license": "MIT",
|
||||
@@ -12,6 +12,11 @@
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"import": "./dist/types/index.js",
|
||||
"default": "./dist/types/index.js"
|
||||
}
|
||||
},
|
||||
"bin": {
|
||||
|
||||
+19
-10
@@ -23,14 +23,23 @@ const BunMariaDB = {
|
||||
|
||||
export default BunMariaDB;
|
||||
|
||||
export type {
|
||||
BunMariaDBConfig,
|
||||
BUN_MARIADB_DatabaseSchemaType,
|
||||
BUN_MARIADB_TableSchemaType,
|
||||
BUN_MARIADB_FieldSchemaType,
|
||||
BUN_MARIADB_IndexSchemaType,
|
||||
BUN_MARIADB_UniqueConstraintSchemaType,
|
||||
BUN_MARIADB_ForeignKeyType,
|
||||
DBResponseObject,
|
||||
ServerQueryParam,
|
||||
export type * from "./types";
|
||||
export {
|
||||
UsersOmitedFields,
|
||||
MariaDBCollations,
|
||||
MariaDBCharsets,
|
||||
TextFieldTypesArray,
|
||||
BUN_MARIADB_DATATYPES,
|
||||
MariaDBIndexTypes,
|
||||
ServerQueryOperators,
|
||||
ServerQueryEqualities,
|
||||
SQlComparisons,
|
||||
DataCrudRequestMethods,
|
||||
DataCrudRequestMethodsLowerCase,
|
||||
DsqlCrudActions,
|
||||
QueryFields,
|
||||
DockerComposeServices,
|
||||
IndexTypes,
|
||||
DefaultFields,
|
||||
RequiredENVs,
|
||||
} from "./types";
|
||||
|
||||
@@ -75,6 +75,7 @@ export default async function dbHandler<
|
||||
single_res: res_array?.[0],
|
||||
insert_return,
|
||||
count,
|
||||
db_res: res,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
|
||||
@@ -41,7 +41,7 @@ export default async function DbDelete<
|
||||
{
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
value: Number(targetId),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -77,8 +77,9 @@ export default async function DbDelete<
|
||||
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database delete failed",
|
||||
...res,
|
||||
success: false,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
|
||||
@@ -71,8 +71,9 @@ export default async function DbInsert<
|
||||
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database insert failed",
|
||||
...res,
|
||||
success: false,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
|
||||
@@ -43,7 +43,7 @@ export default async function DbSelect<
|
||||
{
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
value: Number(targetId),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -63,8 +63,9 @@ export default async function DbSelect<
|
||||
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database select failed",
|
||||
...res,
|
||||
success: false,
|
||||
debug: {
|
||||
sqlObj,
|
||||
sql: sqlObj.string,
|
||||
|
||||
@@ -20,8 +20,9 @@ export default async function DbSQL<
|
||||
|
||||
if (!res.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "Database query failed",
|
||||
...res,
|
||||
success: false,
|
||||
debug: {
|
||||
sqlObj: {
|
||||
sql: trimmedSql,
|
||||
@@ -37,7 +38,10 @@ export default async function DbSQL<
|
||||
const singleRaw = res.single_res as any;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
...res,
|
||||
success: isSelect
|
||||
? Boolean(single_res) || Boolean(payload?.[0])
|
||||
: true,
|
||||
payload,
|
||||
single_res,
|
||||
debug: {
|
||||
|
||||
@@ -45,7 +45,7 @@ export default async function DbUpdate<
|
||||
{
|
||||
query: {
|
||||
id: {
|
||||
value: String(targetId),
|
||||
value: Number(targetId),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -106,6 +106,10 @@ export default async function DbUpdate<
|
||||
config,
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
return res;
|
||||
}
|
||||
|
||||
sqlObj.string = sql;
|
||||
sqlObj.values = values as any[];
|
||||
|
||||
@@ -113,22 +117,7 @@ export default async function DbUpdate<
|
||||
let updated_sql_values: any[] = [];
|
||||
|
||||
updated_sql += `SELECT * FROM ${quoteIdentifier(table)} ${whereClause}`;
|
||||
updated_sql_values = [...updated_sql_values, ...sqlQueryObj.values];
|
||||
updated_sql += ` AND `;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (!key) continue;
|
||||
if (key == "updated_at") continue;
|
||||
|
||||
const isLast = i == keys.length - 1;
|
||||
|
||||
updated_sql += ` ${quoteIdentifier(key)}=?`;
|
||||
updated_sql_values.push(finalData[key] ?? null);
|
||||
|
||||
if (!isLast) {
|
||||
updated_sql += ` AND `;
|
||||
}
|
||||
}
|
||||
updated_sql_values = [...sqlQueryObj.values];
|
||||
|
||||
const updated_res = await dbHandler({
|
||||
query: updated_sql,
|
||||
@@ -136,17 +125,12 @@ export default async function DbUpdate<
|
||||
config,
|
||||
});
|
||||
|
||||
const affected_rows = updated_res.payload?.length;
|
||||
|
||||
return {
|
||||
...res,
|
||||
success: Boolean(affected_rows),
|
||||
insert_return: {
|
||||
affected_rows,
|
||||
},
|
||||
...updated_res,
|
||||
debug: {
|
||||
sqlObj,
|
||||
},
|
||||
db_res: res.db_res,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
|
||||
@@ -30,7 +30,7 @@ export default async function createTable({
|
||||
primaryKeys.push(field.fieldName);
|
||||
}
|
||||
|
||||
if (field.foreignKey && !table.isVector) {
|
||||
if (field.foreignKey) {
|
||||
foreignKeys.push(
|
||||
buildForeignKeyConstraint(field, table.tableName),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { BunMariaDBConfig } from "../../types";
|
||||
import { querySchemaRows } from "./run-schema-query";
|
||||
import schemaCondition from "./schema-condition";
|
||||
|
||||
export type ColumnInfoRow = {
|
||||
name: string;
|
||||
type: string;
|
||||
comment?: string;
|
||||
isNullable: boolean;
|
||||
columnDefault: string | null;
|
||||
extra: string;
|
||||
};
|
||||
|
||||
export default async function getTableColumnsGemini({
|
||||
tableName,
|
||||
config,
|
||||
}: {
|
||||
tableName: string;
|
||||
config?: BunMariaDBConfig;
|
||||
}): Promise<ColumnInfoRow[]> {
|
||||
const schemaCond = schemaCondition(config);
|
||||
const rows = await querySchemaRows<{
|
||||
COLUMN_NAME: string;
|
||||
COLUMN_TYPE: string;
|
||||
COLUMN_COMMENT: string;
|
||||
IS_NULLABLE: string;
|
||||
COLUMN_DEFAULT: string | null;
|
||||
EXTRA: string;
|
||||
IS_JSON: number | boolean;
|
||||
}>({
|
||||
query: `
|
||||
SELECT
|
||||
c.COLUMN_NAME,
|
||||
c.COLUMN_TYPE,
|
||||
c.COLUMN_COMMENT,
|
||||
c.IS_NULLABLE,
|
||||
c.COLUMN_DEFAULT,
|
||||
c.EXTRA,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.CHECK_CONSTRAINTS cc
|
||||
WHERE cc.CONSTRAINT_SCHEMA = c.TABLE_SCHEMA
|
||||
AND cc.TABLE_NAME = c.TABLE_NAME
|
||||
AND cc.CHECK_CLAUSE LIKE CONCAT('%json_valid(\`', c.COLUMN_NAME, '\`)%')
|
||||
) AS IS_JSON
|
||||
FROM information_schema.COLUMNS c
|
||||
WHERE ${schemaCond.where.replace(/\bTABLE_SCHEMA\b/g, "c.TABLE_SCHEMA")}
|
||||
AND c.TABLE_NAME = ?
|
||||
ORDER BY c.ORDINAL_POSITION
|
||||
`,
|
||||
values: [...schemaCond.values, tableName],
|
||||
config,
|
||||
});
|
||||
|
||||
return rows.map((row) => {
|
||||
const isJson = Number(row.IS_JSON) === 1;
|
||||
|
||||
// Normalize longtext with a json_valid constraint to "json"
|
||||
let resolvedType = row.COLUMN_TYPE;
|
||||
if (isJson && row.COLUMN_TYPE.toLowerCase() === "longtext") {
|
||||
resolvedType = "json";
|
||||
}
|
||||
|
||||
return {
|
||||
name: row.COLUMN_NAME,
|
||||
type: resolvedType,
|
||||
comment: row.COLUMN_COMMENT,
|
||||
isNullable: String(row.IS_NULLABLE).toUpperCase() === "YES",
|
||||
columnDefault: row.COLUMN_DEFAULT,
|
||||
extra: row.EXTRA || "",
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -13,9 +13,9 @@ export default function mapDataType(
|
||||
|
||||
switch (dataType) {
|
||||
case "CHAR":
|
||||
return `CHAR(${field.integerLength || 255})`;
|
||||
return `CHAR(${field.dataLength || 255})`;
|
||||
case "VARCHAR":
|
||||
return `VARCHAR(${field.integerLength || 255})`;
|
||||
return `VARCHAR(${field.dataLength || 255})`;
|
||||
case "TEXT":
|
||||
return "TEXT";
|
||||
case "TINYTEXT":
|
||||
@@ -25,36 +25,34 @@ export default function mapDataType(
|
||||
case "LONGTEXT":
|
||||
return "LONGTEXT";
|
||||
case "TINYINT":
|
||||
return field.integerLength
|
||||
? `TINYINT(${field.integerLength})`
|
||||
return field.dataLength
|
||||
? `TINYINT(${field.dataLength})`
|
||||
: "TINYINT";
|
||||
case "SMALLINT":
|
||||
return field.integerLength
|
||||
? `SMALLINT(${field.integerLength})`
|
||||
return field.dataLength
|
||||
? `SMALLINT(${field.dataLength})`
|
||||
: "SMALLINT";
|
||||
case "MEDIUMINT":
|
||||
return field.integerLength
|
||||
? `MEDIUMINT(${field.integerLength})`
|
||||
return field.dataLength
|
||||
? `MEDIUMINT(${field.dataLength})`
|
||||
: "MEDIUMINT";
|
||||
case "INT":
|
||||
return field.integerLength ? `INT(${field.integerLength})` : "INT";
|
||||
return field.dataLength ? `INT(${field.dataLength})` : "INT";
|
||||
case "BIGINT":
|
||||
return field.integerLength
|
||||
? `BIGINT(${field.integerLength})`
|
||||
: "BIGINT";
|
||||
return field.dataLength ? `BIGINT(${field.dataLength})` : "BIGINT";
|
||||
case "FLOAT":
|
||||
return "FLOAT";
|
||||
case "DOUBLE":
|
||||
return "DOUBLE";
|
||||
case "DECIMAL":
|
||||
if (field.integerLength && field.decimals) {
|
||||
return `DECIMAL(${field.integerLength}, ${field.decimals})`;
|
||||
if (field.dataLength && field.decimals) {
|
||||
return `DECIMAL(${field.dataLength}, ${field.decimals})`;
|
||||
}
|
||||
return "DECIMAL(10,2)";
|
||||
case "BINARY":
|
||||
return `BINARY(${field.integerLength || 1})`;
|
||||
return `BINARY(${field.dataLength || 1})`;
|
||||
case "VARBINARY":
|
||||
return `VARBINARY(${field.integerLength || 255})`;
|
||||
return `VARBINARY(${field.dataLength || 255})`;
|
||||
case "BLOB":
|
||||
return "BLOB";
|
||||
case "TINYBLOB":
|
||||
@@ -74,11 +72,12 @@ export default function mapDataType(
|
||||
case "YEAR":
|
||||
return "YEAR";
|
||||
case "UUID":
|
||||
return "CHAR(36)"; // MariaDB does not have a native UUID type
|
||||
return "UUID";
|
||||
case "JSON":
|
||||
return "JSON";
|
||||
case "INET6":
|
||||
return "INET6";
|
||||
case "BOOL":
|
||||
case "BOOLEAN":
|
||||
return "TINYINT(1)";
|
||||
case "ENUM": {
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import type {
|
||||
BUN_MARIADB_TableSchemaType,
|
||||
BunMariaDBConfig,
|
||||
} from "../../types";
|
||||
import createTable from "./create-table";
|
||||
import getTableColumns from "./get-table-columns";
|
||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||
import schemaCondition from "./schema-condition";
|
||||
|
||||
async function checkIfTableExists({
|
||||
tableName,
|
||||
config,
|
||||
}: {
|
||||
tableName: string;
|
||||
config?: BunMariaDBConfig;
|
||||
}): Promise<boolean> {
|
||||
const schemaCond = schemaCondition(config);
|
||||
const rows = await querySchemaRows<{ table_exists: number }>({
|
||||
query: `SELECT 1 AS \`table_exists\` FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_NAME = ? LIMIT 1`,
|
||||
values: [...schemaCond.values, tableName],
|
||||
config,
|
||||
});
|
||||
|
||||
return Boolean(rows[0]?.table_exists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full table rebuild. For `isVector` tables this drops and recreates in place
|
||||
* (preserving rows when possible). For regular tables it uses a temp-table swap.
|
||||
*/
|
||||
export default async function recreateTable({
|
||||
table,
|
||||
config,
|
||||
}: {
|
||||
table: BUN_MARIADB_TableSchemaType;
|
||||
config?: BunMariaDBConfig;
|
||||
}): Promise<void> {
|
||||
const doesTableExist = await checkIfTableExists({
|
||||
tableName: table.tableName,
|
||||
config,
|
||||
});
|
||||
|
||||
if (!doesTableExist) {
|
||||
await createTable({ table, config });
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vector tables: drop + recreate + reinsert (MariaDB VECTOR INDEX / dim
|
||||
* changes are not reliably alterable in place).
|
||||
*/
|
||||
if (table.isVector) {
|
||||
console.log(`Recreating vector table: ${table.tableName}`);
|
||||
|
||||
const existingRows = await querySchemaRows<Record<string, any>>({
|
||||
query: `SELECT * FROM ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
|
||||
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config });
|
||||
try {
|
||||
await runSchemaQuery({
|
||||
query: `DROP TABLE IF EXISTS ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
await createTable({ table, config });
|
||||
} finally {
|
||||
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config });
|
||||
}
|
||||
|
||||
if (existingRows.length > 0) {
|
||||
const schemaFieldNames = new Set(
|
||||
(table.fields || [])
|
||||
.map((f) => f.fieldName)
|
||||
.filter((n): n is string => Boolean(n)),
|
||||
);
|
||||
|
||||
for (const row of existingRows) {
|
||||
const columns = Object.keys(row).filter((c) =>
|
||||
schemaFieldNames.has(c),
|
||||
);
|
||||
if (columns.length === 0) continue;
|
||||
|
||||
const placeholders = columns.map(() => "?").join(", ");
|
||||
const columnList = columns
|
||||
.map((c) => MariaDBQuoteGen(c))
|
||||
.join(", ");
|
||||
|
||||
await runSchemaQuery({
|
||||
query: `INSERT INTO ${MariaDBQuoteGen(table.tableName)} (${columnList}) VALUES (${placeholders})`,
|
||||
values: columns.map((c) => row[c] ?? null),
|
||||
config,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const tempTableName = `${table.tableName}_temp_${Date.now()}`;
|
||||
const backupOldTableName = `${table.tableName}_old_${Date.now()}`;
|
||||
const existingColumns = await getTableColumns({
|
||||
tableName: table.tableName,
|
||||
config,
|
||||
});
|
||||
|
||||
const columnsToKeep = (table.fields || [])
|
||||
.filter((field) =>
|
||||
existingColumns.some((column) => column.name === field.fieldName),
|
||||
)
|
||||
.map((field) => field.fieldName)
|
||||
.filter((fieldName): fieldName is string => Boolean(fieldName));
|
||||
|
||||
await createTable({
|
||||
table: { ...table, tableName: tempTableName },
|
||||
config,
|
||||
});
|
||||
|
||||
if (columnsToKeep.length > 0) {
|
||||
const columnList = columnsToKeep
|
||||
.map((column) => MariaDBQuoteGen(column))
|
||||
.join(", ");
|
||||
|
||||
await runSchemaQuery({
|
||||
query: `INSERT INTO ${MariaDBQuoteGen(tempTableName)} (${columnList}) SELECT ${columnList} FROM ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
}
|
||||
|
||||
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config });
|
||||
try {
|
||||
await runSchemaQuery({
|
||||
query: `RENAME TABLE ${MariaDBQuoteGen(table.tableName)} TO ${MariaDBQuoteGen(backupOldTableName)}`,
|
||||
config,
|
||||
});
|
||||
await runSchemaQuery({
|
||||
query: `RENAME TABLE ${MariaDBQuoteGen(tempTableName)} TO ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
await runSchemaQuery({
|
||||
query: `DROP TABLE ${MariaDBQuoteGen(backupOldTableName)}`,
|
||||
config,
|
||||
});
|
||||
} finally {
|
||||
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config });
|
||||
}
|
||||
}
|
||||
@@ -41,8 +41,6 @@ export default function resolveTable(
|
||||
tableName: table.tableName,
|
||||
tableDescription: table.tableDescription || parentTable.tableDescription,
|
||||
collation: table.collation || parentTable.collation,
|
||||
isVector:
|
||||
table.isVector !== undefined ? table.isVector : parentTable.isVector,
|
||||
fields: Array.from(mergedFieldsMap.values()),
|
||||
indexes: _.uniqBy(
|
||||
[...(parentTable.indexes || []), ...(table.indexes || [])],
|
||||
|
||||
@@ -57,10 +57,6 @@ function rulesMatch(
|
||||
export function grabDesiredForeignKeys(
|
||||
table: BUN_MARIADB_TableSchemaType,
|
||||
): DesiredForeignKey[] {
|
||||
if (table.isVector) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const desired: DesiredForeignKey[] = [];
|
||||
|
||||
for (const field of table.fields || []) {
|
||||
|
||||
@@ -14,7 +14,6 @@ function isVectorIndexDef(
|
||||
table: BUN_MARIADB_TableSchemaType,
|
||||
): boolean {
|
||||
if (index.indexType === "VECTOR") return true;
|
||||
if (table.isVector) return true;
|
||||
|
||||
const firstFieldName = index.indexTableFields?.[0];
|
||||
if (!firstFieldName) return false;
|
||||
@@ -97,8 +96,7 @@ async function createIndex({
|
||||
.map((field) => MariaDBQuoteGen(field))
|
||||
.join(", ");
|
||||
const typeUpper = index.indexType?.toUpperCase();
|
||||
const isSpecialType =
|
||||
typeUpper === "FULLTEXT" || typeUpper === "SPATIAL";
|
||||
const isSpecialType = typeUpper === "FULLTEXT" || typeUpper === "SPATIAL";
|
||||
const indexPrefix = isSpecialType ? `${typeUpper} ` : "";
|
||||
const indexSuffix =
|
||||
!isSpecialType && (typeUpper === "BTREE" || typeUpper === "HASH")
|
||||
@@ -199,11 +197,7 @@ export default async function syncIndexes({
|
||||
const columnsMatch =
|
||||
details.columns.length === schemaColumns.length &&
|
||||
details.columns.every((col, idx) => col === schemaColumns[idx]);
|
||||
const typeMatch = indexTypesMatch(
|
||||
details.type,
|
||||
schemaIndex,
|
||||
table,
|
||||
);
|
||||
const typeMatch = indexTypesMatch(details.type, schemaIndex, table);
|
||||
|
||||
if (!columnsMatch || !typeMatch) {
|
||||
console.log(`Recreating changed index: ${indexName}`);
|
||||
|
||||
@@ -8,10 +8,10 @@ import buildColumnDefinition, {
|
||||
} from "./build-column-definition";
|
||||
import createTable from "./create-table";
|
||||
import getTableColumns, { type ColumnInfoRow } from "./get-table-columns";
|
||||
import getTableColumnsGemini from "./get-table-columns-gemnini";
|
||||
import isVectorField from "./is-vector-field";
|
||||
import mapDataType from "./map-data-types";
|
||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||
import recreateTable from "./recreate-table";
|
||||
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||
import schemaCondition from "./schema-condition";
|
||||
import { dropForeignKeysOnColumns } from "./sync-foreign-keys";
|
||||
@@ -138,7 +138,11 @@ function columnAttributesDiverged(
|
||||
(live.extra.match(/on update\s+(.+)/i) || [])[1] || "",
|
||||
);
|
||||
const expectedOu = normalizeDefault(wantsOnUpdate);
|
||||
if (liveOnUpdate && expectedOu && !defaultsMatch(liveOnUpdate, expectedOu)) {
|
||||
if (
|
||||
liveOnUpdate &&
|
||||
expectedOu &&
|
||||
!defaultsMatch(liveOnUpdate, expectedOu)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -197,6 +201,76 @@ async function dropColumn({
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop + re-add a column (values discarded). Used when VECTOR dimensions change —
|
||||
* MODIFY cannot resize VECTOR, and a full table rebuild is unnecessary.
|
||||
*/
|
||||
async function recreateColumn({
|
||||
tableName,
|
||||
field,
|
||||
config,
|
||||
}: {
|
||||
tableName: string;
|
||||
field: BUN_MARIADB_FieldSchemaType;
|
||||
config?: BunMariaDBConfig;
|
||||
}): Promise<void> {
|
||||
if (!field.fieldName) return;
|
||||
|
||||
console.log(
|
||||
`Recreating column: ${tableName}.${field.fieldName} (values will be cleared)`,
|
||||
);
|
||||
|
||||
await dropForeignKeysOnColumns({
|
||||
tableName,
|
||||
columns: [field.fieldName],
|
||||
config,
|
||||
});
|
||||
|
||||
const schemaCond = schemaCondition(config);
|
||||
const indexRows = await querySchemaRows<{
|
||||
INDEX_NAME: string;
|
||||
COLUMN_NAME: string;
|
||||
}>({
|
||||
query: `SELECT INDEX_NAME, COLUMN_NAME FROM information_schema.STATISTICS WHERE ${schemaCond.where} AND TABLE_NAME = ? AND INDEX_NAME <> 'PRIMARY'`,
|
||||
values: [...schemaCond.values, tableName],
|
||||
config,
|
||||
});
|
||||
|
||||
const indexesToDrop = new Set<string>();
|
||||
for (const row of indexRows) {
|
||||
if (row.COLUMN_NAME === field.fieldName) {
|
||||
indexesToDrop.add(row.INDEX_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
for (const indexName of indexesToDrop) {
|
||||
console.log(
|
||||
`Dropping index ${indexName} because column ${field.fieldName} is being recreated`,
|
||||
);
|
||||
try {
|
||||
await runSchemaQuery({
|
||||
query: `DROP INDEX ${MariaDBQuoteGen(indexName)} ON ${MariaDBQuoteGen(tableName)}`,
|
||||
config,
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (
|
||||
String(err?.message || "").includes(
|
||||
"needed in a foreign key constraint",
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
`Skipping drop of index ${indexName}: required by a foreign key constraint`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
await dropColumn({ tableName, fieldName: field.fieldName, config });
|
||||
await addColumn({ tableName, field, config });
|
||||
}
|
||||
|
||||
export default async function updateTable({
|
||||
table,
|
||||
config,
|
||||
@@ -204,7 +278,7 @@ export default async function updateTable({
|
||||
table: BUN_MARIADB_TableSchemaType;
|
||||
config?: BunMariaDBConfig;
|
||||
}): Promise<void> {
|
||||
const existingColumns = await getTableColumns({
|
||||
const existingColumns = await getTableColumnsGemini({
|
||||
tableName: table.tableName,
|
||||
config,
|
||||
});
|
||||
@@ -223,8 +297,8 @@ export default async function updateTable({
|
||||
|
||||
const fieldsToAdd: BUN_MARIADB_FieldSchemaType[] = [];
|
||||
const fieldsToModify: BUN_MARIADB_FieldSchemaType[] = [];
|
||||
const fieldsToRecreate: BUN_MARIADB_FieldSchemaType[] = [];
|
||||
const fieldsToDrop: string[] = [];
|
||||
let needsVectorRecreate = false;
|
||||
|
||||
for (const field of table.fields || []) {
|
||||
if (!field.fieldName) continue;
|
||||
@@ -234,9 +308,11 @@ export default async function updateTable({
|
||||
if (!liveField) {
|
||||
fieldsToAdd.push(field);
|
||||
} else {
|
||||
let mapped_data_type = mapDataType(field);
|
||||
|
||||
let typeDiverged = !columnTypesMatch(
|
||||
liveField.type,
|
||||
mapDataType(field),
|
||||
mapped_data_type,
|
||||
);
|
||||
|
||||
if (isVectorField(field)) {
|
||||
@@ -246,7 +322,9 @@ export default async function updateTable({
|
||||
field,
|
||||
);
|
||||
if (typeDiverged) {
|
||||
needsVectorRecreate = true;
|
||||
// VECTOR dimensions / storage cannot be MODIFYed — drop + re-add column
|
||||
fieldsToRecreate.push(field);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,15 +337,6 @@ export default async function updateTable({
|
||||
}
|
||||
}
|
||||
|
||||
// Vector dimension / storage type changes → full rebuild automatically
|
||||
if (needsVectorRecreate) {
|
||||
console.log(
|
||||
`Vector column change detected on \`${table.tableName}\`; recreating table`,
|
||||
);
|
||||
await recreateTable({ table, config });
|
||||
return;
|
||||
}
|
||||
|
||||
for (const col of existingColumns) {
|
||||
if (!codeFieldsMap.has(col.name)) {
|
||||
fieldsToDrop.push(col.name);
|
||||
@@ -277,6 +346,7 @@ export default async function updateTable({
|
||||
if (
|
||||
fieldsToAdd.length === 0 &&
|
||||
fieldsToModify.length === 0 &&
|
||||
fieldsToRecreate.length === 0 &&
|
||||
fieldsToDrop.length === 0
|
||||
) {
|
||||
return;
|
||||
@@ -370,18 +440,15 @@ export default async function updateTable({
|
||||
}
|
||||
|
||||
for (const field of fieldsToModify) {
|
||||
try {
|
||||
await modifyColumn({ tableName: table.tableName, field, config });
|
||||
} catch (err: any) {
|
||||
if (isVectorField(field)) {
|
||||
console.warn(
|
||||
`[Vector Resize] Re-aligning dimension spaces natively via safe migration schema rebuild.`,
|
||||
);
|
||||
await recreateTable({ table, config });
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
for (const field of fieldsToRecreate) {
|
||||
await recreateColumn({
|
||||
tableName: table.tableName,
|
||||
field,
|
||||
config,
|
||||
});
|
||||
}
|
||||
|
||||
for (const fieldName of fieldsToDrop) {
|
||||
|
||||
+6
-6
@@ -96,10 +96,6 @@ export interface BUN_MARIADB_TableSchemaType {
|
||||
*/
|
||||
childTableDbId?: string | number;
|
||||
collation?: (typeof MariaDBCollations)[number];
|
||||
/**
|
||||
* If this is a vector-oriented table (native MariaDB VECTOR columns/indexes)
|
||||
*/
|
||||
isVector?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +198,10 @@ export type BUN_MARIADB_FieldSchemaType = {
|
||||
onDelete?: string;
|
||||
onDeleteLiteral?: string;
|
||||
cssFiles?: string[];
|
||||
integerLength?: string | number;
|
||||
/**
|
||||
* Datatype length. Eg 255 for VARCHAR
|
||||
*/
|
||||
dataLength?: string | number;
|
||||
decimals?: string | number;
|
||||
code?: boolean;
|
||||
options?: (string | number)[];
|
||||
@@ -916,7 +915,7 @@ export type ServerQueryParamsJoin<
|
||||
tableName: Table;
|
||||
match?:
|
||||
| ServerQueryParamsJoinMatchObject<Field>
|
||||
| ServerQueryParamsJoinMatchObject<Field>[];
|
||||
| (ServerQueryParamsJoinMatchObject<Field> | undefined)[];
|
||||
selectFields?: (keyof Field | SelectFieldObject<Field>)[];
|
||||
omitFields?: (
|
||||
| keyof Field
|
||||
@@ -1619,6 +1618,7 @@ export type DBResponseObject<
|
||||
msg?: string;
|
||||
debug?: any;
|
||||
count?: number;
|
||||
db_res?: any;
|
||||
};
|
||||
|
||||
export type DBInsertReturn = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isUndefined } from "lodash";
|
||||
import _, { isUndefined } from "lodash";
|
||||
import type { ServerQueryParam, TableSelectFieldsObject } from "../types";
|
||||
import sqlGenGrabConcatStr from "./sql-generator-grab-concat-str";
|
||||
import sqlGenGenJoinStr from "./sql-generator-gen-join-str";
|
||||
@@ -195,6 +195,7 @@ export default function sqlGenGenQueryStr<
|
||||
return (
|
||||
"(" +
|
||||
join.match
|
||||
.filter((mtch) => !_.isUndefined(mtch))
|
||||
.map((mtch) => {
|
||||
const { str, values } =
|
||||
sqlGenGenJoinStr({
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function sqlInsertGenerator({
|
||||
? value
|
||||
: null;
|
||||
|
||||
if (!finalValue) {
|
||||
if (!finalValue && typeof value !== "number") {
|
||||
queryValues.push(null);
|
||||
return "?";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user