From eb86e1803d24be19c022c4798306c7d7a59a4a59 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Sun, 21 Jun 2026 07:05:49 +0100 Subject: [PATCH] First version without typescript errors --- src/commands/schema.ts | 4 - src/data/app-data.ts | 1 + src/functions/init.ts | 57 ++++++++--- src/lib/db-handler.ts | 17 +--- src/lib/grab-db-ssl.ts | 2 +- src/lib/grab-dsql-connection-config.ts | 17 ++-- src/lib/grab-duplicate-safe-insert-sql.ts | 62 ++++++------ src/lib/mariadb/db-delete.ts | 59 +++++++---- src/lib/mariadb/db-insert.ts | 34 +++++-- src/lib/mariadb/db-schema-manager.ts | 46 +++++---- src/lib/mariadb/db-select.ts | 72 ++++++++++---- src/lib/mariadb/db-sql.ts | 43 +++++--- src/lib/mariadb/db-update.ts | 113 +++++++++++++--------- src/lib/mariadb/schema.ts | 7 -- src/types/index.ts | 43 ++++---- src/utils/sql-insert-generator.ts | 12 ++- 16 files changed, 364 insertions(+), 225 deletions(-) delete mode 100644 src/lib/mariadb/schema.ts diff --git a/src/commands/schema.ts b/src/commands/schema.ts index 59825d3..e543a59 100644 --- a/src/commands/schema.ts +++ b/src/commands/schema.ts @@ -25,9 +25,6 @@ export default function () { const dbSchema = global.DB_SCHEMA; const { ROOT_DIR, BUN_MARIADB_TEMP_DB_FILE_PATH } = grabDirNames(); - const { db_file_path } = grabDBDir({ config }); - - cpSync(db_file_path, BUN_MARIADB_TEMP_DB_FILE_PATH); try { const isVector = Boolean(opts.vector || opts.v); @@ -66,7 +63,6 @@ export default function () { process.exit(); } catch (error) { console.log(error); - cpSync(BUN_MARIADB_TEMP_DB_FILE_PATH, db_file_path); process.exit(1); } }); diff --git a/src/data/app-data.ts b/src/data/app-data.ts index 1a8462b..64f7838 100644 --- a/src/data/app-data.ts +++ b/src/data/app-data.ts @@ -3,4 +3,5 @@ export const AppData = { MaxBackups: 10, DefaultBackupDirName: ".backups", DbSchemaManagerTableName: "__db_schema_manager__", + DbSchemaFileName: "schema.ts", } as const; diff --git a/src/functions/init.ts b/src/functions/init.ts index 7678d80..272ca30 100644 --- a/src/functions/init.ts +++ b/src/functions/init.ts @@ -2,10 +2,11 @@ import path from "path"; import fs from "fs"; import { AppData } from "../data/app-data"; import grabDirNames from "../data/grab-dir-names"; -import type { - BunMariaDBConfig, - BunMariaDBConfigReturn, - BUN_MARIADB_DatabaseSchemaType, +import { + type BunMariaDBConfig, + type BunMariaDBConfigReturn, + type BUN_MARIADB_DatabaseSchemaType, + RequiredENVs, } from "../types"; export default async function init(): Promise { @@ -25,27 +26,53 @@ export default async function init(): Promise { const ConfigImport = await import(ConfigFilePath); const Config = ConfigImport["default"] as BunMariaDBConfig; + if (!Config) { + console.error( + `No default export from \`${ConfigFilePath}\`. Please export a default module.`, + ); + process.exit(1); + } + if (!Config.db_name) { console.error(`\`db_name\` is required in your config`); process.exit(1); } - if (!Config.db_schema_file_name) { - console.error(`\`db_schema_file_name\` is required in your config`); + RequiredENVs.forEach((env_key) => { + if ( + env_key == "BUN_MARIADB_SERVER_PORT" || + env_key == "BUN_MARIADB_SERVER_SSL_KEY_PATH" + ) { + return; + } + + if (!process.env[env_key]) { + console.error(`\`${env_key}\` env variable is required.`); + process.exit(1); + } + }); + + if (!Config.db_dir) { + console.error( + `\`db_dir\` is required in your config. This directory holds all database related configuration. Also note that a \`${AppData["DbSchemaFileName"]}\` file is also required in this directory to define your database schema`, + ); process.exit(1); } - let db_dir = ROOT_DIR; - - if (Config.db_dir) { - db_dir = path.resolve(ROOT_DIR, Config.db_dir); - - if (!fs.existsSync(Config.db_dir)) { - fs.mkdirSync(Config.db_dir, { recursive: true }); - } + const db_dir = path.resolve(ROOT_DIR, Config.db_dir); + if (!fs.existsSync(Config.db_dir)) { + fs.mkdirSync(Config.db_dir, { recursive: true }); + } + + const DBSchemaFilePath = path.join(db_dir, AppData["DbSchemaFileName"]); + + if (!fs.existsSync(DBSchemaFilePath)) { + console.error( + `Please create a schema file at \`${DBSchemaFilePath}\`. Don't forget to export a default module from this file.`, + ); + process.exit(1); } - const DBSchemaFilePath = path.join(db_dir, Config.db_schema_file_name); const DbSchemaImport = await import(DBSchemaFilePath); const DbSchema = DbSchemaImport[ "default" diff --git a/src/lib/db-handler.ts b/src/lib/db-handler.ts index 56076d3..8c9ac74 100644 --- a/src/lib/db-handler.ts +++ b/src/lib/db-handler.ts @@ -5,9 +5,6 @@ import grabDBConnection from "./grab-db-connection"; type Param = { query: string; values?: string[] | object; - noErrorLogs?: boolean; - database?: string; - tableSchema?: BUN_MARIADB_TableSchemaType; config?: ConnectionConfig; }; @@ -17,18 +14,12 @@ type Param = { */ export default async function dbHandler< T extends { [k: string]: any } = { [k: string]: any }, ->({ - query, - values, - noErrorLogs, - database, - config, -}: Param): Promise { +>({ query, values, config }: Param): Promise { let CONNECTION: Connection | undefined; let results: T | null = null; try { - CONNECTION = await grabDBConnection({ database, config }); + CONNECTION = await grabDBConnection({ config }); if (query && values) { const queryResults = await CONNECTION.query(query, values); @@ -46,10 +37,6 @@ export default async function dbHandler< throw new Error("Authentication Failed!"); } - if (!noErrorLogs) { - console.log(error); - } - results = null; } finally { await CONNECTION?.end(); diff --git a/src/lib/grab-db-ssl.ts b/src/lib/grab-db-ssl.ts index 59a0528..7a2c21b 100644 --- a/src/lib/grab-db-ssl.ts +++ b/src/lib/grab-db-ssl.ts @@ -8,7 +8,7 @@ type Return = ConnectionConfig["ssl"] | undefined; * # Grab SSL */ export default function grabDbSSL(): Return { - const caProivdedPath = process.env.DSQL_SSL_CA_CERT; + const caProivdedPath = process.env.BUN_MARIADB_SERVER_SSL_KEY_PATH; if (!caProivdedPath?.match(/./)) { return undefined; diff --git a/src/lib/grab-dsql-connection-config.ts b/src/lib/grab-dsql-connection-config.ts index da5c4a9..70389b9 100644 --- a/src/lib/grab-dsql-connection-config.ts +++ b/src/lib/grab-dsql-connection-config.ts @@ -8,17 +8,18 @@ import grabDbSSL from "./grab-db-ssl"; export default function grabDSQLConnectionConfig( param?: DsqlConnectionParam, ): ConnectionConfig { - const CONN_TIMEOUT = 10000; + const configData = global.CONFIG; + const CONN_TIMEOUT = configData?.connection_timeout || 10000; const config: ConnectionConfig = { - host: process.env.DSQL_DB_HOST, - user: process.env.DSQL_DB_USERNAME, - password: process.env.DSQL_DB_PASSWORD, - // database: param?.dbFullName, - port: process.env.DSQL_DB_PORT - ? Number(process.env.DSQL_DB_PORT) + host: process.env.BUN_MARIADB_SERVER_HOST, + user: process.env.BUN_MARIADB_SERVER_USERNAME, + password: process.env.BUN_MARIADB_SERVER_PASSWORD, + database: configData?.db_name, + port: process.env.BUN_MARIADB_SERVER_PORT + ? Number(process.env.BUN_MARIADB_SERVER_PORT) : undefined, - charset: "utf8mb4", + charset: configData?.charset || "utf8mb4", ssl: grabDbSSL(), bigIntAsNumber: true, supportBigNumbers: true, diff --git a/src/lib/grab-duplicate-safe-insert-sql.ts b/src/lib/grab-duplicate-safe-insert-sql.ts index f5065b5..ba78ecc 100644 --- a/src/lib/grab-duplicate-safe-insert-sql.ts +++ b/src/lib/grab-duplicate-safe-insert-sql.ts @@ -4,37 +4,45 @@ type Params = { data: any | any[]; }; -export default async function ({ sql: passed_sql, table, data }: Params) { - let sql = passed_sql; +function quoteIdentifier(identifier: string): string { + return `\`${identifier.replace(/`/g, "``")}\``; +} + +export default async function ({ sql: passedSql, table, data }: Params) { const config = global.CONFIG; const dbSchema = global.DB_SCHEMA; - const table_schema = dbSchema.tables.find((t) => t.tableName == table); - const now = Date.now(); + const tableSchema = dbSchema?.tables.find( + (t: { tableName: string }) => t.tableName == table, + ); - if (table_schema?.tableName) { - const set_sql_arr = Object.keys( - Array.isArray(data) ? data[0] : data, - ).map((field) => `${field} = excluded.${field}`); - - set_sql_arr.push(`updated_at = ${now}`); - - const set_sql = set_sql_arr.join(", "); - - const unique_fields = table_schema.fields.filter((f) => f.unique); - - for (let i = 0; i < unique_fields.length; i++) { - const field = unique_fields[i]; - sql += ` ON CONFLICT(${field?.fieldName}) DO UPDATE SET ${set_sql}`; - } - - if (table_schema.uniqueConstraints?.[0]) { - for (let i = 0; i < table_schema.uniqueConstraints.length; i++) { - const constraint = table_schema.uniqueConstraints[i]; - sql += ` ON CONFLICT(${constraint?.constraintTableFields?.map((c) => c.value)?.join(", ")}) DO UPDATE SET ${set_sql}`; - } - } + if (!tableSchema?.tableName) { + return passedSql; } - return sql; + const dataObject = Array.isArray(data) ? data[0] : data; + const dataKeys = Object.keys(dataObject || {}); + + if (dataKeys.length === 0) { + return passedSql; + } + + const hasUniqueField = + tableSchema.fields.some((field) => field.unique) || + Boolean(tableSchema.uniqueConstraints?.length); + + if (!hasUniqueField) { + return passedSql; + } + + const setSql = dataKeys + .filter((field: string) => field !== "updated_at") + .map( + (field: string) => + `${quoteIdentifier(field)} = VALUES(${quoteIdentifier(field)})`, + ); + + setSql.push(`updated_at = ${Date.now()}`); + + return `${passedSql} ON DUPLICATE KEY UPDATE ${setSql.join(", ")}`; } diff --git a/src/lib/mariadb/db-delete.ts b/src/lib/mariadb/db-delete.ts index e16f22b..0c143f5 100644 --- a/src/lib/mariadb/db-delete.ts +++ b/src/lib/mariadb/db-delete.ts @@ -1,4 +1,4 @@ -import DbClient from "."; +import dbHandler from "../db-handler"; import _ from "lodash"; import type { APIResponseObject, ServerQueryParam } from "../../types"; import sqlGenerator from "../../utils/sql-generator"; @@ -12,6 +12,10 @@ type Params< targetId?: number | string; }; +function quoteIdentifier(identifier: string): string { + return `\`${identifier.replace(/`/g, "``")}\``; +} + export default async function DbDelete< Schema extends { [k: string]: any } = { [k: string]: any }, Table extends string = string, @@ -39,30 +43,13 @@ export default async function DbDelete< } sqlObj = sqlGenerator({ - tableName: table, + tableName: quoteIdentifier(table), genObject: finalQuery, }); const whereClause = sqlObj.string.match(/WHERE .*/)?.[0]; - if (whereClause) { - let sql = `DELETE FROM ${table} ${whereClause}`; - - sqlObj.string = sql; - - const res = DbClient.run(sql, sqlObj.values); - - return { - success: Boolean(res.changes), - postInsertReturn: { - affectedRows: res.changes, - insertId: Number(res.lastInsertRowid), - }, - debug: { - sqlObj, - }, - }; - } else { + if (!whereClause) { return { success: false, msg: `No WHERE clause`, @@ -71,6 +58,38 @@ export default async function DbDelete< }, }; } + + const sql = `DELETE FROM ${quoteIdentifier(table)} ${whereClause}`; + + sqlObj.string = sql; + + const res = await dbHandler({ + query: sql, + values: sqlObj.values as any, + }); + + if (!res.success) { + return { + success: false, + msg: "Database delete failed", + debug: { + sqlObj, + }, + }; + } + + const singleRes = res.single_res as any; + + return { + success: Boolean(singleRes?.affectedRows), + postInsertReturn: { + affectedRows: Number(singleRes?.affectedRows), + insertId: Number(singleRes?.insertId), + }, + debug: { + sqlObj, + }, + }; } catch (error: any) { return { success: false, diff --git a/src/lib/mariadb/db-insert.ts b/src/lib/mariadb/db-insert.ts index 5dce989..94c9eb1 100644 --- a/src/lib/mariadb/db-insert.ts +++ b/src/lib/mariadb/db-insert.ts @@ -1,4 +1,4 @@ -import DbClient from "."; +import dbHandler from "../db-handler"; import type { APIResponseObject, SQLInsertGenReturn } from "../../types"; import sqlInsertGenerator from "../../utils/sql-insert-generator"; import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql"; @@ -35,21 +35,41 @@ export default async function DbInsert< data: finalData as any[], }) || null; - let sql = sqlObj?.query || ""; + if (!sqlObj) { + return { + success: false, + msg: "No insert SQL generated", + }; + } + + let sql = sqlObj.query; if (update_on_duplicate && data[0]) { sql = await grabDuplicateSafeInsertSql({ data, table, sql }); } - (sqlObj || ({} as any)).query = sql; + const res = await dbHandler({ + query: sql, + values: sqlObj.values as any, + }); - const res = DbClient.run(sql, sqlObj?.values || []); + if (!res.success) { + return { + success: false, + msg: "Database insert failed", + debug: { + sqlObj, + }, + }; + } + + const singleRes = res.single_res as any; return { - success: Boolean(Number(res.lastInsertRowid)), + success: Boolean(singleRes?.affectedRows || singleRes?.insertId), postInsertReturn: { - affectedRows: res.changes, - insertId: Number(res.lastInsertRowid), + affectedRows: Number(singleRes?.affectedRows), + insertId: Number(singleRes?.insertId), }, debug: { sqlObj, diff --git a/src/lib/mariadb/db-schema-manager.ts b/src/lib/mariadb/db-schema-manager.ts index 442205c..f3ef2db 100644 --- a/src/lib/mariadb/db-schema-manager.ts +++ b/src/lib/mariadb/db-schema-manager.ts @@ -59,10 +59,6 @@ class MariaDBSchemaManager { console.log("Schema synchronization complete!"); } - private getDatabaseName(): string | undefined { - return this.db_schema.dbName || this.db_schema.dbSlug; - } - private quoteIdentifier(identifier: string): string { return `\`${identifier.replace(/`/g, "``")}\``; } @@ -71,7 +67,8 @@ class MariaDBSchemaManager { where: string; values: QueryValues; } { - const databaseName = this.getDatabaseName(); + const config = global.CONFIG; + const databaseName = config?.db_name; if (databaseName) { return { @@ -87,12 +84,11 @@ class MariaDBSchemaManager { } private async run(query: string, values?: QueryValues): Promise { + const config = global.CONFIG; const res = await dbHandler({ query, values: values as any, - config: this.getDatabaseName() - ? { database: this.getDatabaseName() } - : undefined, + config: config?.db_config, }); if (!res.success) { @@ -107,9 +103,7 @@ class MariaDBSchemaManager { const res = await dbHandler({ query, values: values as any, - config: this.getDatabaseName() - ? { database: this.getDatabaseName() } - : undefined, + config: global.CONFIG.db_config, }); if (!res.success) { @@ -195,7 +189,9 @@ class MariaDBSchemaManager { for (const tableName of tablesToDrop) { console.log(`Dropping table: ${tableName}`); - await this.run(`DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`); + await this.run( + `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`, + ); await this.removeDbManagerTable(tableName); } } @@ -308,7 +304,10 @@ class MariaDBSchemaManager { const options = ["ENGINE=InnoDB"]; if (table.collation) { - options.push("DEFAULT CHARSET=utf8mb4", `COLLATE ${table.collation}`); + options.push( + "DEFAULT CHARSET=utf8mb4", + `COLLATE ${table.collation}`, + ); } return ` ${options.join(" ")}`; @@ -321,11 +320,12 @@ class MariaDBSchemaManager { await this.recreateTable(table); } - private async getTableColumns( - tableName: string, - ): Promise { + private async getTableColumns(tableName: string): Promise { const tableSchemaWhere = this.tableSchemaWhere(tableName); - const rows = await this.query<{ COLUMN_NAME: string; COLUMN_TYPE: string }>( + const rows = await this.query<{ + COLUMN_NAME: string; + COLUMN_TYPE: string; + }>( `SELECT COLUMN_NAME AS name, COLUMN_TYPE AS type FROM information_schema.COLUMNS WHERE ${tableSchemaWhere.where} AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`, tableSchemaWhere.values, ); @@ -379,7 +379,9 @@ class MariaDBSchemaManager { existingRows = await this.query>( `SELECT * FROM ${this.quoteIdentifier(table.tableName)}`, ); - await this.run(`DROP TABLE ${this.quoteIdentifier(table.tableName)}`); + await this.run( + `DROP TABLE ${this.quoteIdentifier(table.tableName)}`, + ); } await this.createTable(table); @@ -395,7 +397,9 @@ class MariaDBSchemaManager { const existingColumns = await this.getTableColumns(table.tableName); const columnsToKeep = table.fields .filter((field) => - existingColumns.some((column) => column.name === field.fieldName), + existingColumns.some( + (column) => column.name === field.fieldName, + ), ) .map((field) => field.fieldName) .filter((fieldName): fieldName is string => Boolean(fieldName)); @@ -624,7 +628,9 @@ class MariaDBSchemaManager { if (!stillExists) { console.log(`Dropping index: ${indexName}`); - await this.run(`DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteIdentifier(table.tableName)}`); + await this.run( + `DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteIdentifier(table.tableName)}`, + ); } } diff --git a/src/lib/mariadb/db-select.ts b/src/lib/mariadb/db-select.ts index 86c3ca4..4040d28 100644 --- a/src/lib/mariadb/db-select.ts +++ b/src/lib/mariadb/db-select.ts @@ -1,5 +1,4 @@ -import mysql from "mysql"; -import DbClient from "."; +import dbHandler from "../db-handler"; import _ from "lodash"; import type { APIResponseObject, ServerQueryParam } from "../../types"; import sqlGenerator from "../../utils/sql-generator"; @@ -14,6 +13,10 @@ type Params< targetId?: number | string; }; +function quoteIdentifier(identifier: string): string { + return `\`${identifier.replace(/`/g, "``")}\``; +} + export default async function DbSelect< Schema extends { [k: string]: any } = { [k: string]: any }, Table extends string = string, @@ -42,44 +45,75 @@ export default async function DbSelect< } sqlObj = sqlGenerator({ - tableName: table, + tableName: quoteIdentifier(table), genObject: finalQuery, }); - let sql = mysql.format(sqlObj.string, sqlObj.values); + const res = await dbHandler({ + query: sqlObj.string, + values: sqlObj.values as any, + }); - const res = DbClient.query(sql); - const batchRes = res.all(); + if (!res.success) { + return { + success: false, + msg: "Database select failed", + debug: { + sqlObj, + sql: sqlObj.string, + }, + }; + } + + const batchRes = (res.payload || []) as Schema[]; let resp: APIResponseObject = { - success: Boolean(batchRes[0]), + success: true, payload: batchRes, singleRes: batchRes[0], debug: { sqlObj, - sql, + sql: sqlObj.string, }, }; if (count) { - let count_sql_object = sqlGenerator({ - tableName: table, + const countSqlObject = sqlGenerator({ + tableName: quoteIdentifier(table), genObject: finalQuery, count, }); - let count_sql = mysql.format( - count_sql_object.string, - count_sql_object.values, - ); + const countSql = `SELECT COUNT(*) AS count FROM (${countSqlObject.string}) AS c`; - count_sql = `SELECT COUNT(*) FROM (${count_sql}) as c`; + const countRes = await dbHandler<{ count: number }>({ + query: countSql, + values: countSqlObject.values as any, + }); - const count_res = DbClient.query(count_sql).all(); + if (!countRes.success) { + return { + ...resp, + success: false, + msg: "Database count failed", + debug: { + ...resp.debug, + count_sql: countSql, + }, + }; + } - const count_val = count_res[0]?.["COUNT(*)"]; - resp["count"] = Number(count_val); - resp["debug"]["count_sql"] = count_sql; + const countRows = countRes.payload || []; + const countVal = countRows[0]?.count ?? countRows[0]?.["COUNT(*)"]; + + resp = { + ...resp, + count: Number(countVal), + debug: { + ...resp.debug, + count_sql: countSql, + }, + }; } return resp; diff --git a/src/lib/mariadb/db-sql.ts b/src/lib/mariadb/db-sql.ts index eb89ad2..ef327bf 100644 --- a/src/lib/mariadb/db-sql.ts +++ b/src/lib/mariadb/db-sql.ts @@ -1,5 +1,4 @@ -import DbClient from "."; -import _ from "lodash"; +import dbHandler from "../db-handler"; import type { APIResponseObject, SQLInsertGenValueType } from "../../types"; type Params = { @@ -11,25 +10,45 @@ export default async function DbSQL< T extends { [k: string]: any } = { [k: string]: any }, >({ sql, values }: Params): Promise> { try { - const trimmed_sql = sql.trim(); + const trimmedSql = sql.trim(); + const isSelect = trimmedSql.match(/^select/i); - const res = trimmed_sql.match(/^select/i) - ? DbClient.query(trimmed_sql).all(...(values || [])) - : DbClient.run(trimmed_sql, values || []); + const res = await dbHandler({ + query: trimmedSql, + values: values as any, + }); + + if (!res.success) { + return { + success: false, + msg: "Database query failed", + debug: { + sqlObj: { + sql: trimmedSql, + values, + }, + sql, + }, + }; + } + + const payload = isSelect ? ((res.payload || []) as T[]) : undefined; + const singleRes = isSelect ? payload?.[0] : (res.single_res as T); + const singleRaw = res.single_res as any; return { success: true, - payload: Array.isArray(res) ? (res as T[]) : undefined, - singleRes: Array.isArray(res) ? (res as T[])?.[0] : undefined, - postInsertReturn: Array.isArray(res) + payload, + singleRes, + postInsertReturn: isSelect ? undefined : { - affectedRows: res.changes, - insertId: Number(res.lastInsertRowid), + affectedRows: Number(singleRaw?.affectedRows), + insertId: Number(singleRaw?.insertId), }, debug: { sqlObj: { - sql: trimmed_sql, + sql: trimmedSql, values, }, sql, diff --git a/src/lib/mariadb/db-update.ts b/src/lib/mariadb/db-update.ts index 51d5f7e..e0da450 100644 --- a/src/lib/mariadb/db-update.ts +++ b/src/lib/mariadb/db-update.ts @@ -1,4 +1,4 @@ -import DbClient from "."; +import dbHandler from "../db-handler"; import _ from "lodash"; import type { APIResponseObject, @@ -17,6 +17,10 @@ type Params< targetId?: number | string; }; +function quoteIdentifier(identifier: string): string { + return `\`${identifier.replace(/`/g, "``")}\``; +} + export default async function DbUpdate< Schema extends { [k: string]: any } = { [k: string]: any }, Table extends string = string, @@ -45,63 +49,76 @@ export default async function DbUpdate< } const sqlQueryObj = sqlGenerator({ - tableName: table, + tableName: quoteIdentifier(table), genObject: finalQuery, }); - let values: SQLInsertGenValueType[] = []; - const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0]; - if (whereClause) { - let sql = `UPDATE ${table} SET`; - - const finalData: { [k: string]: SQLInsertGenValueType } = { - updated_at: Date.now(), - ...data, - }; - - const keys = Object.keys(finalData); - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (!key) continue; - - const isLast = i == keys.length - 1; - - sql += ` ${key}=?`; - const value = finalData[key]; - values.push(value || null); - - if (!isLast) { - sql += `,`; - } - } - - sql += ` ${whereClause}`; - values = [...values, ...sqlQueryObj.values]; - - sqlObj.string = sql; - sqlObj.values = values as any[]; - - const res = DbClient.run(sql, values); - - return { - success: Boolean(res.changes), - postInsertReturn: { - affectedRows: res.changes, - insertId: Number(res.lastInsertRowid), - }, - debug: { - sqlObj, - }, - }; - } else { + if (!whereClause) { return { success: false, msg: `No WHERE clause`, }; } + + let values: SQLInsertGenValueType[] = []; + let sql = `UPDATE ${quoteIdentifier(table)} SET`; + + const finalData: { [k: string]: SQLInsertGenValueType } = { + updated_at: Date.now(), + ...data, + }; + + const keys = Object.keys(finalData); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (!key) continue; + + const isLast = i == keys.length - 1; + + sql += ` ${quoteIdentifier(key)}=?`; + values.push(finalData[key] ?? null); + + if (!isLast) { + sql += `,`; + } + } + + sql += ` ${whereClause}`; + values = [...values, ...sqlQueryObj.values]; + + sqlObj.string = sql; + sqlObj.values = values as any[]; + + const res = await dbHandler({ + query: sql, + values: values as any, + }); + + if (!res.success) { + return { + success: false, + msg: "Database update failed", + debug: { + sqlObj, + }, + }; + } + + const singleRes = res.single_res as any; + + return { + success: Boolean(singleRes?.affectedRows || singleRes?.changedRows), + postInsertReturn: { + affectedRows: Number(singleRes?.affectedRows), + insertId: Number(singleRes?.insertId), + }, + debug: { + sqlObj, + }, + }; } catch (error: any) { return { success: false, diff --git a/src/lib/mariadb/schema.ts b/src/lib/mariadb/schema.ts deleted file mode 100644 index 8926845..0000000 --- a/src/lib/mariadb/schema.ts +++ /dev/null @@ -1,7 +0,0 @@ -import _ from "lodash"; -import type { BUN_MARIADB_DatabaseSchemaType } from "../../types"; - -export const DbSchema: BUN_MARIADB_DatabaseSchemaType = { - dbName: "travis-ai", - tables: [], -}; diff --git a/src/types/index.ts b/src/types/index.ts index 095d938..7552e89 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -28,18 +28,8 @@ export const UsersOmitedFields = [ * database metadata. */ export interface BUN_MARIADB_DatabaseSchemaType { - id?: string | number; - dbName?: string; - dbSlug?: string; - dbFullName?: string; - dbDescription?: string; - dbImage?: string; tables: BUN_MARIADB_TableSchemaType[]; - childrenDatabases?: BUN_MARIADB_ChildrenDatabaseObject[]; - childDatabase?: boolean; - childDatabaseDbId?: string | number; - updateData?: boolean; - collation?: (typeof MariaDBCollations)[number]; + collation?: MariaDbCollationsType; } /** @@ -58,6 +48,12 @@ export const MariaDBCollations = [ "utf8mb4_unicode_520_ci", ] as const; +export type MariaDbCollationsType = (typeof MariaDBCollations)[number]; + +export const MariaDBCharsets = ["utf8mb4"] as const; + +export type MariaDbCharsetType = (typeof MariaDBCharsets)[number]; + /** * Describes a single table within a database schema, including its fields, * indexes, unique constraints, and parent/child table relationships. @@ -1498,11 +1494,6 @@ export type SQLInsertGenParams = { */ export type BunMariaDBConfig = { db_name: string; - /** - * The Name of the Database Schema File. Eg `db_schema.ts`. This is - * relative to `db_dir`, or root dir if `db_dir` is not provided - */ - db_schema_file_name: string; /** * The Directory for backups. Relative to db_dir. */ @@ -1518,9 +1509,17 @@ export type BunMariaDBConfig = { */ typedef_file_path?: string; /** - * Whether to enable `WAL` mode + * Configuration for the MariaDB Connection */ - wal_mode?: boolean; + db_config?: ConnectionConfig; + /** + * Database charset. Defaults to `utf8mb4` + */ + charset?: MariaDbCharsetType; + /** + * Database Connection timeout. Defaults to `10000` + */ + connection_timeout?: number; }; /** @@ -1589,3 +1588,11 @@ export type DBResponseObject< payload?: T[]; single_res?: T; }; + +export const RequiredENVs = [ + "BUN_MARIADB_SERVER_HOST", + "BUN_MARIADB_SERVER_PORT", + "BUN_MARIADB_SERVER_USERNAME", + "BUN_MARIADB_SERVER_PASSWORD", + "BUN_MARIADB_SERVER_SSL_KEY_PATH", +] as const; diff --git a/src/utils/sql-insert-generator.ts b/src/utils/sql-insert-generator.ts index 86bd612..b25286a 100644 --- a/src/utils/sql-insert-generator.ts +++ b/src/utils/sql-insert-generator.ts @@ -4,6 +4,10 @@ import type { SQLInsertGenValueType, } from "../types"; +function quoteIdentifier(identifier: string): string { + return `\`${identifier.replace(/`/g, "``")}\``; +} + /** * # SQL Insert Generator */ @@ -12,7 +16,7 @@ export default function sqlInsertGenerator({ data, dbFullName, }: SQLInsertGenParams): SQLInsertGenReturn | undefined { - const finalDbName = dbFullName ? `${dbFullName}.` : ""; + const finalDbName = dbFullName ? `${quoteIdentifier(dbFullName)}.` : ""; try { if (Array.isArray(data) && data?.[0]) { @@ -64,9 +68,9 @@ export default function sqlInsertGenerator({ .join(",")})`, ); }); - let query = `INSERT INTO ${finalDbName}${tableName} (${insertKeys.join( - ",", - )}) VALUES ${queryBatches.join(",")}`; + + const insertColumns = insertKeys.map(quoteIdentifier).join(","); + let query = `INSERT INTO ${finalDbName}${quoteIdentifier(tableName)} (${insertColumns}) VALUES ${queryBatches.join(",")}`; return { query: query,