First Commit. Based off bun-sqlite

This commit is contained in:
2026-06-21 06:18:34 +01:00
commit 9c8f6e20aa
155 changed files with 11103 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
import { Command } from "commander";
import grabDBDir from "../../utils/grab-db-dir";
import chalk from "chalk";
import { select } from "@inquirer/prompts";
import listTables from "./list-tables";
import runSQL from "./run-sql";
export default function () {
return new Command("admin")
.description("View Tables and Data, Run SQL Queries, Etc.")
.action(async () => {
const config = global.CONFIG;
const { db_file_path } = grabDBDir({ config });
console.log(chalk.bold(chalk.blue("\nBun MariaDB Admin\n")));
try {
while (true) {
const paradigm = await select({
message: "Choose an action:",
choices: [
{ name: "Tables", value: "list_tables" },
{ name: "SQL", value: "run_sql" },
{ name: chalk.dim("✕ Exit"), value: "exit" },
],
});
if (paradigm === "exit") break;
if (paradigm === "list_tables") {
const result = await listTables();
if (result === "__exit__") break;
}
if (paradigm === "run_sql") await runSQL();
}
} catch (error: any) {
console.error(error.message);
}
process.exit();
});
}
+96
View File
@@ -0,0 +1,96 @@
import chalk from "chalk";
import { select } from "@inquirer/prompts";
import { AppData } from "../../data/app-data";
import showEntries from "./show-entries";
import showFields from "./show-fields";
import dbHandler from "../../lib/db-handler";
type Params = {};
type ColumnInfo = {
cid: number;
name: string;
type: string;
notnull: number;
dflt_value: string | null;
pk: number;
};
export default async function listTables(
params?: Params,
): Promise<"__exit__" | void> {
const tables = (
await dbHandler({
query: `SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`,
})
).payload;
if (!tables?.length) {
console.log(chalk.yellow("\nNo tables found.\n"));
return;
}
// Level 1: table selection loop
while (true) {
const tableName = await select({
message: "Select a table:",
choices: [
...tables.map((t) => ({
name: t.table_name,
value: t.table_name,
})),
{ name: chalk.dim("← Go Back"), value: "__back__" },
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
],
});
if (tableName === "__back__") break;
if (tableName === "__exit__") return "__exit__";
// Level 2: action loop — stays here until "Go Back"
while (true) {
const action = await select({
message: `"${tableName}" — choose an action:`,
choices: [
{ name: "Entries", value: "entries" },
{ name: "Fields", value: "fields" },
{ name: "Schema", value: "schema" },
{ name: chalk.dim("← Go Back"), value: "__back__" },
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
],
});
if (action === "__back__") break;
if (action === "__exit__") return "__exit__";
if (action === "entries") {
const result = await showEntries({ tableName });
if (result === "__exit__") return "__exit__";
}
if (action === "fields") {
const result = await showFields({ tableName });
if (result === "__exit__") return "__exit__";
}
// if (action === "schema") {
// const columns = db
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
// .all();
// console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
// console.table(
// columns.map((c) => ({
// "#": c.cid,
// Name: c.name,
// Type: c.type,
// "Not Null": c.notnull ? "YES" : "NO",
// Default: c.dflt_value ?? "(none)",
// "Primary Key": c.pk ? "YES" : "NO",
// })),
// );
// console.log();
// }
}
}
}
+32
View File
@@ -0,0 +1,32 @@
import { input } from "@inquirer/prompts";
import chalk from "chalk";
import dbHandler from "../../lib/db-handler";
type Params = {};
export default async function runSQL(params?: Params) {
const sql = await input({
message: "Enter SQL query:",
validate: (val) => val.trim().length > 0 || "Query cannot be empty",
});
try {
const res = await dbHandler({ query: sql });
if (res.payload) {
console.log(
`\n${chalk.bold(`Result (${res.payload.length} row${res.payload.length !== 1 ? "s" : ""}):`)} \n`,
);
if (res.payload.length) console.table(res.payload);
else console.log(chalk.yellow("No res returned.\n"));
} else if (res.single_res) {
console.log(
chalk.green(
`\nSuccess! Affected rows: ${res.single_res.changes}, Last insert ID: ${res.single_res.lastInsertRowid}\n`,
),
);
}
} catch (error: any) {
console.error(chalk.red(`\nSQL Error: ${error.message}\n`));
}
}
+103
View File
@@ -0,0 +1,103 @@
import chalk from "chalk";
import { select, input } from "@inquirer/prompts";
import dbHandler from "../../lib/db-handler";
type Params = {
tableName: string;
};
type ColumnInfo = { cid: number; name: string };
const LIMIT = 50;
export default async function showEntries({ tableName }: Params) {
let page = 0;
let searchField: string | null = null;
let searchTerm: string | null = null;
while (true) {
const offset = page * LIMIT;
const rows = searchTerm
? (
await dbHandler({
query: `SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
values: [`%${searchTerm}%`],
})
).payload
: (
await dbHandler({
query: `SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`,
})
).payload;
const countRow = searchTerm
? (
await dbHandler({
query: `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`,
values: [`%${searchTerm}%`],
})
).payload
: (
await dbHandler({
query: `SELECT COUNT(*) as count FROM "${tableName}"`,
})
).payload;
const total = countRow?.[0]?.count;
const searchInfo = searchTerm
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
: "";
if (!rows) {
return;
}
console.log(
`\n${chalk.bold(tableName)} — Page ${page + 1}${searchInfo} (${rows.length} of ${total}):\n`,
);
if (rows.length) {
console.log(rows);
// if (rows.length) console.table(rows);
} else {
console.log(chalk.yellow("No rows found."));
console.log();
}
const choices: { name: string; value: string }[] = [];
if (page > 0) choices.push({ name: "← Previous Page", value: "prev" });
if (offset + rows.length < total)
choices.push({ name: "Next Page →", value: "next" });
choices.push({ name: "Search by Field", value: "search" });
if (searchTerm)
choices.push({ name: "Clear Search", value: "clear_search" });
choices.push({ name: chalk.dim("← Go Back"), value: "__back__" });
choices.push({ name: chalk.dim("✕ Exit"), value: "__exit__" });
const action = await select({ message: "Navigate:", choices });
if (action === "__back__") break;
if (action === "__exit__") return "__exit__";
if (action === "next") page++;
if (action === "prev") page--;
if (action === "clear_search") {
searchField = null;
searchTerm = null;
page = 0;
}
// if (action === "search") {
// const columns = db
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
// .all();
// searchField = await select({
// message: "Search by field:",
// choices: columns.map((c) => ({ name: c.name, value: c.name })),
// });
// searchTerm = await input({
// message: `Search term for "${searchField}":`,
// validate: (v) => v.trim().length > 0 || "Cannot be empty",
// });
// page = 0;
// }
}
}
+92
View File
@@ -0,0 +1,92 @@
type Params = {
tableName: string;
};
type ColumnInfo = {
cid: number;
name: string;
type: string;
notnull: number;
dflt_value: string | null;
pk: number;
};
type IndexInfo = { name: string; unique: number; origin: string };
type IndexColumn = { name: string };
type ForeignKey = {
id: number;
from: string;
table: string;
to: string;
on_update: string;
on_delete: string;
};
export default async function showFields({
tableName,
}: Params): Promise<"__exit__" | void> {
// const columns = db
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
// .all();
// const indexes = db
// .query<IndexInfo, []>(`PRAGMA index_list("${tableName}")`)
// .all();
// const foreignKeys = db
// .query<ForeignKey, []>(`PRAGMA foreign_key_list("${tableName}")`)
// .all();
// const indexedFields = new Map<string, { unique: boolean }>();
// for (const idx of indexes) {
// const cols = db
// .query<IndexColumn, []>(`PRAGMA index_info("${idx.name}")`)
// .all();
// for (const col of cols) {
// indexedFields.set(col.name, { unique: idx.unique === 1 });
// }
// }
// while (true) {
// const fieldName = await select({
// message: `"${tableName}" — select a field:`,
// choices: [
// ...columns.map((c) => ({ name: c.name, value: c.name })),
// { name: chalk.dim("← Go Back"), value: "__back__" },
// { name: chalk.dim("✕ Exit"), value: "__exit__" },
// ],
// });
// if (fieldName === "__back__") break;
// if (fieldName === "__exit__") return "__exit__";
// const col = columns.find((c) => c.name === fieldName)!;
// const idx = indexedFields.get(fieldName);
// const fk = foreignKeys.find((f) => f.from === fieldName);
// console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
// console.log(` ${chalk.dim("Table")} ${tableName}`);
// console.log(` ${chalk.dim("Column #")} ${col.cid}`);
// console.log(
// ` ${chalk.dim("Type")} ${col.type || chalk.italic("(none)")}`,
// );
// console.log(
// ` ${chalk.dim("Primary Key")} ${col.pk ? chalk.green("YES") : "NO"}`,
// );
// console.log(
// ` ${chalk.dim("Not Null")} ${col.notnull ? chalk.yellow("YES") : "NO"}`,
// );
// console.log(
// ` ${chalk.dim("Default")} ${col.dflt_value ?? chalk.italic("(none)")}`,
// );
// console.log(
// ` ${chalk.dim("Indexed")} ${idx ? chalk.cyan("YES") : "NO"}`,
// );
// console.log(
// ` ${chalk.dim("Unique")} ${idx?.unique ? chalk.cyan("YES") : "NO"}`,
// );
// if (fk) {
// console.log(
// ` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.table}(${fk.to})`)}`,
// );
// console.log(` ${chalk.dim("On Update")} ${fk.on_update}`);
// console.log(` ${chalk.dim("On Delete")} ${fk.on_delete}`);
// } else {
// console.log(` ${chalk.dim("Foreign Key")} NO`);
// }
// console.log();
// }
}
+28
View File
@@ -0,0 +1,28 @@
import { Command } from "commander";
import path from "path";
import grabDBDir from "../utils/grab-db-dir";
import fs from "fs";
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
import chalk from "chalk";
import trimBackups from "../utils/trim-backups";
export default function () {
return new Command("backup")
.description("Backup Database")
.action(async (opts) => {
console.log(`Backing up database ...`);
const config = global.CONFIG;
const { backup_dir, db_file_path } = grabDBDir({ config });
const new_db_file_name = grabDBBackupFileName({ config });
fs.cpSync(db_file_path, path.join(backup_dir, new_db_file_name));
trimBackups({ config });
console.log(`${chalk.bold(chalk.green(`DB Backup Success!`))}`);
process.exit();
});
}
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bun
import { program } from "commander";
import schema from "./schema";
import typedef from "./typedef";
import backup from "./backup";
import restore from "./restore";
import admin from "./admin";
import type {
BUN_MARIADB_DatabaseSchemaType,
BunMariaDBConfig,
} from "../types";
import init from "../functions/init";
/**
* # Declare Global Variables
*/
declare global {
var CONFIG: BunMariaDBConfig;
var DB_SCHEMA: BUN_MARIADB_DatabaseSchemaType;
}
await init();
if (!global.CONFIG) {
console.error(`Couldn't grab global Config.`);
process.exit(1);
}
if (!global.DB_SCHEMA) {
console.error(`Couldn't grab Database Schema.`);
process.exit(1);
}
/**
* # Describe Program
*/
program
.name(`bun-mariadb`)
.description(`MariaDB manager for Bun`)
.version(`1.0.0`);
/**
* # Declare Commands
*/
program.addCommand(schema());
program.addCommand(typedef());
program.addCommand(backup());
program.addCommand(restore());
program.addCommand(admin());
/**
* # Handle Unavailable Commands
*/
program.on("command:*", () => {
console.error(
"Invalid command: %s\nSee --help for a list of available commands.",
program.args.join(" "),
);
process.exit(1);
});
/**
* # Parse Arguments
*/
program.parse(Bun.argv);
+55
View File
@@ -0,0 +1,55 @@
import { Command } from "commander";
import grabDBDir from "../utils/grab-db-dir";
import fs from "fs";
import chalk from "chalk";
import grabSortedBackups from "../utils/grab-sorted-backups";
import { select } from "@inquirer/prompts";
import grabBackupData from "../utils/grab-backup-data";
import path from "path";
export default function () {
return new Command("restore")
.description("Restore Database")
.action(async (opts) => {
console.log(`Restoring up database ...`);
const config = global.CONFIG;
const { backup_dir, db_file_path } = grabDBDir({ config });
const backups = grabSortedBackups({ config });
if (!backups?.[0]) {
console.error(
`No Backups to restore. Use the \`backup\` command to create a backup`,
);
process.exit(1);
}
try {
const selected_backup = await select({
message: "Select a backup:",
choices: backups.map((b, i) => {
const { backup_date } = grabBackupData({
backup_name: b,
});
return {
name: `Backup #${i + 1}: ${backup_date.toDateString()} ${backup_date.getHours()}:${backup_date.getMinutes()}:${backup_date.getSeconds().toString().padStart(2, "0")}`,
value: b,
};
}),
});
fs.cpSync(path.join(backup_dir, selected_backup), db_file_path);
console.log(
`${chalk.bold(chalk.green(`DB Restore Success!`))}`,
);
process.exit();
} catch (error: any) {
console.error(`Backup Restore ERROR => ${error.message}`);
process.exit();
}
});
}
+73
View File
@@ -0,0 +1,73 @@
import { Command } from "commander";
import { MariaDBSchemaManager } from "../lib/mariadb/db-schema-manager";
import grabDirNames from "../data/grab-dir-names";
import path from "path";
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
import _ from "lodash";
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
import chalk from "chalk";
import { writeLiveSchema } from "../functions/live-schema";
import grabDBDir from "../utils/grab-db-dir";
import { cpSync } from "fs";
export default function () {
return new Command("schema")
.description("Build DB From Schema")
.option(
"-v, --vector",
"Recreate Vector Tables. This will drop and rebuild all vector tables",
)
.option("-t, --typedef", "Generate typescript type definitions")
.action(async (opts) => {
console.log(`Starting process ...`);
const config = global.CONFIG;
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);
const isTypeDef = Boolean(opts.typedef || opts.t);
const finaldbSchema = appendDefaultFieldsToDbSchema({
dbSchema,
});
const manager = new MariaDBSchemaManager({
schema: finaldbSchema,
recreate_vector_table: isVector,
});
await manager.syncSchema();
manager.close();
if (isTypeDef && config.typedef_file_path) {
const out_file = path.resolve(
ROOT_DIR,
config.typedef_file_path,
);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
});
}
writeLiveSchema({ schema: finaldbSchema });
console.log(
`${chalk.bold(chalk.green(`DB Schema setup success!`))}`,
);
process.exit();
} catch (error) {
console.log(error);
cpSync(BUN_MARIADB_TEMP_DB_FILE_PATH, db_file_path);
process.exit(1);
}
});
}
+40
View File
@@ -0,0 +1,40 @@
import { Command } from "commander";
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
import path from "path";
import grabDirNames from "../data/grab-dir-names";
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
import chalk from "chalk";
export default function () {
return new Command("typedef")
.description("Build DB From Schema")
.action(async (opts) => {
console.log(`Creating Type Definition From DB Schema ...`);
const config = global.CONFIG;
const dbSchema = global.DB_SCHEMA;
const { ROOT_DIR } = grabDirNames();
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
if (config.typedef_file_path) {
const out_file = path.resolve(
ROOT_DIR,
config.typedef_file_path,
);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
});
} else {
console.error(``);
process.exit(1);
}
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
process.exit();
});
}
+6
View File
@@ -0,0 +1,6 @@
export const AppData = {
ConfigFileName: "bun-mariadb.config.ts",
MaxBackups: 10,
DefaultBackupDirName: ".backups",
DbSchemaManagerTableName: "__db_schema_manager__",
} as const;
+28
View File
@@ -0,0 +1,28 @@
import path from "path";
import type { BunMariaDBConfig } from "../types";
type Params = {
config?: BunMariaDBConfig;
};
export default function grabDirNames(params?: Params) {
const ROOT_DIR = process.cwd();
const BUN_MARIADB_DIR = path.join(ROOT_DIR, ".bun-mariadb");
const BUN_MARIADB_TEMP_DIR = path.join(BUN_MARIADB_DIR, ".tmp");
const BUN_MARIADB_TEMP_DB_FILE_PATH = path.join(
BUN_MARIADB_TEMP_DIR,
"temp.db",
);
const BUN_MARIADB_LIVE_SCHEMA = path.join(
BUN_MARIADB_DIR,
"live-schema.json",
);
return {
ROOT_DIR,
BUN_MARIADB_DIR,
BUN_MARIADB_TEMP_DIR,
BUN_MARIADB_LIVE_SCHEMA,
BUN_MARIADB_TEMP_DB_FILE_PATH,
};
}
+73
View File
@@ -0,0 +1,73 @@
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,
} from "../types";
export default async function init(): Promise<BunMariaDBConfigReturn> {
try {
const { ROOT_DIR } = grabDirNames();
const { ConfigFileName } = AppData;
const ConfigFilePath = path.join(ROOT_DIR, ConfigFileName);
if (!fs.existsSync(ConfigFilePath)) {
console.error(
`Please create a \`${ConfigFileName}\` file at the root of your project.`,
);
process.exit(1);
}
const ConfigImport = await import(ConfigFilePath);
const Config = ConfigImport["default"] as BunMariaDBConfig;
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`);
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 DBSchemaFilePath = path.join(db_dir, Config.db_schema_file_name);
const DbSchemaImport = await import(DBSchemaFilePath);
const DbSchema = DbSchemaImport[
"default"
] as BUN_MARIADB_DatabaseSchemaType;
const backup_dir =
Config.db_backup_dir || AppData["DefaultBackupDirName"];
const BackupDir = path.resolve(db_dir, backup_dir);
if (!fs.existsSync(BackupDir)) {
fs.mkdirSync(BackupDir, { recursive: true });
}
global.CONFIG = Config;
global.DB_SCHEMA = DbSchema;
return {
config: Config,
dbSchema: DbSchema,
};
} catch (error: any) {
console.error(`Initialization ERROR => ` + error.message);
process.exit(1);
}
}
+24
View File
@@ -0,0 +1,24 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import grabDirNames from "../data/grab-dir-names";
import type { BUN_MARIADB_DatabaseSchemaType } from "../types";
import path from "path";
const { BUN_MARIADB_LIVE_SCHEMA } = grabDirNames();
type Params = {
schema: BUN_MARIADB_DatabaseSchemaType;
};
export function writeLiveSchema({ schema }: Params) {
mkdirSync(path.dirname(BUN_MARIADB_LIVE_SCHEMA), { recursive: true });
writeFileSync(BUN_MARIADB_LIVE_SCHEMA, JSON.stringify(schema));
}
export function readLiveSchema() {
if (!existsSync(BUN_MARIADB_LIVE_SCHEMA)) {
return undefined;
}
const live_schema = readFileSync(BUN_MARIADB_LIVE_SCHEMA, "utf-8");
return JSON.parse(live_schema) as BUN_MARIADB_DatabaseSchemaType;
}
+40
View File
@@ -0,0 +1,40 @@
import init from "./functions/init";
import DbDelete from "./lib/mariadb/db-delete";
import DbInsert from "./lib/mariadb/db-insert";
import DbSelect from "./lib/mariadb/db-select";
import DbSQL from "./lib/mariadb/db-sql";
import DbUpdate from "./lib/mariadb/db-update";
import type { BUN_MARIADB_DatabaseSchemaType, BunMariaDBConfig } from "./types";
import grabDbSchema from "./utils/grab-db-schema";
import grabJoinFieldsFromQueryObject from "./utils/grab-join-fields-from-query-object";
declare global {
var CONFIG: BunMariaDBConfig;
var DB_SCHEMA: BUN_MARIADB_DatabaseSchemaType;
}
await init();
if (!global.CONFIG) {
console.error(`Couldn't grab global Config.`);
process.exit(1);
}
if (!global.DB_SCHEMA) {
console.error(`Couldn't grab Database Schema.`);
process.exit(1);
}
const BunMariaDB = {
select: DbSelect,
insert: DbInsert,
update: DbUpdate,
delete: DbDelete,
sql: DbSQL,
utils: {
grab_db_schema: grabDbSchema,
grab_join_fields_from_query_object: grabJoinFieldsFromQueryObject,
},
} as const;
export default BunMariaDB;
+69
View File
@@ -0,0 +1,69 @@
import type { Connection, ConnectionConfig } from "mariadb";
import type { BUN_MARIADB_TableSchemaType, DBResponseObject } from "../types";
import grabDBConnection from "./grab-db-connection";
type Param = {
query: string;
values?: string[] | object;
noErrorLogs?: boolean;
database?: string;
tableSchema?: BUN_MARIADB_TableSchemaType;
config?: ConnectionConfig;
};
/**
* # Main DB Handler Function
* @requires DSQL_DB_CONN - Gobal Variable for Datasquirel Database
*/
export default async function dbHandler<
T extends { [k: string]: any } = { [k: string]: any },
>({
query,
values,
noErrorLogs,
database,
config,
}: Param): Promise<DBResponseObject> {
let CONNECTION: Connection | undefined;
let results: T | null = null;
try {
CONNECTION = await grabDBConnection({ database, config });
if (query && values) {
const queryResults = await CONNECTION.query(query, values);
results = queryResults[0];
} else {
const queryResults = await CONNECTION.query(query);
results = queryResults[0];
}
} catch (error: any) {
if (
error.message &&
typeof error.message == "string" &&
error.message.match(/Access denied for user.*password/i)
) {
throw new Error("Authentication Failed!");
}
if (!noErrorLogs) {
console.log(error);
}
results = null;
} finally {
await CONNECTION?.end();
}
if (results) {
return {
success: true,
payload: Array.isArray(results) ? results : undefined,
single_res: Array.isArray(results) ? undefined : results,
};
} else {
return {
success: false,
};
}
}
+20
View File
@@ -0,0 +1,20 @@
import * as mariadb from "mariadb";
import type { Connection } from "mariadb";
import grabDSQLConnectionConfig from "./grab-dsql-connection-config";
import type { DsqlConnectionParam } from "../types";
/**
* # Grab General CONNECTION for DSQL
*/
export default async function grabDBConnection(
param?: DsqlConnectionParam,
): Promise<Connection> {
const config = grabDSQLConnectionConfig(param);
try {
return await mariadb.createConnection(config);
} catch (error) {
console.log(`Error Grabbing DSQL Connection =>`, config);
throw error;
}
}
+28
View File
@@ -0,0 +1,28 @@
import fs from "fs";
import type { ConnectionConfig } from "mariadb";
import path from "path";
type Return = ConnectionConfig["ssl"] | undefined;
/**
* # Grab SSL
*/
export default function grabDbSSL(): Return {
const caProivdedPath = process.env.DSQL_SSL_CA_CERT;
if (!caProivdedPath?.match(/./)) {
return undefined;
}
const caFilePath = path.resolve(process.cwd(), caProivdedPath);
if (!fs.existsSync(caFilePath)) {
console.log(`${caFilePath} does not exist`);
return undefined;
}
return {
ca: fs.readFileSync(caFilePath),
rejectUnauthorized: false,
};
}
+35
View File
@@ -0,0 +1,35 @@
import { type ConnectionConfig } from "mariadb";
import type { DsqlConnectionParam } from "../types";
import grabDbSSL from "./grab-db-ssl";
/**
* # Grab General CONNECTION for DSQL
*/
export default function grabDSQLConnectionConfig(
param?: DsqlConnectionParam,
): ConnectionConfig {
const CONN_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)
: undefined,
charset: "utf8mb4",
ssl: grabDbSSL(),
bigIntAsNumber: true,
supportBigNumbers: true,
bigNumberStrings: false,
dateStrings: true,
metaAsArray: true,
socketTimeout: CONN_TIMEOUT,
connectTimeout: CONN_TIMEOUT,
compress: true,
...param?.config,
};
return config;
}
+40
View File
@@ -0,0 +1,40 @@
type Params = {
sql: string;
table: string;
data: any | any[];
};
export default async function ({ sql: passed_sql, table, data }: Params) {
let sql = passed_sql;
const config = global.CONFIG;
const dbSchema = global.DB_SCHEMA;
const table_schema = dbSchema.tables.find((t) => t.tableName == table);
const now = Date.now();
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}`;
}
}
}
return sql;
}
+83
View File
@@ -0,0 +1,83 @@
import DbClient from ".";
import _ from "lodash";
import type { APIResponseObject, ServerQueryParam } from "../../types";
import sqlGenerator from "../../utils/sql-generator";
type Params<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
> = {
table: Table;
query?: ServerQueryParam<Schema>;
targetId?: number | string;
};
export default async function DbDelete<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
>({
table,
query,
targetId,
}: Params<Schema, Table>): Promise<APIResponseObject> {
let sqlObj: ReturnType<typeof sqlGenerator> | null = null;
try {
let finalQuery = query || {};
if (targetId) {
finalQuery = _.merge<ServerQueryParam<any>, ServerQueryParam<any>>(
finalQuery,
{
query: {
id: {
value: String(targetId),
},
},
},
);
}
sqlObj = sqlGenerator({
tableName: 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 {
return {
success: false,
msg: `No WHERE clause`,
debug: {
sqlObj,
},
};
}
} catch (error: any) {
return {
success: false,
error: error.message,
debug: {
sqlObj,
},
};
}
}
+117
View File
@@ -0,0 +1,117 @@
import type {
BUN_MARIADB_FieldSchemaType,
BUN_MARIADB_TableSchemaType,
} from "../../types";
type Param = {
paradigm: "JavaScript" | "TypeScript" | undefined;
table: BUN_MARIADB_TableSchemaType;
query?: any;
typeDefName?: string;
allValuesOptional?: boolean;
addExport?: boolean;
dbName?: string;
};
export default function generateTypeDefinition({
paradigm,
table,
query,
typeDefName,
allValuesOptional,
addExport,
dbName,
}: Param) {
let typeDefinition: string | null = ``;
let tdName: string | null = ``;
try {
tdName = typeDefName
? typeDefName
: dbName
? `BUN_MARIADB_${dbName}_${table.tableName}`.toUpperCase()
: `BUN_MARIADB_${query.single}_${query.single_table}`.toUpperCase();
const fields = table.fields;
function typeMap(schemaType: BUN_MARIADB_FieldSchemaType) {
if (schemaType.options && schemaType.options.length > 0) {
let opts = schemaType.options.map((opt) =>
schemaType.dataType?.match(/int/i) || typeof opt == "number"
? `${opt}`
: `"${opt}"`,
);
opts.push(`""`);
return opts.join(" | ");
}
if (schemaType.dataType?.match(/blob/i)) {
return `Float32Array<ArrayBuffer> | Buffer<ArrayBuffer> | null`;
}
if (schemaType.dataType?.match(/int|double|decimal|real/i)) {
return `number | ""`;
}
if (schemaType.dataType?.match(/text|varchar|timestamp/i)) {
return `string`;
}
if (schemaType.dataType?.match(/boolean/i)) {
return "0 | 1";
}
return "string";
}
const typesArrayTypeScript = [];
const typesArrayJavascript = [];
typesArrayTypeScript.push(
`${addExport ? "export " : ""}type ${tdName} = {`,
);
typesArrayJavascript.push(`/**\n * @typedef {object} ${tdName}`);
fields.forEach((field) => {
if (field.fieldDescription) {
typesArrayTypeScript.push(
` /** \n * ${field.fieldDescription}\n */`,
);
}
const nullValue = allValuesOptional
? "?"
: field.notNullValue
? ""
: "?";
typesArrayTypeScript.push(
` ${field.fieldName}${nullValue}: ${typeMap(field)};`,
);
typesArrayJavascript.push(
` * @property {${typeMap(field)}${nullValue}} ${
field.fieldName
}`,
);
});
typesArrayTypeScript.push(`}`);
typesArrayJavascript.push(` */`);
if (paradigm?.match(/javascript/i)) {
typeDefinition = typesArrayJavascript.join("\n");
}
if (paradigm?.match(/typescript/i)) {
typeDefinition = typesArrayTypeScript.join("\n");
}
} catch (error: any) {
console.log(error.message);
typeDefinition = null;
}
return { typeDefinition, tdName };
}
+67
View File
@@ -0,0 +1,67 @@
import DbClient from ".";
import type { APIResponseObject, SQLInsertGenReturn } from "../../types";
import sqlInsertGenerator from "../../utils/sql-insert-generator";
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
type Params<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
> = {
table: Table;
data: Schema[];
update_on_duplicate?: boolean;
};
export default async function DbInsert<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
>({
table,
data,
update_on_duplicate,
}: Params<Schema, Table>): Promise<APIResponseObject> {
let sqlObj: SQLInsertGenReturn | null = null;
try {
const finalData: { [k: string]: any }[] = data.map((d) => ({
created_at: Date.now(),
updated_at: Date.now(),
...d,
}));
sqlObj =
sqlInsertGenerator({
tableName: table,
data: finalData as any[],
}) || null;
let sql = sqlObj?.query || "";
if (update_on_duplicate && data[0]) {
sql = await grabDuplicateSafeInsertSql({ data, table, sql });
}
(sqlObj || ({} as any)).query = sql;
const res = DbClient.run(sql, sqlObj?.values || []);
return {
success: Boolean(Number(res.lastInsertRowid)),
postInsertReturn: {
affectedRows: res.changes,
insertId: Number(res.lastInsertRowid),
},
debug: {
sqlObj,
},
};
} catch (error: any) {
return {
success: false,
error: error.message,
debug: {
sqlObj,
},
};
}
}
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
import _ from "lodash";
import type {
BUN_MARIADB_DatabaseSchemaType,
BunMariaDBConfig,
} from "../../types";
import generateTypeDefinition from "./db-generate-type-defs";
type Params = {
dbSchema: BUN_MARIADB_DatabaseSchemaType;
config: BunMariaDBConfig;
};
export default function dbSchemaToType({
config,
dbSchema,
}: Params): string[] | undefined {
let datasquirelSchema = dbSchema;
if (!datasquirelSchema) return;
let tableNames = `export const BunMariaDBTables = [\n${datasquirelSchema.tables
.map((tbl) => ` "${tbl.tableName}",`)
.join("\n")}\n] as const`;
const dbTablesSchemas = datasquirelSchema.tables;
const defDbName = config.db_name
?.toUpperCase()
.replace(/[^a-zA-Z0-9]/g, "_");
const defNames: string[] = [];
const schemas = dbTablesSchemas
.map((table) => {
let final_table = _.cloneDeep(table);
if (final_table.parentTableName) {
const parent_table = dbTablesSchemas.find(
(t) => t.tableName === final_table.parentTableName,
);
if (parent_table) {
final_table = _.merge(parent_table, {
tableName: final_table.tableName,
tableDescription: final_table.tableDescription,
});
}
}
const defObj = generateTypeDefinition({
paradigm: "TypeScript",
table: final_table,
typeDefName: `BUN_MARIADB_${defDbName}_${final_table.tableName.toUpperCase()}`,
allValuesOptional: true,
addExport: true,
});
if (defObj.tdName?.match(/./)) {
defNames.push(defObj.tdName);
}
return defObj.typeDefinition;
})
.filter((schm) => typeof schm == "string");
const allTd = defNames?.[0]
? `export type BUN_MARIADB_${defDbName}_ALL_TYPEDEFS = ${defNames.join(` & `)}`
: ``;
return [tableNames, ...schemas, allTd];
}
+95
View File
@@ -0,0 +1,95 @@
import mysql from "mysql";
import DbClient from ".";
import _ from "lodash";
import type { APIResponseObject, ServerQueryParam } from "../../types";
import sqlGenerator from "../../utils/sql-generator";
type Params<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
> = {
query?: ServerQueryParam<Schema>;
table: Table;
count?: boolean;
targetId?: number | string;
};
export default async function DbSelect<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
>({
table,
query,
count,
targetId,
}: Params<Schema, Table>): Promise<APIResponseObject<Schema>> {
let sqlObj: ReturnType<typeof sqlGenerator> | null = null;
try {
let finalQuery = query || {};
if (targetId) {
finalQuery = _.merge<ServerQueryParam<any>, ServerQueryParam<any>>(
finalQuery,
{
query: {
id: {
value: String(targetId),
},
},
},
);
}
sqlObj = sqlGenerator({
tableName: table,
genObject: finalQuery,
});
let sql = mysql.format(sqlObj.string, sqlObj.values);
const res = DbClient.query<Schema, Schema[]>(sql);
const batchRes = res.all();
let resp: APIResponseObject<Schema> = {
success: Boolean(batchRes[0]),
payload: batchRes,
singleRes: batchRes[0],
debug: {
sqlObj,
sql,
},
};
if (count) {
let count_sql_object = sqlGenerator({
tableName: table,
genObject: finalQuery,
count,
});
let count_sql = mysql.format(
count_sql_object.string,
count_sql_object.values,
);
count_sql = `SELECT COUNT(*) FROM (${count_sql}) as c`;
const count_res = DbClient.query<Schema, Schema[]>(count_sql).all();
const count_val = count_res[0]?.["COUNT(*)"];
resp["count"] = Number(count_val);
resp["debug"]["count_sql"] = count_sql;
}
return resp;
} catch (error: any) {
return {
success: false,
error: error.message,
debug: {
sqlObj,
},
};
}
}
+44
View File
@@ -0,0 +1,44 @@
import DbClient from ".";
import _ from "lodash";
import type { APIResponseObject, SQLInsertGenValueType } from "../../types";
type Params = {
sql: string;
values?: SQLInsertGenValueType[];
};
export default async function DbSQL<
T extends { [k: string]: any } = { [k: string]: any },
>({ sql, values }: Params): Promise<APIResponseObject<T>> {
try {
const trimmed_sql = sql.trim();
const res = trimmed_sql.match(/^select/i)
? DbClient.query(trimmed_sql).all(...(values || []))
: DbClient.run(trimmed_sql, values || []);
return {
success: true,
payload: Array.isArray(res) ? (res as T[]) : undefined,
singleRes: Array.isArray(res) ? (res as T[])?.[0] : undefined,
postInsertReturn: Array.isArray(res)
? undefined
: {
affectedRows: res.changes,
insertId: Number(res.lastInsertRowid),
},
debug: {
sqlObj: {
sql: trimmed_sql,
values,
},
sql,
},
};
} catch (error: any) {
return {
success: false,
error: error.message,
};
}
}
+114
View File
@@ -0,0 +1,114 @@
import DbClient from ".";
import _ from "lodash";
import type {
APIResponseObject,
SQLInsertGenValueType,
ServerQueryParam,
} from "../../types";
import sqlGenerator from "../../utils/sql-generator";
type Params<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
> = {
table: Table;
data: Schema;
query?: ServerQueryParam<Schema>;
targetId?: number | string;
};
export default async function DbUpdate<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
>({
table,
data,
query,
targetId,
}: Params<Schema, Table>): Promise<APIResponseObject> {
let sqlObj: ReturnType<typeof sqlGenerator> = { string: "", values: [] };
try {
let finalQuery = query || {};
if (targetId) {
finalQuery = _.merge<ServerQueryParam<any>, ServerQueryParam<any>>(
finalQuery,
{
query: {
id: {
value: String(targetId),
},
},
},
);
}
const sqlQueryObj = sqlGenerator({
tableName: 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 {
return {
success: false,
msg: `No WHERE clause`,
};
}
} catch (error: any) {
return {
success: false,
error: error.message,
debug: {
sqlObj,
},
};
}
}
+35
View File
@@ -0,0 +1,35 @@
import path from "node:path";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import type {
BUN_MARIADB_DatabaseSchemaType,
BunMariaDBConfig,
} from "../../types";
import dbSchemaToType from "./db-schema-to-typedef";
type Params = {
dbSchema: BUN_MARIADB_DatabaseSchemaType;
dst_file: string;
config: BunMariaDBConfig;
};
export default function dbSchemaToTypeDef({
dbSchema,
dst_file,
config,
}: Params) {
try {
if (!dbSchema) throw new Error("No schema found");
const definitions = dbSchemaToType({ dbSchema, config });
const ourfileDir = path.dirname(dst_file);
if (!existsSync(ourfileDir)) {
mkdirSync(ourfileDir, { recursive: true });
}
writeFileSync(dst_file, definitions?.join("\n\n") || "", "utf-8");
} catch (error: any) {
console.log(`Schema to Typedef Error =>`, error.message);
}
}
+7
View File
@@ -0,0 +1,7 @@
import _ from "lodash";
import type { BUN_MARIADB_DatabaseSchemaType } from "../../types";
export const DbSchema: BUN_MARIADB_DatabaseSchemaType = {
dbName: "travis-ai",
tables: [],
};
+1591
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
import _ from "lodash";
import { DefaultFields, type BUN_MARIADB_DatabaseSchemaType } from "../types";
type Params = {
dbSchema: BUN_MARIADB_DatabaseSchemaType;
};
export default function ({ dbSchema }: Params): BUN_MARIADB_DatabaseSchemaType {
const finaldbSchema = _.cloneDeep(dbSchema);
finaldbSchema.tables = finaldbSchema.tables.map((t) => {
const newTable = _.cloneDeep(t);
newTable.fields = newTable.fields.filter(
(f) => !f.fieldName?.match(/^(id|created_at|updated_at)$/),
);
newTable.fields.unshift(...DefaultFields);
return newTable;
});
return finaldbSchema;
}
+13
View File
@@ -0,0 +1,13 @@
type Params = {
backup_name: string;
};
export default function grabBackupData({ backup_name }: Params) {
const backup_parts = backup_name.split("-");
const backup_date_timestamp = Number(backup_parts.pop());
const origin_backup_name = backup_parts.join("-");
const backup_date = new Date(backup_date_timestamp);
return { backup_date, backup_date_timestamp, origin_backup_name };
}
+11
View File
@@ -0,0 +1,11 @@
import type { BunMariaDBConfig } from "../types";
type Params = {
config: BunMariaDBConfig;
};
export default function grabDBBackupFileName({ config }: Params) {
const new_db_file_name = `${config.db_name}-${Date.now()}`;
return new_db_file_name;
}
+26
View File
@@ -0,0 +1,26 @@
import path from "path";
import grabDirNames from "../data/grab-dir-names";
import type { BunMariaDBConfig } from "../types";
import { AppData } from "../data/app-data";
type Params = {
config: BunMariaDBConfig;
};
export default function grabDBDir({ config }: Params) {
const { ROOT_DIR } = grabDirNames();
let db_dir = ROOT_DIR;
if (config.db_dir) {
db_dir = config.db_dir;
}
const backup_dir_name =
config.db_backup_dir || AppData["DefaultBackupDirName"];
const backup_dir = path.resolve(db_dir, backup_dir_name);
const db_file_path = path.resolve(db_dir, config.db_name);
return { db_dir, backup_dir, db_file_path };
}
+6
View File
@@ -0,0 +1,6 @@
export default async function grabDbSchema() {
const config = global.CONFIG;
const dbSchema = global.DB_SCHEMA;
return dbSchema;
}
@@ -0,0 +1,94 @@
import _ from "lodash";
import type {
BunMariaDBQueryFieldValues,
ServerQueryParam,
ServerQueryParamsJoin,
} from "../types";
type Params<Q extends Record<string, any> = Record<string, any>> = {
query: ServerQueryParam<Q>;
ignore_select_fields?: boolean;
};
export default function grabJoinFieldsFromQueryObject<
Q extends Record<string, any> = Record<string, any>,
F extends string = string,
T extends string = string,
>({
query,
ignore_select_fields,
}: Params<Q>): BunMariaDBQueryFieldValues<F, T>[] {
const fields_values: BunMariaDBQueryFieldValues<F, T>[] = [];
const new_query = _.cloneDeep(query);
if (new_query.join) {
for (let i = 0; i < new_query.join.length; i++) {
const join = new_query.join[i];
if (!join) continue;
if (Array.isArray(join)) {
for (let i = 0; i < join.length; i++) {
const single_join = join[i];
fields_values.push(
...(grabSingleJoinData({
join: single_join as ServerQueryParamsJoin,
ignore_select_fields,
}) as BunMariaDBQueryFieldValues<F, T>[]),
);
}
} else {
fields_values.push(
...(grabSingleJoinData({
join: join as ServerQueryParamsJoin,
ignore_select_fields,
}) as BunMariaDBQueryFieldValues<F, T>[]),
);
}
}
}
return fields_values;
}
function grabSingleJoinData({
join,
ignore_select_fields,
}: {
join: ServerQueryParamsJoin;
ignore_select_fields?: boolean;
}): BunMariaDBQueryFieldValues[] {
let values: BunMariaDBQueryFieldValues[] = [];
const join_select_fields = join?.selectFields;
if (!join_select_fields?.[0] && !ignore_select_fields) {
throw new Error(
`\`selectFields\` required in joins. To ignore this error, pass the \`ignore_select_fields\` parameter`,
);
}
if (join_select_fields?.[0]) {
for (let i = 0; i < join_select_fields.length; i++) {
const select_field = join_select_fields[i];
if (select_field) {
values.push({
table: join.tableName,
field:
typeof select_field == "object"
? String(select_field.field)
: String(select_field),
});
}
}
}
if (join.group_concat) {
values.push({
table: join.tableName,
field: join.group_concat.field,
});
}
return values;
}
+29
View File
@@ -0,0 +1,29 @@
import grabDBDir from "../utils/grab-db-dir";
import fs from "fs";
import type { BunMariaDBConfig } from "../types";
type Params = {
config: BunMariaDBConfig;
};
export default function grabSortedBackups({ config }: Params) {
const { backup_dir } = grabDBDir({ config });
const backups = fs.readdirSync(backup_dir);
/**
* Order Backups. Most recent first.
*/
const ordered_backups = backups.sort((a, b) => {
const a_date = Number(a.split("-").pop());
const b_date = Number(b.split("-").pop());
if (a_date > b_date) {
return -1;
}
return 1;
});
return ordered_backups;
}
+33
View File
@@ -0,0 +1,33 @@
import type { QueryRawValueType, ServerQueryObjectValue } from "../types";
type Params = {
query_value: ServerQueryObjectValue;
};
export default function queryValueParser({
query_value,
}: Params): QueryRawValueType | QueryRawValueType[] {
if (typeof query_value == "string" || typeof query_value == "number") {
return query_value;
}
if (Array.isArray(query_value)) {
let values: QueryRawValueType[] = [];
for (let i = 0; i < query_value.length; i++) {
const single_value = query_value[i];
if (single_value) {
const single_parsed_value = queryValueParser({
query_value: single_value,
});
if (!Array.isArray(single_parsed_value)) {
values.push(single_parsed_value);
}
}
}
return values;
}
return query_value?.value;
}
+44
View File
@@ -0,0 +1,44 @@
import { ServerQueryEqualities } from "../types";
export default function sqlEqualityParser(
eq: (typeof ServerQueryEqualities)[number],
): string {
switch (eq) {
case "EQUAL":
return "=";
case "LIKE":
return "LIKE";
case "NOT LIKE":
return "NOT LIKE";
case "NOT EQUAL":
return "<>";
case "IS NOT":
return "IS NOT";
case "IN":
return "IN";
case "NOT IN":
return "NOT IN";
case "BETWEEN":
return "BETWEEN";
case "NOT BETWEEN":
return "NOT BETWEEN";
case "IS NULL":
return "IS NULL";
case "IS NOT NULL":
return "IS NOT NULL";
case "EXISTS":
return "EXISTS";
case "NOT EXISTS":
return "NOT EXISTS";
case "GREATER THAN":
return ">";
case "GREATER THAN OR EQUAL":
return ">=";
case "LESS THAN":
return "<";
case "LESS THAN OR EQUAL":
return "<=";
default:
return "=";
}
}
+149
View File
@@ -0,0 +1,149 @@
import type {
ServerQueryEqualities,
ServerQueryObject,
SQLInsertGenValueType,
} from "../types";
import sqlEqualityParser from "./sql-equality-parser";
type Params = {
fieldName: string;
value?: SQLInsertGenValueType;
equality?: (typeof ServerQueryEqualities)[number];
queryObj: ServerQueryObject<
{
[key: string]: any;
},
string
>;
isValueFieldValue?: boolean;
};
type Return = {
str?: string;
param?: SQLInsertGenValueType;
};
/**
* # SQL Gen Operator Gen
* @description Generates an SQL operator for node module `mysql` or `serverless-mysql`
*/
export default function sqlGenOperatorGen({
fieldName,
value,
equality,
queryObj,
isValueFieldValue,
}: Params): Return {
if (queryObj.nullValue) {
return { str: `${fieldName} IS NULL` };
}
if (queryObj.notNullValue) {
return { str: `${fieldName} IS NOT NULL` };
}
if (value) {
const finalValue = isValueFieldValue ? value : "?";
const finalParams = isValueFieldValue ? undefined : value;
if (equality == "MATCH") {
return {
str: `MATCH(${fieldName}) AGAINST(${finalValue} IN NATURAL LANGUAGE MODE)`,
param: finalParams,
};
} else if (equality == "MATCH_BOOLEAN") {
return {
str: `MATCH(${fieldName}) AGAINST(${finalValue} IN BOOLEAN MODE)`,
param: finalParams,
};
} else if (equality == "LIKE_LOWER") {
return {
str: `LOWER(${fieldName}) LIKE LOWER(${finalValue})`,
param: `%${finalParams}%`,
};
} else if (equality == "LIKE_LOWER_RAW") {
return {
str: `LOWER(${fieldName}) LIKE LOWER(${finalValue})`,
param: finalParams,
};
} else if (equality == "LIKE") {
return {
str: `${fieldName} LIKE ${finalValue}`,
param: `%${finalParams}%`,
};
} else if (equality == "LIKE_RAW") {
return {
str: `${fieldName} LIKE ${finalValue}`,
param: finalParams,
};
} else if (equality == "NOT_LIKE_LOWER") {
return {
str: `LOWER(${fieldName}) NOT LIKE LOWER(${finalValue})`,
param: `%${finalParams}%`,
};
} else if (equality == "NOT_LIKE_LOWER_RAW") {
return {
str: `LOWER(${fieldName}) NOT LIKE LOWER(${finalValue})`,
param: finalParams,
};
} else if (equality == "NOT LIKE") {
return {
str: `${fieldName} NOT LIKE ${finalValue}`,
param: finalParams,
};
} else if (equality == "NOT LIKE_RAW") {
return {
str: `${fieldName} NOT LIKE ${finalValue}`,
param: finalParams,
};
} else if (equality == "REGEXP") {
return {
str: `LOWER(${fieldName}) REGEXP LOWER(${finalValue})`,
param: finalParams,
};
} else if (equality == "FULLTEXT") {
return {
str: `MATCH(${fieldName}) AGAINST(${finalValue} IN BOOLEAN MODE)`,
param: finalParams,
};
} else if (equality == "NOT EQUAL") {
return {
str: `${fieldName} != ${finalValue}`,
param: finalParams,
};
} else if (equality == "IS NOT") {
return {
str: `${fieldName} IS NOT ${finalValue}`,
param: finalParams,
};
} else if (equality) {
return {
str: `${fieldName} ${sqlEqualityParser(
equality,
)} ${finalValue}`,
param: finalParams,
};
} else {
return {
str: `${fieldName} = ${finalValue}`,
param: finalParams,
};
}
} else {
if (equality == "IS NULL") {
return { str: `${fieldName} IS NULL` };
} else if (equality == "IS NOT NULL") {
return { str: `${fieldName} IS NOT NULL` };
} else if (equality) {
return {
str: `${fieldName} ${sqlEqualityParser(equality)} ?`,
param: value,
};
} else {
return {
str: `${fieldName} = ?`,
param: value,
};
}
}
}
+109
View File
@@ -0,0 +1,109 @@
import type {
ServerQueryParamsJoin,
ServerQueryParamsJoinMatchObject,
SQLInsertGenValueType,
} from "../types";
type Param = {
mtch: ServerQueryParamsJoinMatchObject;
join: ServerQueryParamsJoin;
table_name: string;
};
export default function sqlGenGenJoinStr({ join, mtch, table_name }: Param) {
let values: SQLInsertGenValueType[] = [];
if (mtch.__batch) {
let btch_mtch = ``;
btch_mtch += `(`;
for (let i = 0; i < mtch.__batch.matches.length; i++) {
const __mtch = mtch.__batch.matches[
i
] as ServerQueryParamsJoinMatchObject;
const { str, values: batch_values } = sqlGenGenJoinStr({
join,
mtch: __mtch,
table_name,
});
btch_mtch += str;
values.push(...batch_values);
if (i < mtch.__batch.matches.length - 1) {
btch_mtch += ` ${mtch.__batch.operator || "OR"} `;
}
}
btch_mtch += `)`;
return {
str: btch_mtch,
values,
};
}
const equality = mtch.raw_equality || "=";
const lhs = `${
typeof mtch.source == "object" ? mtch.source.tableName : table_name
}.${typeof mtch.source == "object" ? mtch.source.fieldName : mtch.source}`;
const rhs = `${(() => {
if (mtch.targetLiteral) {
values.push(mtch.targetLiteral);
// if (typeof mtch.targetLiteral == "number") {
// return `${mtch.targetLiteral}`;
// }
// return `'${mtch.targetLiteral}'`;
return `?`;
}
if (join.alias) {
return `${
typeof mtch.target == "object"
? mtch.target.tableName
: join.alias
}.${
typeof mtch.target == "object"
? mtch.target.fieldName
: mtch.target
}`;
}
return `${
typeof mtch.target == "object"
? mtch.target.tableName
: join.tableName
}.${
typeof mtch.target == "object" ? mtch.target.fieldName : mtch.target
}`;
})()}`;
if (mtch.between) {
values.push(mtch.between.min, mtch.between.max);
return {
str: `${lhs} BETWEEN ? AND ?`,
values,
};
}
if (mtch.not_between) {
values.push(mtch.not_between.min, mtch.not_between.max);
return {
str: `${lhs} NOT BETWEEN ? AND ?`,
values,
};
}
return {
str: `${lhs} ${equality} ${rhs}`,
values,
};
}
+235
View File
@@ -0,0 +1,235 @@
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";
import sqlGenGrabSelectFieldSQL from "./sql-generator-grab-select-field-sql";
type Param<T extends { [key: string]: any } = { [key: string]: any }> = {
genObject?: ServerQueryParam<T>;
selectFields?: (keyof T | TableSelectFieldsObject<T>)[];
append_table_names?: boolean;
table_name: string;
full_text_match_str?: string;
full_text_search_str?: string;
};
export default function sqlGenGenQueryStr<
T extends { [key: string]: any } = { [key: string]: any },
>(params: Param<T>) {
let str = "SELECT";
const genObject = params.genObject;
const table_name = params.table_name;
const full_text_match_str = params.full_text_match_str;
const full_text_search_str = params.full_text_search_str;
let sqlSearhValues: any[] = [];
if (genObject?.select_sql) {
str += ` ${genObject.select_sql}`;
} else if (genObject?.selectFields?.[0]) {
if (genObject.join) {
str += sqlGenGrabSelectFieldSQL<T>({
selectFields: genObject.selectFields,
append_table_names: true,
table_name,
});
} else {
str += sqlGenGrabSelectFieldSQL<T>({
selectFields: genObject.selectFields,
table_name,
});
}
} else {
if (genObject?.join) {
str += ` ${table_name}.*`;
} else {
str += " *";
}
}
if (genObject?.countSubQueries) {
let countSqls: string[] = [];
for (let i = 0; i < genObject.countSubQueries.length; i++) {
const countSubQuery = genObject.countSubQueries[i];
if (!countSubQuery) continue;
const tableAlias = countSubQuery.table_alias;
let subQStr = `(SELECT COUNT(*)`;
subQStr += ` FROM ${countSubQuery.table}${
tableAlias ? ` ${tableAlias}` : ""
}`;
subQStr += ` WHERE (`;
for (let j = 0; j < countSubQuery.srcTrgMap.length; j++) {
const csqSrc = countSubQuery.srcTrgMap[j];
if (!csqSrc) continue;
subQStr += ` ${tableAlias || countSubQuery.table}.${
csqSrc.src
}`;
if (typeof csqSrc.trg == "string") {
subQStr += ` = ?`;
sqlSearhValues.push(csqSrc.trg);
} else if (typeof csqSrc.trg == "object") {
subQStr += ` = ${csqSrc.trg.table}.${csqSrc.trg.field}`;
}
if (j < countSubQuery.srcTrgMap.length - 1) {
subQStr += ` AND `;
}
}
subQStr += ` )) AS ${countSubQuery.alias}`;
countSqls.push(subQStr);
}
str += `, ${countSqls.join(",")}`;
}
if (genObject?.join) {
const existingJoinTableNames: string[] = [table_name];
str +=
"," +
genObject.join
.flat()
.filter((j) => !isUndefined(j))
.map((joinObj) => {
const joinTableName = joinObj.alias
? joinObj.alias
: joinObj.tableName;
if (existingJoinTableNames.includes(joinTableName))
return null;
existingJoinTableNames.push(joinTableName);
if (joinObj.group_concat) {
return sqlGenGrabConcatStr({
field: `${joinTableName}.${joinObj.group_concat.field}`,
alias: joinObj.group_concat.alias,
separator: joinObj.group_concat.separator,
});
} else if (joinObj.selectFields) {
return joinObj.selectFields
.map((selectField) => {
if (typeof selectField == "string") {
return `${joinTableName}.${selectField}`;
} else if (typeof selectField == "object") {
let aliasSelectField = `${joinTableName}.${selectField.field}`;
if (selectField.count) {
aliasSelectField = `COUNT(${joinTableName}.${selectField.field})`;
} else if (selectField.sum) {
aliasSelectField = `SUM(${selectField.distinct ? "DISTINCT " : ""}${joinTableName}.${selectField.field})`;
} else if (selectField.average) {
aliasSelectField = `AVERAGE(${joinTableName}.${selectField.field})`;
} else if (selectField.max) {
aliasSelectField = `MAX(${joinTableName}.${selectField.field})`;
} else if (selectField.min) {
aliasSelectField = `MIN(${joinTableName}.${selectField.field})`;
} else if (
selectField.group_concat &&
selectField.alias
) {
return sqlGenGrabConcatStr({
field: `${joinTableName}.${selectField.field}`,
alias: selectField.alias,
separator:
selectField.group_concat
.separator,
distinct:
selectField.group_concat
.distinct,
});
} else if (selectField.distinct) {
aliasSelectField = `DISTINCT ${joinTableName}.${selectField.field}`;
}
if (selectField.alias)
aliasSelectField += ` AS ${selectField.alias}`;
return aliasSelectField;
}
})
.join(",");
} else {
return `${joinTableName}.*`;
}
})
.filter((_) => Boolean(_))
.join(",");
}
if (
genObject?.fullTextSearch &&
full_text_match_str &&
full_text_search_str
) {
str += `, ${full_text_match_str} AS ${genObject.fullTextSearch.scoreAlias}`;
sqlSearhValues.push(full_text_search_str);
}
str += ` FROM ${table_name}`;
if (genObject?.join) {
str +=
" " +
genObject.join
.flat()
.filter((j) => !isUndefined(j))
.map((join) => {
return (
join.joinType +
" " +
(join.alias
? `${join.tableName}` + " " + join.alias
: `${join.tableName}`) +
" ON " +
(() => {
if (Array.isArray(join.match)) {
return (
"(" +
join.match
.map((mtch) => {
const { str, values } =
sqlGenGenJoinStr({
mtch,
join,
table_name,
});
sqlSearhValues.push(...values);
return str;
})
.join(
join.operator
? ` ${join.operator} `
: " AND ",
) +
")"
);
} else if (typeof join.match == "object") {
const { str, values } = sqlGenGenJoinStr({
mtch: join.match,
join,
table_name,
});
sqlSearhValues.push(...values);
return str;
}
})()
);
})
.join(" ");
}
return { str, values: sqlSearhValues };
}
+123
View File
@@ -0,0 +1,123 @@
import type {
QueryRawValueType,
ServerQueryParamsJoin,
ServerQueryQueryObject,
ServerQueryValuesObject,
SQLInsertGenValueType,
} from "../types";
import sqlGenOperatorGen from "./sql-gen-operator-gen";
type Param = {
queryObj: ServerQueryQueryObject[string];
join?: (ServerQueryParamsJoin | ServerQueryParamsJoin[] | undefined)[];
field?: string;
table_name: string;
};
export default function sqlGenGenSearchStr({
queryObj,
join,
field,
table_name,
}: Param) {
let sqlSearhValues: SQLInsertGenValueType[] = [];
const finalFieldName = (() => {
if (queryObj?.tableName) {
return `${queryObj.tableName}.${field}`;
}
if (join) {
return `${table_name}.${field}`;
}
return field;
})();
let str = `${finalFieldName}=?`;
function grabValue(val?: string | number | ServerQueryValuesObject | null) {
const valueParsed = val;
if (!valueParsed) return;
const valueString =
typeof valueParsed == "string" || typeof valueParsed == "number"
? valueParsed
: valueParsed
? valueParsed.fieldName && valueParsed.tableName
? `${valueParsed.tableName}.${valueParsed.fieldName}`
: valueParsed.value
: undefined;
const valueEquality =
typeof valueParsed == "object"
? valueParsed.equality || queryObj.equality
: queryObj.equality;
const operatorStrParam = sqlGenOperatorGen({
queryObj,
equality: valueEquality,
fieldName: finalFieldName || "",
value: valueString || "",
isValueFieldValue: Boolean(
typeof valueParsed == "object" &&
valueParsed.fieldName &&
valueParsed.tableName,
),
});
return operatorStrParam;
}
if (Array.isArray(queryObj.value)) {
const strArray: string[] = [];
queryObj.value.forEach((val) => {
const operatorStrParam = grabValue(val);
if (!operatorStrParam) return;
if (operatorStrParam.str && operatorStrParam.param) {
strArray.push(operatorStrParam.str);
sqlSearhValues.push(operatorStrParam.param);
} else if (operatorStrParam.str) {
strArray.push(operatorStrParam.str);
}
});
str = "(" + strArray.join(` ${queryObj.operator || "AND"} `) + ")";
} else if (typeof queryObj.value == "object") {
const operatorStrParam = grabValue(queryObj.value);
if (operatorStrParam?.str) {
str = operatorStrParam.str;
if (operatorStrParam.param) {
sqlSearhValues.push(operatorStrParam.param);
}
}
} else if (queryObj.raw_equality && queryObj.value) {
str = `${finalFieldName} ${queryObj.raw_equality} ?`;
sqlSearhValues.push(queryObj.value);
} else if (queryObj.between) {
str = `${finalFieldName} BETWEEN ? AND ?`;
sqlSearhValues.push(queryObj.between.min, queryObj.between.max);
} else {
const valueParsed = queryObj.value ? queryObj.value : undefined;
const operatorStrParam = sqlGenOperatorGen({
equality: queryObj.equality,
fieldName: finalFieldName || "",
value: valueParsed,
queryObj,
});
if (operatorStrParam.str && operatorStrParam.param) {
str = operatorStrParam.str;
sqlSearhValues.push(operatorStrParam.param);
} else if (operatorStrParam.str && !operatorStrParam.str.match(/\?/)) {
str = operatorStrParam.str;
} else {
sqlSearhValues.push(valueParsed || "");
}
}
return { str, values: sqlSearhValues };
}
@@ -0,0 +1,30 @@
type Param = {
field: string;
alias: string;
separator?: string;
distinct?: boolean;
};
export default function sqlGenGrabConcatStr({
alias,
field,
separator = ",",
distinct,
}: Param) {
let gc = `GROUP_CONCAT(`;
if (distinct) {
gc += `DISTINCT `;
}
gc += `${field}`;
if (!distinct) {
gc += `, '${separator}'`;
}
gc += `)`;
gc += ` AS ${alias}`;
return gc;
}
@@ -0,0 +1,65 @@
import type { TableSelectFieldsObject } from "../types";
import sqlGenGrabConcatStr from "./sql-generator-grab-concat-str";
type Param<T extends { [key: string]: any } = { [key: string]: any }> = {
selectFields: (keyof T | TableSelectFieldsObject<T>)[];
append_table_names?: boolean;
table_name: string;
};
export default function sqlGenGrabSelectFieldSQL<
T extends { [key: string]: any } = { [key: string]: any },
>({ selectFields, append_table_names, table_name }: Param<T>) {
let str = "";
str += ` ${selectFields
?.map((fld) => {
let fld_str = ``;
const final_fld_name =
typeof fld == "object"
? append_table_names
? `${table_name}.${String(fld)}`
: `${String(fld.fieldName)}`
: `${String(fld)}`;
if (typeof fld == "object") {
const fld_name = `${String(fld.fieldName)}`;
if (fld.count) {
fld_str += `COUNT(${fld_name})`;
} else if (fld.sum) {
fld_str += `SUM(${fld_name})`;
} else if (fld.average) {
fld_str += `AVERAGE(${fld_name})`;
} else if (fld.max) {
fld_str += `MAX(${fld_name})`;
} else if (fld.min) {
fld_str += `MIN(${fld_name})`;
} else if (fld.distinct) {
fld_str += `DISTINCT ${fld_name}`;
} else if (fld.group_concat) {
fld_str += sqlGenGrabConcatStr({
field: fld_name,
alias: fld.group_concat.alias,
separator: fld.group_concat.separator,
distinct: fld.group_concat.distinct,
});
} else {
fld_str +=
final_fld_name + (fld.alias ? ` as ${fld.alias}` : ``);
}
if (fld.alias) {
fld_str += ` AS ${fld.alias}`;
}
} else {
fld_str += final_fld_name;
}
return fld_str;
})
.join(",")}`;
return str;
}
+378
View File
@@ -0,0 +1,378 @@
import type {
ServerQueryParam,
ServerQueryParamOrder,
SQLInsertGenValueType,
} from "../types";
import sqlGenGenSearchStr from "./sql-generator-gen-search-str";
import sqlGenGenQueryStr from "./sql-generator-gen-query-str";
type Param<T extends { [key: string]: any } = { [key: string]: any }> = {
genObject?: ServerQueryParam<T>;
tableName: string;
dbFullName?: string;
count?: boolean;
};
type Return = {
string: string;
values: SQLInsertGenValueType[];
};
/**
* # SQL Query Generator
* @description Generates an SQL Query for node module `mysql` or `serverless-mysql`
*/
export default function sqlGenerator<
T extends { [key: string]: any } = { [key: string]: any },
>({ tableName, genObject, dbFullName, count }: Param<T>): Return {
const finalQuery = genObject?.query ? genObject.query : undefined;
const queryKeys = finalQuery ? Object.keys(finalQuery) : undefined;
const sqlSearhValues: SQLInsertGenValueType[] = [];
let fullTextMatchStr = genObject?.fullTextSearch
? ` MATCH(${genObject.fullTextSearch.fields
.map((f) =>
genObject.join ? `${tableName}.${String(f)}` : `${String(f)}`,
)
.join(",")}) AGAINST (? IN BOOLEAN MODE)`
: undefined;
const fullTextSearchStr = genObject?.fullTextSearch
? genObject.fullTextSearch.searchTerm
.split(` `)
.map((t) => `${t}`)
.join(" ")
: undefined;
let { str: queryString, values } = sqlGenGenQueryStr<T>({
table_name: tableName,
append_table_names: true,
full_text_match_str: fullTextMatchStr,
full_text_search_str: fullTextSearchStr,
genObject,
});
sqlSearhValues.push(...values);
const sqlSearhString = queryKeys?.map((field) => {
const queryObj = finalQuery?.[field];
if (!queryObj) return;
if (queryObj.__query) {
const subQueryGroup = queryObj.__query;
const subSearchKeys = Object.keys(subQueryGroup);
const subSearchString = subSearchKeys.map((_field) => {
const newSubQueryObj = subQueryGroup?.[_field];
if (newSubQueryObj) {
const { str, values } = sqlGenGenSearchStr({
queryObj: newSubQueryObj,
field: newSubQueryObj.fieldName || _field,
join: genObject?.join,
table_name: tableName,
});
sqlSearhValues.push(...values);
return str;
}
});
return (
"(" +
subSearchString.join(` ${queryObj.operator || "AND"} `) +
")"
);
}
const { str, values } = sqlGenGenSearchStr({
queryObj,
field: queryObj.fieldName || field,
join: genObject?.join,
table_name: tableName,
});
sqlSearhValues.push(...values);
return str;
});
const cleanedUpSearchStr = sqlSearhString?.filter(
(str) => typeof str == "string",
);
const isSearchStr =
cleanedUpSearchStr?.[0] && cleanedUpSearchStr.find((str) => str);
if (isSearchStr) {
const stringOperator = genObject?.searchOperator || "AND";
queryString += ` WHERE ${cleanedUpSearchStr.join(
` ${stringOperator} `,
)}`;
}
if (genObject?.fullTextSearch && fullTextSearchStr && fullTextMatchStr) {
queryString += `${isSearchStr ? " AND" : " WHERE"} ${fullTextMatchStr}`;
sqlSearhValues.push(fullTextSearchStr);
}
if (genObject?.group) {
let group_by_txt = ``;
if (typeof genObject.group == "string") {
group_by_txt = genObject.group;
} else if (Array.isArray(genObject.group)) {
for (let i = 0; i < genObject.group.length; i++) {
const group = genObject.group[i];
if (typeof group == "string") {
group_by_txt += `\`${group.toString()}\``;
} else if (typeof group == "object" && group.table) {
group_by_txt += `${group.table}.${String(group.field)}`;
} else if (typeof group == "object") {
group_by_txt += `${String(group.field)}`;
}
if (i < genObject.group.length - 1) {
group_by_txt += ",";
}
}
} else if (typeof genObject.group == "object") {
if (genObject.group.table) {
group_by_txt = `${genObject.group.table}.${String(genObject.group.field)}`;
} else {
group_by_txt = `${String(genObject.group.field)}`;
}
}
queryString += ` GROUP BY ${group_by_txt}`;
}
function grabOrderString(order: ServerQueryParamOrder<T>) {
let orderFields = [];
let orderSrt = ``;
if (genObject?.fullTextSearch && genObject.fullTextSearch.scoreAlias) {
orderFields.push(genObject.fullTextSearch.scoreAlias);
} else if (genObject?.join) {
orderFields.push(`${tableName}.${String(order.field)}`);
} else {
orderFields.push(order.field);
}
orderSrt += ` ${orderFields.join(", ")} ${order.strategy}`;
return orderSrt;
}
if (genObject?.order) {
let orderSrt = ` ORDER BY`;
if (Array.isArray(genObject.order)) {
for (let i = 0; i < genObject.order.length; i++) {
const order = genObject.order[i];
if (order) {
orderSrt +=
grabOrderString(order) +
(i < genObject.order.length - 1 ? `,` : "");
}
}
} else {
orderSrt += grabOrderString(genObject.order);
}
queryString += ` ${orderSrt}`;
}
if (genObject?.limit && !count) queryString += ` LIMIT ${genObject.limit}`;
if (genObject?.offset) {
queryString += ` OFFSET ${genObject.offset}`;
} else if (genObject?.page && genObject.limit && !count) {
queryString += ` OFFSET ${(genObject.page - 1) * genObject.limit}`;
}
return {
string: queryString,
values: sqlSearhValues,
};
}
// let queryString = (() => {
// let str = "SELECT";
// if (genObject?.select_sql) {
// str += ` ${genObject.select_sql}`;
// } else if (genObject?.selectFields?.[0]) {
// if (genObject.join) {
// str += sqlGenGrabSelectFieldSQL<T>({
// selectFields: genObject.selectFields,
// append_table_names: true,
// table_name: tableName,
// });
// } else {
// str += sqlGenGrabSelectFieldSQL({
// selectFields: genObject.selectFields,
// table_name: tableName,
// });
// }
// } else {
// if (genObject?.join) {
// str += ` ${tableName}.*`;
// } else {
// str += " *";
// }
// }
// if (genObject?.countSubQueries) {
// let countSqls: string[] = [];
// for (let i = 0; i < genObject.countSubQueries.length; i++) {
// const countSubQuery = genObject.countSubQueries[i];
// if (!countSubQuery) continue;
// const tableAlias = countSubQuery.table_alias;
// let subQStr = `(SELECT COUNT(*)`;
// subQStr += ` FROM ${countSubQuery.table}${
// tableAlias ? ` ${tableAlias}` : ""
// }`;
// subQStr += ` WHERE (`;
// for (let j = 0; j < countSubQuery.srcTrgMap.length; j++) {
// const csqSrc = countSubQuery.srcTrgMap[j];
// if (!csqSrc) continue;
// subQStr += ` ${tableAlias || countSubQuery.table}.${
// csqSrc.src
// }`;
// if (typeof csqSrc.trg == "string") {
// subQStr += ` = ?`;
// sqlSearhValues.push(csqSrc.trg);
// } else if (typeof csqSrc.trg == "object") {
// subQStr += ` = ${csqSrc.trg.table}.${csqSrc.trg.field}`;
// }
// if (j < countSubQuery.srcTrgMap.length - 1) {
// subQStr += ` AND `;
// }
// }
// subQStr += ` )) AS ${countSubQuery.alias}`;
// countSqls.push(subQStr);
// }
// str += `, ${countSqls.join(",")}`;
// }
// if (genObject?.join) {
// const existingJoinTableNames: string[] = [tableName];
// str +=
// "," +
// genObject.join
// .flat()
// .filter((j) => !isUndefined(j))
// .map((joinObj) => {
// const joinTableName = joinObj.alias
// ? joinObj.alias
// : joinObj.tableName;
// if (existingJoinTableNames.includes(joinTableName))
// return null;
// existingJoinTableNames.push(joinTableName);
// if (joinObj.group_concat) {
// return sqlGenGrabConcatStr({
// field: `${joinTableName}.${joinObj.group_concat.field}`,
// alias: joinObj.group_concat.alias,
// separator: joinObj.group_concat.separator,
// });
// } else if (joinObj.selectFields) {
// return joinObj.selectFields
// .map((selectField) => {
// if (typeof selectField == "string") {
// return `${joinTableName}.${selectField}`;
// } else if (typeof selectField == "object") {
// let aliasSelectField = selectField.count
// ? `COUNT(${joinTableName}.${selectField.field})`
// : `${joinTableName}.${selectField.field}`;
// if (selectField.alias)
// aliasSelectField += ` AS ${selectField.alias}`;
// return aliasSelectField;
// }
// })
// .join(",");
// } else {
// return `${joinTableName}.*`;
// }
// })
// .filter((_) => Boolean(_))
// .join(",");
// }
// if (
// genObject?.fullTextSearch &&
// fullTextMatchStr &&
// fullTextSearchStr
// ) {
// str += `, ${fullTextMatchStr} AS ${genObject.fullTextSearch.scoreAlias}`;
// sqlSearhValues.push(fullTextSearchStr);
// }
// str += ` FROM ${tableName}`;
// if (genObject?.join) {
// str +=
// " " +
// genObject.join
// .flat()
// .filter((j) => !isUndefined(j))
// .map((join) => {
// return (
// join.joinType +
// " " +
// (join.alias
// ? `${join.tableName}` + " " + join.alias
// : `${join.tableName}`) +
// " ON " +
// (() => {
// if (Array.isArray(join.match)) {
// return (
// "(" +
// join.match
// .map((mtch) =>
// sqlGenGenJoinStr({
// mtch,
// join,
// table_name: tableName,
// }),
// )
// .join(
// join.operator
// ? ` ${join.operator} `
// : " AND ",
// ) +
// ")"
// );
// } else if (typeof join.match == "object") {
// return sqlGenGenJoinStr({
// mtch: join.match,
// join,
// table_name: tableName,
// });
// }
// })()
// );
// })
// .join(" ");
// }
// return str;
// })();
+82
View File
@@ -0,0 +1,82 @@
import type {
SQLInsertGenParams,
SQLInsertGenReturn,
SQLInsertGenValueType,
} from "../types";
/**
* # SQL Insert Generator
*/
export default function sqlInsertGenerator({
tableName,
data,
dbFullName,
}: SQLInsertGenParams): SQLInsertGenReturn | undefined {
const finalDbName = dbFullName ? `${dbFullName}.` : "";
try {
if (Array.isArray(data) && data?.[0]) {
let insertKeys: string[] = [];
data.forEach((dt) => {
const kys = Object.keys(dt);
kys.forEach((ky) => {
if (!insertKeys.includes(ky)) {
insertKeys.push(ky);
}
});
});
let queryBatches: string[] = [];
let queryValues: SQLInsertGenValueType[] = [];
data.forEach((item) => {
queryBatches.push(
`(${insertKeys
.map((ky) => {
const value = item[ky];
const finalValue =
typeof value == "string" ||
typeof value == "number"
? value
: typeof value == "function"
? value().value
: value
? value
: null;
if (!finalValue) {
queryValues.push(null);
return "?";
}
queryValues.push(finalValue);
const placeholder =
typeof value == "function"
? value().placeholder
: "?";
return placeholder;
})
.filter((k) => Boolean(k))
.join(",")})`,
);
});
let query = `INSERT INTO ${finalDbName}${tableName} (${insertKeys.join(
",",
)}) VALUES ${queryBatches.join(",")}`;
return {
query: query,
values: queryValues,
};
} else {
return undefined;
}
} catch (/** @type {any} */ error: any) {
console.log(`SQL insert gen ERROR: ${error.message}`);
return undefined;
}
}
+27
View File
@@ -0,0 +1,27 @@
import grabDBDir from "../utils/grab-db-dir";
import fs from "fs";
import type { BunMariaDBConfig } from "../types";
import grabSortedBackups from "./grab-sorted-backups";
import { AppData } from "../data/app-data";
import path from "path";
type Params = {
config: BunMariaDBConfig;
};
export default function trimBackups({ config }: Params) {
const { backup_dir } = grabDBDir({ config });
const backups = grabSortedBackups({ config });
const max_backups = config.max_backups || AppData["MaxBackups"];
for (let i = 0; i < backups.length; i++) {
const backup_name = backups[i];
if (!backup_name) continue;
if (i > max_backups - 1) {
const backup_file_to_unlink = path.join(backup_dir, backup_name);
fs.unlinkSync(backup_file_to_unlink);
}
}
}