First version without typescript errors
This commit is contained in:
parent
9c8f6e20aa
commit
eb86e1803d
@ -25,9 +25,6 @@ export default function () {
|
|||||||
const dbSchema = global.DB_SCHEMA;
|
const dbSchema = global.DB_SCHEMA;
|
||||||
|
|
||||||
const { ROOT_DIR, BUN_MARIADB_TEMP_DB_FILE_PATH } = grabDirNames();
|
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 {
|
try {
|
||||||
const isVector = Boolean(opts.vector || opts.v);
|
const isVector = Boolean(opts.vector || opts.v);
|
||||||
@ -66,7 +63,6 @@ export default function () {
|
|||||||
process.exit();
|
process.exit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
cpSync(BUN_MARIADB_TEMP_DB_FILE_PATH, db_file_path);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,4 +3,5 @@ export const AppData = {
|
|||||||
MaxBackups: 10,
|
MaxBackups: 10,
|
||||||
DefaultBackupDirName: ".backups",
|
DefaultBackupDirName: ".backups",
|
||||||
DbSchemaManagerTableName: "__db_schema_manager__",
|
DbSchemaManagerTableName: "__db_schema_manager__",
|
||||||
|
DbSchemaFileName: "schema.ts",
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@ -2,10 +2,11 @@ import path from "path";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { AppData } from "../data/app-data";
|
import { AppData } from "../data/app-data";
|
||||||
import grabDirNames from "../data/grab-dir-names";
|
import grabDirNames from "../data/grab-dir-names";
|
||||||
import type {
|
import {
|
||||||
BunMariaDBConfig,
|
type BunMariaDBConfig,
|
||||||
BunMariaDBConfigReturn,
|
type BunMariaDBConfigReturn,
|
||||||
BUN_MARIADB_DatabaseSchemaType,
|
type BUN_MARIADB_DatabaseSchemaType,
|
||||||
|
RequiredENVs,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
export default async function init(): Promise<BunMariaDBConfigReturn> {
|
export default async function init(): Promise<BunMariaDBConfigReturn> {
|
||||||
@ -25,27 +26,53 @@ export default async function init(): Promise<BunMariaDBConfigReturn> {
|
|||||||
const ConfigImport = await import(ConfigFilePath);
|
const ConfigImport = await import(ConfigFilePath);
|
||||||
const Config = ConfigImport["default"] as BunMariaDBConfig;
|
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) {
|
if (!Config.db_name) {
|
||||||
console.error(`\`db_name\` is required in your config`);
|
console.error(`\`db_name\` is required in your config`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Config.db_schema_file_name) {
|
RequiredENVs.forEach((env_key) => {
|
||||||
console.error(`\`db_schema_file_name\` is required in your config`);
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let db_dir = ROOT_DIR;
|
const db_dir = path.resolve(ROOT_DIR, Config.db_dir);
|
||||||
|
|
||||||
if (Config.db_dir) {
|
|
||||||
db_dir = path.resolve(ROOT_DIR, Config.db_dir);
|
|
||||||
|
|
||||||
if (!fs.existsSync(Config.db_dir)) {
|
if (!fs.existsSync(Config.db_dir)) {
|
||||||
fs.mkdirSync(Config.db_dir, { recursive: true });
|
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 DbSchemaImport = await import(DBSchemaFilePath);
|
||||||
const DbSchema = DbSchemaImport[
|
const DbSchema = DbSchemaImport[
|
||||||
"default"
|
"default"
|
||||||
|
|||||||
@ -5,9 +5,6 @@ import grabDBConnection from "./grab-db-connection";
|
|||||||
type Param = {
|
type Param = {
|
||||||
query: string;
|
query: string;
|
||||||
values?: string[] | object;
|
values?: string[] | object;
|
||||||
noErrorLogs?: boolean;
|
|
||||||
database?: string;
|
|
||||||
tableSchema?: BUN_MARIADB_TableSchemaType;
|
|
||||||
config?: ConnectionConfig;
|
config?: ConnectionConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -17,18 +14,12 @@ type Param = {
|
|||||||
*/
|
*/
|
||||||
export default async function dbHandler<
|
export default async function dbHandler<
|
||||||
T extends { [k: string]: any } = { [k: string]: any },
|
T extends { [k: string]: any } = { [k: string]: any },
|
||||||
>({
|
>({ query, values, config }: Param): Promise<DBResponseObject> {
|
||||||
query,
|
|
||||||
values,
|
|
||||||
noErrorLogs,
|
|
||||||
database,
|
|
||||||
config,
|
|
||||||
}: Param): Promise<DBResponseObject> {
|
|
||||||
let CONNECTION: Connection | undefined;
|
let CONNECTION: Connection | undefined;
|
||||||
let results: T | null = null;
|
let results: T | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CONNECTION = await grabDBConnection({ database, config });
|
CONNECTION = await grabDBConnection({ config });
|
||||||
|
|
||||||
if (query && values) {
|
if (query && values) {
|
||||||
const queryResults = await CONNECTION.query(query, values);
|
const queryResults = await CONNECTION.query(query, values);
|
||||||
@ -46,10 +37,6 @@ export default async function dbHandler<
|
|||||||
throw new Error("Authentication Failed!");
|
throw new Error("Authentication Failed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!noErrorLogs) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
results = null;
|
results = null;
|
||||||
} finally {
|
} finally {
|
||||||
await CONNECTION?.end();
|
await CONNECTION?.end();
|
||||||
|
|||||||
@ -8,7 +8,7 @@ type Return = ConnectionConfig["ssl"] | undefined;
|
|||||||
* # Grab SSL
|
* # Grab SSL
|
||||||
*/
|
*/
|
||||||
export default function grabDbSSL(): Return {
|
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(/./)) {
|
if (!caProivdedPath?.match(/./)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -8,17 +8,18 @@ import grabDbSSL from "./grab-db-ssl";
|
|||||||
export default function grabDSQLConnectionConfig(
|
export default function grabDSQLConnectionConfig(
|
||||||
param?: DsqlConnectionParam,
|
param?: DsqlConnectionParam,
|
||||||
): ConnectionConfig {
|
): ConnectionConfig {
|
||||||
const CONN_TIMEOUT = 10000;
|
const configData = global.CONFIG;
|
||||||
|
const CONN_TIMEOUT = configData?.connection_timeout || 10000;
|
||||||
|
|
||||||
const config: ConnectionConfig = {
|
const config: ConnectionConfig = {
|
||||||
host: process.env.DSQL_DB_HOST,
|
host: process.env.BUN_MARIADB_SERVER_HOST,
|
||||||
user: process.env.DSQL_DB_USERNAME,
|
user: process.env.BUN_MARIADB_SERVER_USERNAME,
|
||||||
password: process.env.DSQL_DB_PASSWORD,
|
password: process.env.BUN_MARIADB_SERVER_PASSWORD,
|
||||||
// database: param?.dbFullName,
|
database: configData?.db_name,
|
||||||
port: process.env.DSQL_DB_PORT
|
port: process.env.BUN_MARIADB_SERVER_PORT
|
||||||
? Number(process.env.DSQL_DB_PORT)
|
? Number(process.env.BUN_MARIADB_SERVER_PORT)
|
||||||
: undefined,
|
: undefined,
|
||||||
charset: "utf8mb4",
|
charset: configData?.charset || "utf8mb4",
|
||||||
ssl: grabDbSSL(),
|
ssl: grabDbSSL(),
|
||||||
bigIntAsNumber: true,
|
bigIntAsNumber: true,
|
||||||
supportBigNumbers: true,
|
supportBigNumbers: true,
|
||||||
|
|||||||
@ -4,37 +4,45 @@ type Params = {
|
|||||||
data: any | any[];
|
data: any | any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function ({ sql: passed_sql, table, data }: Params) {
|
function quoteIdentifier(identifier: string): string {
|
||||||
let sql = passed_sql;
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ({ sql: passedSql, table, data }: Params) {
|
||||||
const config = global.CONFIG;
|
const config = global.CONFIG;
|
||||||
const dbSchema = global.DB_SCHEMA;
|
const dbSchema = global.DB_SCHEMA;
|
||||||
|
|
||||||
const table_schema = dbSchema.tables.find((t) => t.tableName == table);
|
const tableSchema = dbSchema?.tables.find(
|
||||||
const now = Date.now();
|
(t: { tableName: string }) => t.tableName == table,
|
||||||
|
);
|
||||||
|
|
||||||
if (table_schema?.tableName) {
|
if (!tableSchema?.tableName) {
|
||||||
const set_sql_arr = Object.keys(
|
return passedSql;
|
||||||
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]) {
|
const dataObject = Array.isArray(data) ? data[0] : data;
|
||||||
for (let i = 0; i < table_schema.uniqueConstraints.length; i++) {
|
const dataKeys = Object.keys(dataObject || {});
|
||||||
const constraint = table_schema.uniqueConstraints[i];
|
|
||||||
sql += ` ON CONFLICT(${constraint?.constraintTableFields?.map((c) => c.value)?.join(", ")}) DO UPDATE SET ${set_sql}`;
|
if (dataKeys.length === 0) {
|
||||||
}
|
return passedSql;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sql;
|
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(", ")}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import DbClient from ".";
|
import dbHandler from "../db-handler";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type { APIResponseObject, ServerQueryParam } from "../../types";
|
import type { APIResponseObject, ServerQueryParam } from "../../types";
|
||||||
import sqlGenerator from "../../utils/sql-generator";
|
import sqlGenerator from "../../utils/sql-generator";
|
||||||
@ -12,6 +12,10 @@ type Params<
|
|||||||
targetId?: number | string;
|
targetId?: number | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function quoteIdentifier(identifier: string): string {
|
||||||
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
|
|
||||||
export default async function DbDelete<
|
export default async function DbDelete<
|
||||||
Schema extends { [k: string]: any } = { [k: string]: any },
|
Schema extends { [k: string]: any } = { [k: string]: any },
|
||||||
Table extends string = string,
|
Table extends string = string,
|
||||||
@ -39,30 +43,13 @@ export default async function DbDelete<
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlObj = sqlGenerator({
|
sqlObj = sqlGenerator({
|
||||||
tableName: table,
|
tableName: quoteIdentifier(table),
|
||||||
genObject: finalQuery,
|
genObject: finalQuery,
|
||||||
});
|
});
|
||||||
|
|
||||||
const whereClause = sqlObj.string.match(/WHERE .*/)?.[0];
|
const whereClause = sqlObj.string.match(/WHERE .*/)?.[0];
|
||||||
|
|
||||||
if (whereClause) {
|
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 {
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
msg: `No WHERE clause`,
|
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) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import DbClient from ".";
|
import dbHandler from "../db-handler";
|
||||||
import type { APIResponseObject, SQLInsertGenReturn } from "../../types";
|
import type { APIResponseObject, SQLInsertGenReturn } from "../../types";
|
||||||
import sqlInsertGenerator from "../../utils/sql-insert-generator";
|
import sqlInsertGenerator from "../../utils/sql-insert-generator";
|
||||||
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
|
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
|
||||||
@ -35,21 +35,41 @@ export default async function DbInsert<
|
|||||||
data: finalData as any[],
|
data: finalData as any[],
|
||||||
}) || null;
|
}) || 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]) {
|
if (update_on_duplicate && data[0]) {
|
||||||
sql = await grabDuplicateSafeInsertSql({ data, table, sql });
|
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 {
|
return {
|
||||||
success: Boolean(Number(res.lastInsertRowid)),
|
success: Boolean(singleRes?.affectedRows || singleRes?.insertId),
|
||||||
postInsertReturn: {
|
postInsertReturn: {
|
||||||
affectedRows: res.changes,
|
affectedRows: Number(singleRes?.affectedRows),
|
||||||
insertId: Number(res.lastInsertRowid),
|
insertId: Number(singleRes?.insertId),
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
sqlObj,
|
sqlObj,
|
||||||
|
|||||||
@ -59,10 +59,6 @@ class MariaDBSchemaManager {
|
|||||||
console.log("Schema synchronization complete!");
|
console.log("Schema synchronization complete!");
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDatabaseName(): string | undefined {
|
|
||||||
return this.db_schema.dbName || this.db_schema.dbSlug;
|
|
||||||
}
|
|
||||||
|
|
||||||
private quoteIdentifier(identifier: string): string {
|
private quoteIdentifier(identifier: string): string {
|
||||||
return `\`${identifier.replace(/`/g, "``")}\``;
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
}
|
}
|
||||||
@ -71,7 +67,8 @@ class MariaDBSchemaManager {
|
|||||||
where: string;
|
where: string;
|
||||||
values: QueryValues;
|
values: QueryValues;
|
||||||
} {
|
} {
|
||||||
const databaseName = this.getDatabaseName();
|
const config = global.CONFIG;
|
||||||
|
const databaseName = config?.db_name;
|
||||||
|
|
||||||
if (databaseName) {
|
if (databaseName) {
|
||||||
return {
|
return {
|
||||||
@ -87,12 +84,11 @@ class MariaDBSchemaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async run(query: string, values?: QueryValues): Promise<void> {
|
private async run(query: string, values?: QueryValues): Promise<void> {
|
||||||
|
const config = global.CONFIG;
|
||||||
const res = await dbHandler({
|
const res = await dbHandler({
|
||||||
query,
|
query,
|
||||||
values: values as any,
|
values: values as any,
|
||||||
config: this.getDatabaseName()
|
config: config?.db_config,
|
||||||
? { database: this.getDatabaseName() }
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
@ -107,9 +103,7 @@ class MariaDBSchemaManager {
|
|||||||
const res = await dbHandler<T>({
|
const res = await dbHandler<T>({
|
||||||
query,
|
query,
|
||||||
values: values as any,
|
values: values as any,
|
||||||
config: this.getDatabaseName()
|
config: global.CONFIG.db_config,
|
||||||
? { database: this.getDatabaseName() }
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
@ -195,7 +189,9 @@ class MariaDBSchemaManager {
|
|||||||
|
|
||||||
for (const tableName of tablesToDrop) {
|
for (const tableName of tablesToDrop) {
|
||||||
console.log(`Dropping table: ${tableName}`);
|
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);
|
await this.removeDbManagerTable(tableName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -308,7 +304,10 @@ class MariaDBSchemaManager {
|
|||||||
const options = ["ENGINE=InnoDB"];
|
const options = ["ENGINE=InnoDB"];
|
||||||
|
|
||||||
if (table.collation) {
|
if (table.collation) {
|
||||||
options.push("DEFAULT CHARSET=utf8mb4", `COLLATE ${table.collation}`);
|
options.push(
|
||||||
|
"DEFAULT CHARSET=utf8mb4",
|
||||||
|
`COLLATE ${table.collation}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ` ${options.join(" ")}`;
|
return ` ${options.join(" ")}`;
|
||||||
@ -321,11 +320,12 @@ class MariaDBSchemaManager {
|
|||||||
await this.recreateTable(table);
|
await this.recreateTable(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTableColumns(
|
private async getTableColumns(tableName: string): Promise<ColumnInfoRow[]> {
|
||||||
tableName: string,
|
|
||||||
): Promise<ColumnInfoRow[]> {
|
|
||||||
const tableSchemaWhere = this.tableSchemaWhere(tableName);
|
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`,
|
`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,
|
tableSchemaWhere.values,
|
||||||
);
|
);
|
||||||
@ -379,7 +379,9 @@ class MariaDBSchemaManager {
|
|||||||
existingRows = await this.query<Record<string, any>>(
|
existingRows = await this.query<Record<string, any>>(
|
||||||
`SELECT * FROM ${this.quoteIdentifier(table.tableName)}`,
|
`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);
|
await this.createTable(table);
|
||||||
@ -395,7 +397,9 @@ class MariaDBSchemaManager {
|
|||||||
const existingColumns = await this.getTableColumns(table.tableName);
|
const existingColumns = await this.getTableColumns(table.tableName);
|
||||||
const columnsToKeep = table.fields
|
const columnsToKeep = table.fields
|
||||||
.filter((field) =>
|
.filter((field) =>
|
||||||
existingColumns.some((column) => column.name === field.fieldName),
|
existingColumns.some(
|
||||||
|
(column) => column.name === field.fieldName,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.map((field) => field.fieldName)
|
.map((field) => field.fieldName)
|
||||||
.filter((fieldName): fieldName is string => Boolean(fieldName));
|
.filter((fieldName): fieldName is string => Boolean(fieldName));
|
||||||
@ -624,7 +628,9 @@ class MariaDBSchemaManager {
|
|||||||
|
|
||||||
if (!stillExists) {
|
if (!stillExists) {
|
||||||
console.log(`Dropping index: ${indexName}`);
|
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)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import mysql from "mysql";
|
import dbHandler from "../db-handler";
|
||||||
import DbClient from ".";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type { APIResponseObject, ServerQueryParam } from "../../types";
|
import type { APIResponseObject, ServerQueryParam } from "../../types";
|
||||||
import sqlGenerator from "../../utils/sql-generator";
|
import sqlGenerator from "../../utils/sql-generator";
|
||||||
@ -14,6 +13,10 @@ type Params<
|
|||||||
targetId?: number | string;
|
targetId?: number | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function quoteIdentifier(identifier: string): string {
|
||||||
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
|
|
||||||
export default async function DbSelect<
|
export default async function DbSelect<
|
||||||
Schema extends { [k: string]: any } = { [k: string]: any },
|
Schema extends { [k: string]: any } = { [k: string]: any },
|
||||||
Table extends string = string,
|
Table extends string = string,
|
||||||
@ -42,44 +45,75 @@ export default async function DbSelect<
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlObj = sqlGenerator({
|
sqlObj = sqlGenerator({
|
||||||
tableName: table,
|
tableName: quoteIdentifier(table),
|
||||||
genObject: finalQuery,
|
genObject: finalQuery,
|
||||||
});
|
});
|
||||||
|
|
||||||
let sql = mysql.format(sqlObj.string, sqlObj.values);
|
const res = await dbHandler<Schema>({
|
||||||
|
query: sqlObj.string,
|
||||||
|
values: sqlObj.values as any,
|
||||||
|
});
|
||||||
|
|
||||||
const res = DbClient.query<Schema, Schema[]>(sql);
|
if (!res.success) {
|
||||||
const batchRes = res.all();
|
return {
|
||||||
|
success: false,
|
||||||
|
msg: "Database select failed",
|
||||||
|
debug: {
|
||||||
|
sqlObj,
|
||||||
|
sql: sqlObj.string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchRes = (res.payload || []) as Schema[];
|
||||||
|
|
||||||
let resp: APIResponseObject<Schema> = {
|
let resp: APIResponseObject<Schema> = {
|
||||||
success: Boolean(batchRes[0]),
|
success: true,
|
||||||
payload: batchRes,
|
payload: batchRes,
|
||||||
singleRes: batchRes[0],
|
singleRes: batchRes[0],
|
||||||
debug: {
|
debug: {
|
||||||
sqlObj,
|
sqlObj,
|
||||||
sql,
|
sql: sqlObj.string,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (count) {
|
if (count) {
|
||||||
let count_sql_object = sqlGenerator({
|
const countSqlObject = sqlGenerator({
|
||||||
tableName: table,
|
tableName: quoteIdentifier(table),
|
||||||
genObject: finalQuery,
|
genObject: finalQuery,
|
||||||
count,
|
count,
|
||||||
});
|
});
|
||||||
|
|
||||||
let count_sql = mysql.format(
|
const countSql = `SELECT COUNT(*) AS count FROM (${countSqlObject.string}) AS c`;
|
||||||
count_sql_object.string,
|
|
||||||
count_sql_object.values,
|
|
||||||
);
|
|
||||||
|
|
||||||
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<Schema, Schema[]>(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(*)"];
|
const countRows = countRes.payload || [];
|
||||||
resp["count"] = Number(count_val);
|
const countVal = countRows[0]?.count ?? countRows[0]?.["COUNT(*)"];
|
||||||
resp["debug"]["count_sql"] = count_sql;
|
|
||||||
|
resp = {
|
||||||
|
...resp,
|
||||||
|
count: Number(countVal),
|
||||||
|
debug: {
|
||||||
|
...resp.debug,
|
||||||
|
count_sql: countSql,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import DbClient from ".";
|
import dbHandler from "../db-handler";
|
||||||
import _ from "lodash";
|
|
||||||
import type { APIResponseObject, SQLInsertGenValueType } from "../../types";
|
import type { APIResponseObject, SQLInsertGenValueType } from "../../types";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@ -11,25 +10,45 @@ export default async function DbSQL<
|
|||||||
T extends { [k: string]: any } = { [k: string]: any },
|
T extends { [k: string]: any } = { [k: string]: any },
|
||||||
>({ sql, values }: Params): Promise<APIResponseObject<T>> {
|
>({ sql, values }: Params): Promise<APIResponseObject<T>> {
|
||||||
try {
|
try {
|
||||||
const trimmed_sql = sql.trim();
|
const trimmedSql = sql.trim();
|
||||||
|
const isSelect = trimmedSql.match(/^select/i);
|
||||||
|
|
||||||
const res = trimmed_sql.match(/^select/i)
|
const res = await dbHandler<T>({
|
||||||
? DbClient.query(trimmed_sql).all(...(values || []))
|
query: trimmedSql,
|
||||||
: DbClient.run(trimmed_sql, values || []);
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
payload: Array.isArray(res) ? (res as T[]) : undefined,
|
payload,
|
||||||
singleRes: Array.isArray(res) ? (res as T[])?.[0] : undefined,
|
singleRes,
|
||||||
postInsertReturn: Array.isArray(res)
|
postInsertReturn: isSelect
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
affectedRows: res.changes,
|
affectedRows: Number(singleRaw?.affectedRows),
|
||||||
insertId: Number(res.lastInsertRowid),
|
insertId: Number(singleRaw?.insertId),
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
sqlObj: {
|
sqlObj: {
|
||||||
sql: trimmed_sql,
|
sql: trimmedSql,
|
||||||
values,
|
values,
|
||||||
},
|
},
|
||||||
sql,
|
sql,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import DbClient from ".";
|
import dbHandler from "../db-handler";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type {
|
import type {
|
||||||
APIResponseObject,
|
APIResponseObject,
|
||||||
@ -17,6 +17,10 @@ type Params<
|
|||||||
targetId?: number | string;
|
targetId?: number | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function quoteIdentifier(identifier: string): string {
|
||||||
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
|
|
||||||
export default async function DbUpdate<
|
export default async function DbUpdate<
|
||||||
Schema extends { [k: string]: any } = { [k: string]: any },
|
Schema extends { [k: string]: any } = { [k: string]: any },
|
||||||
Table extends string = string,
|
Table extends string = string,
|
||||||
@ -45,16 +49,21 @@ export default async function DbUpdate<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sqlQueryObj = sqlGenerator({
|
const sqlQueryObj = sqlGenerator({
|
||||||
tableName: table,
|
tableName: quoteIdentifier(table),
|
||||||
genObject: finalQuery,
|
genObject: finalQuery,
|
||||||
});
|
});
|
||||||
|
|
||||||
let values: SQLInsertGenValueType[] = [];
|
|
||||||
|
|
||||||
const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0];
|
const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0];
|
||||||
|
|
||||||
if (whereClause) {
|
if (!whereClause) {
|
||||||
let sql = `UPDATE ${table} SET`;
|
return {
|
||||||
|
success: false,
|
||||||
|
msg: `No WHERE clause`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let values: SQLInsertGenValueType[] = [];
|
||||||
|
let sql = `UPDATE ${quoteIdentifier(table)} SET`;
|
||||||
|
|
||||||
const finalData: { [k: string]: SQLInsertGenValueType } = {
|
const finalData: { [k: string]: SQLInsertGenValueType } = {
|
||||||
updated_at: Date.now(),
|
updated_at: Date.now(),
|
||||||
@ -69,9 +78,8 @@ export default async function DbUpdate<
|
|||||||
|
|
||||||
const isLast = i == keys.length - 1;
|
const isLast = i == keys.length - 1;
|
||||||
|
|
||||||
sql += ` ${key}=?`;
|
sql += ` ${quoteIdentifier(key)}=?`;
|
||||||
const value = finalData[key];
|
values.push(finalData[key] ?? null);
|
||||||
values.push(value || null);
|
|
||||||
|
|
||||||
if (!isLast) {
|
if (!isLast) {
|
||||||
sql += `,`;
|
sql += `,`;
|
||||||
@ -84,24 +92,33 @@ export default async function DbUpdate<
|
|||||||
sqlObj.string = sql;
|
sqlObj.string = sql;
|
||||||
sqlObj.values = values as any[];
|
sqlObj.values = values as any[];
|
||||||
|
|
||||||
const res = DbClient.run(sql, values);
|
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 {
|
return {
|
||||||
success: Boolean(res.changes),
|
success: Boolean(singleRes?.affectedRows || singleRes?.changedRows),
|
||||||
postInsertReturn: {
|
postInsertReturn: {
|
||||||
affectedRows: res.changes,
|
affectedRows: Number(singleRes?.affectedRows),
|
||||||
insertId: Number(res.lastInsertRowid),
|
insertId: Number(singleRes?.insertId),
|
||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
sqlObj,
|
sqlObj,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
msg: `No WHERE clause`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
import _ from "lodash";
|
|
||||||
import type { BUN_MARIADB_DatabaseSchemaType } from "../../types";
|
|
||||||
|
|
||||||
export const DbSchema: BUN_MARIADB_DatabaseSchemaType = {
|
|
||||||
dbName: "travis-ai",
|
|
||||||
tables: [],
|
|
||||||
};
|
|
||||||
@ -28,18 +28,8 @@ export const UsersOmitedFields = [
|
|||||||
* database metadata.
|
* database metadata.
|
||||||
*/
|
*/
|
||||||
export interface BUN_MARIADB_DatabaseSchemaType {
|
export interface BUN_MARIADB_DatabaseSchemaType {
|
||||||
id?: string | number;
|
|
||||||
dbName?: string;
|
|
||||||
dbSlug?: string;
|
|
||||||
dbFullName?: string;
|
|
||||||
dbDescription?: string;
|
|
||||||
dbImage?: string;
|
|
||||||
tables: BUN_MARIADB_TableSchemaType[];
|
tables: BUN_MARIADB_TableSchemaType[];
|
||||||
childrenDatabases?: BUN_MARIADB_ChildrenDatabaseObject[];
|
collation?: MariaDbCollationsType;
|
||||||
childDatabase?: boolean;
|
|
||||||
childDatabaseDbId?: string | number;
|
|
||||||
updateData?: boolean;
|
|
||||||
collation?: (typeof MariaDBCollations)[number];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,6 +48,12 @@ export const MariaDBCollations = [
|
|||||||
"utf8mb4_unicode_520_ci",
|
"utf8mb4_unicode_520_ci",
|
||||||
] as const;
|
] 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,
|
* Describes a single table within a database schema, including its fields,
|
||||||
* indexes, unique constraints, and parent/child table relationships.
|
* indexes, unique constraints, and parent/child table relationships.
|
||||||
@ -1498,11 +1494,6 @@ export type SQLInsertGenParams = {
|
|||||||
*/
|
*/
|
||||||
export type BunMariaDBConfig = {
|
export type BunMariaDBConfig = {
|
||||||
db_name: string;
|
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.
|
* The Directory for backups. Relative to db_dir.
|
||||||
*/
|
*/
|
||||||
@ -1518,9 +1509,17 @@ export type BunMariaDBConfig = {
|
|||||||
*/
|
*/
|
||||||
typedef_file_path?: string;
|
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[];
|
payload?: T[];
|
||||||
single_res?: 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;
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import type {
|
|||||||
SQLInsertGenValueType,
|
SQLInsertGenValueType,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
|
function quoteIdentifier(identifier: string): string {
|
||||||
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # SQL Insert Generator
|
* # SQL Insert Generator
|
||||||
*/
|
*/
|
||||||
@ -12,7 +16,7 @@ export default function sqlInsertGenerator({
|
|||||||
data,
|
data,
|
||||||
dbFullName,
|
dbFullName,
|
||||||
}: SQLInsertGenParams): SQLInsertGenReturn | undefined {
|
}: SQLInsertGenParams): SQLInsertGenReturn | undefined {
|
||||||
const finalDbName = dbFullName ? `${dbFullName}.` : "";
|
const finalDbName = dbFullName ? `${quoteIdentifier(dbFullName)}.` : "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Array.isArray(data) && data?.[0]) {
|
if (Array.isArray(data) && data?.[0]) {
|
||||||
@ -64,9 +68,9 @@ export default function sqlInsertGenerator({
|
|||||||
.join(",")})`,
|
.join(",")})`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let query = `INSERT INTO ${finalDbName}${tableName} (${insertKeys.join(
|
|
||||||
",",
|
const insertColumns = insertKeys.map(quoteIdentifier).join(",");
|
||||||
)}) VALUES ${queryBatches.join(",")}`;
|
let query = `INSERT INTO ${finalDbName}${quoteIdentifier(tableName)} (${insertColumns}) VALUES ${queryBatches.join(",")}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query: query,
|
query: query,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user