From 62bfe308c68e7cfbcd055b1b860d5f49a421a71d Mon Sep 17 00:00:00 2001 From: stfrigerio Date: Wed, 29 Jan 2025 20:58:04 +0100 Subject: [PATCH] added codeblock "sql" to query the db --- main.ts | 23 ++++---- src/helpers/index.ts | 3 +- src/helpers/processSqlBlock.ts | 105 +++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 src/helpers/processSqlBlock.ts diff --git a/main.ts b/main.ts index 18785d2..015f0f3 100644 --- a/main.ts +++ b/main.ts @@ -8,10 +8,12 @@ import { FileSystemAdapter, Editor, MarkdownView, + MarkdownPostProcessorContext } from "obsidian"; + import { DBService } from "./src/dbService"; import { inspectTableStructure, convertEntriesInNotes } from "./src/commands"; -import { pickTableName } from "./src/helpers"; +import { pickTableName, processSqlBlock } from "./src/helpers"; import { SqliteDBSettings, DEFAULT_SETTINGS } from "./src/types"; export default class SqliteDBPlugin extends Plugin { @@ -24,14 +26,7 @@ export default class SqliteDBPlugin extends Plugin { this.dbService = new DBService(); - //! i dont think we need this since we do it in each command - // this.addCommand({ - // id: "open-db", - // name: "Open Local SQLite DB", - // callback: async () => { - // await this.openDatabase(); - // }, - // }); + await this.openDatabase(); this.addCommand({ id: "inspect-table-structure", @@ -45,7 +40,7 @@ export default class SqliteDBPlugin extends Plugin { this.addCommand({ id: "dump-table-to-notes", - name: "Dump Table Rows to Notes", + name: "Dump Table to Notes", callback: async () => { await this.openDatabase(); // ensure DB is loaded @@ -59,7 +54,13 @@ export default class SqliteDBPlugin extends Plugin { await convertEntriesInNotes(this.dbService, chosenTable, this.app); }, }); - + + this.registerMarkdownCodeBlockProcessor( + "sql", // <-- the name of your code block (```sql) + async (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => { + await processSqlBlock(this.dbService, source, el); + } + ); this.addSettingTab(new SqliteDBSettingTab(this.app, this)); } diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 171109b..69fadeb 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,3 +1,4 @@ import { pickTableName } from "./pickTableName"; +import { processSqlBlock } from "./processSqlBlock"; -export { pickTableName }; \ No newline at end of file +export { pickTableName, processSqlBlock }; diff --git a/src/helpers/processSqlBlock.ts b/src/helpers/processSqlBlock.ts new file mode 100644 index 0000000..a1f0761 --- /dev/null +++ b/src/helpers/processSqlBlock.ts @@ -0,0 +1,105 @@ +import { Notice } from "obsidian"; +import { DBService } from "../dbService"; + +/** + * Processes a code block of the form: + * + * ```sqlite + * table: MyTable + * keyColumn: id + * value: 5 + * columns: col1, col2, col3 + * ``` + * + * - If `columns` is specified, we only select those columns. + * - Otherwise, we select all columns (*). + * - If multiple rows match, we create one table per row. + */ +export async function processSqlBlock(dbService: DBService, source: string, el: HTMLElement) { + const db = dbService.getDB(); + if (!db) { + new Notice("No DB loaded. Please open the DB first."); + el.createEl("p", { text: "Database not loaded." }); + return; + } + + // 1) Parse code block params + const params = parseSqlParams(source); + const { table, keyColumn, value, columns } = params; + + if (!table || !keyColumn || !value) { + el.createEl("p", { text: "Missing required parameters (table, keyColumn, value)." }); + return; + } + + // 2) Build query based on columns + // If columns was provided, we split on commas, else use "*". + let selectCols = "*"; + if (columns) { + // e.g. "col1, col2" => "col1, col2" + const colList = columns.split(",").map((c) => c.trim()).join(", "); + selectCols = colList; + } + + const query = `SELECT ${selectCols} FROM "${table}" WHERE "${keyColumn}" = ?;`; + const result = db.exec(query, [value]); + + if (!result || result.length === 0 || result[0].values.length === 0) { + el.createEl("p", { + text: `No rows found in ${table} where ${keyColumn} = ${value}`, + }); + return; + } + + // result[0] => { columns: string[], values: any[][] } + const rowObj = result[0]; + const columnsReturned = rowObj.columns; + const rows = rowObj.values; + + // 3) For each row, create a separate table + rows.forEach((rowValues, rowIndex) => { + // Optional heading + el.createEl("h4", { text: `Row #${rowIndex + 1}` }); + + // Build the table for this row + const tableEl = el.createEl("table"); + columnsReturned.forEach((colName, colIndex) => { + const tr = tableEl.createEl("tr"); + tr.createEl("th", { text: colName }); + tr.createEl("td", { text: String(rowValues[colIndex] ?? "") }); + }); + + el.createEl("hr"); + }); +} + +/** + * Parses the code block parameters line by line for: + * table: + * keyColumn: + * value: + * columns: (e.g. "col1, col2, col3") + */ +function parseSqlParams(source: string): { + table?: string; + keyColumn?: string; + value?: string; + columns?: string; +} { + const lines = source.split("\n"); + const params: any = {}; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + const parts = trimmed.split(":").map((p) => p.trim()); + if (parts.length >= 2) { + const key = parts[0]; + const val = parts.slice(1).join(":"); // re-join in case there's a colon + if (["table", "keyColumn", "value", "columns"].includes(key)) { + params[key] = val; + } + } + } + return params; +}