mirror of
https://github.com/stfrigerio/sqliteDB.git
synced 2026-07-22 05:38:02 +00:00
improving the sqlqueryblock
This commit is contained in:
parent
904cacde9c
commit
c8539fadaf
22 changed files with 316 additions and 141 deletions
3
main.ts
3
main.ts
|
|
@ -13,7 +13,8 @@ import {
|
|||
|
||||
import { DBService } from "./src/dbService";
|
||||
import { inspectTableStructure, convertEntriesInNotes } from "./src/commands";
|
||||
import { pickTableName, processSqlBlock, processSqlChartBlock } from "./src/helpers";
|
||||
import { processSqlBlock, processSqlChartBlock } from "./src/codeblocks";
|
||||
import { pickTableName } from "./src/helpers";
|
||||
import { SqliteDBSettings, DEFAULT_SETTINGS } from "./src/types";
|
||||
|
||||
export default class SqliteDBPlugin extends Plugin {
|
||||
|
|
|
|||
4
src/codeblocks/index.ts
Normal file
4
src/codeblocks/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { processSqlBlock } from "./processSqlBlock";
|
||||
import { processSqlChartBlock } from "./processSqlChartBlock";
|
||||
|
||||
export { processSqlBlock, processSqlChartBlock };
|
||||
73
src/codeblocks/processSqlBlock/helpers/buildSqlQuery.ts
Normal file
73
src/codeblocks/processSqlBlock/helpers/buildSqlQuery.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { SqlParams } from "../types";
|
||||
|
||||
export function buildSqlQuery(params: SqlParams): { query: string; queryParams: any[] } {
|
||||
const {
|
||||
table,
|
||||
columns,
|
||||
keyColumn,
|
||||
value,
|
||||
dateColumn,
|
||||
startDate,
|
||||
endDate,
|
||||
filterColumn,
|
||||
filterValue,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
limit
|
||||
} = params;
|
||||
|
||||
const selectCols = columns
|
||||
? columns.split(",").map(c => c.trim()).join(", ")
|
||||
: "*";
|
||||
|
||||
const queryParams: any[] = [];
|
||||
const conditions: string[] = [];
|
||||
|
||||
// Add key-value condition if provided
|
||||
if (keyColumn && value !== undefined) {
|
||||
conditions.push(`"${keyColumn}" = ?`);
|
||||
queryParams.push(value);
|
||||
}
|
||||
|
||||
// Add date range conditions if provided
|
||||
if (dateColumn) {
|
||||
if (startDate) {
|
||||
conditions.push(`"${dateColumn}" >= ?`);
|
||||
queryParams.push(startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
conditions.push(`"${dateColumn}" <= ?`);
|
||||
queryParams.push(endDate);
|
||||
}
|
||||
}
|
||||
|
||||
// Add filter condition if provided
|
||||
if (filterColumn && filterValue !== undefined) {
|
||||
conditions.push(`"${filterColumn}" = ?`);
|
||||
queryParams.push(filterValue);
|
||||
}
|
||||
|
||||
// Build the query
|
||||
let query = `SELECT ${selectCols} FROM "${table}"`;
|
||||
|
||||
// Add WHERE clause if there are conditions
|
||||
if (conditions.length > 0) {
|
||||
query += ` WHERE ${conditions.join(" AND ")}`;
|
||||
}
|
||||
|
||||
// Add ORDER BY if specified
|
||||
if (orderBy) {
|
||||
query += ` ORDER BY "${orderBy}" ${orderDirection || 'ASC'}`;
|
||||
}
|
||||
|
||||
// Add LIMIT if specified
|
||||
if (limit) {
|
||||
query += ` LIMIT ?`;
|
||||
queryParams.push(limit);
|
||||
}
|
||||
|
||||
return {
|
||||
query: query + ";",
|
||||
queryParams
|
||||
};
|
||||
}
|
||||
6
src/codeblocks/processSqlBlock/helpers/index.ts
Normal file
6
src/codeblocks/processSqlBlock/helpers/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { parseSqlParams } from "./parseSqlParams";
|
||||
import { validateTable } from "./validateTable";
|
||||
import { buildSqlQuery } from "./buildSqlQuery";
|
||||
import { renderResults } from "./renderResults";
|
||||
|
||||
export { parseSqlParams, validateTable, buildSqlQuery, renderResults };
|
||||
50
src/codeblocks/processSqlBlock/helpers/parseSqlParams.ts
Normal file
50
src/codeblocks/processSqlBlock/helpers/parseSqlParams.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { SqlParams } from "../types";
|
||||
|
||||
export function parseSqlParams(source: string): SqlParams | null {
|
||||
const lines = source.split("\n");
|
||||
const params: Partial<SqlParams> = {};
|
||||
|
||||
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(":").trim(); // re-join in case there's a colon
|
||||
|
||||
switch (key) {
|
||||
case 'columns':
|
||||
params.columns = val.split(',').map(c => c.trim()).join(', ');
|
||||
break;
|
||||
case 'limit':
|
||||
const limitNum = parseInt(val);
|
||||
if (!isNaN(limitNum)) params.limit = limitNum;
|
||||
break;
|
||||
case 'orderDirection':
|
||||
if (val.toLowerCase() === 'asc' || val.toLowerCase() === 'desc') {
|
||||
params.orderDirection = val.toLowerCase() as 'asc' | 'desc';
|
||||
}
|
||||
break;
|
||||
case 'table':
|
||||
case 'keyColumn':
|
||||
case 'value':
|
||||
case 'dateColumn':
|
||||
case 'startDate':
|
||||
case 'endDate':
|
||||
case 'filterColumn':
|
||||
case 'filterValue':
|
||||
case 'orderBy':
|
||||
params[key] = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if (!params.table) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return params as SqlParams;
|
||||
}
|
||||
28
src/codeblocks/processSqlBlock/helpers/renderResults.ts
Normal file
28
src/codeblocks/processSqlBlock/helpers/renderResults.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
export function renderResults(result: { columns: string[], values: any[][] }, el: HTMLElement) {
|
||||
const { columns, values } = result;
|
||||
|
||||
// Create a container element for all rows.
|
||||
const container = el.createEl("div");
|
||||
|
||||
values.forEach(rowValues => {
|
||||
// Create a container for each row.
|
||||
const rowContainer = container.createEl("div");
|
||||
|
||||
columns.forEach((col, index) => {
|
||||
// Create a span for the column/value pair.
|
||||
const pairSpan = rowContainer.createEl("span");
|
||||
|
||||
// Append the column name in bold.
|
||||
pairSpan.createEl("strong", { text: col });
|
||||
|
||||
// Append the colon and the corresponding value.
|
||||
pairSpan.createEl("span", { text: ": " + String(rowValues[index] ?? "") });
|
||||
|
||||
// Add a line break after each column/value pair.
|
||||
rowContainer.createEl("br");
|
||||
});
|
||||
|
||||
// Optionally add an extra break between rows.
|
||||
container.createEl("br");
|
||||
});
|
||||
}
|
||||
52
src/codeblocks/processSqlBlock/helpers/validateTable.ts
Normal file
52
src/codeblocks/processSqlBlock/helpers/validateTable.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { SqlParams, ValidationError } from "../types";
|
||||
|
||||
export async function validateTable(db: any, params: SqlParams) {
|
||||
// Get table info
|
||||
const tableInfo = db.exec(`PRAGMA table_info("${params.table}")`);
|
||||
if (!tableInfo || tableInfo.length === 0) {
|
||||
return {
|
||||
message: `Table "${params.table}" does not exist.`
|
||||
};
|
||||
}
|
||||
|
||||
const availableColumns = tableInfo[0].values.map((row: any) => row[1] as string);
|
||||
|
||||
// Validate columns if specified
|
||||
if (params.columns) {
|
||||
const requestedColumns = params.columns.split(',').map(c => c.trim());
|
||||
for (const col of requestedColumns) {
|
||||
if (!availableColumns.includes(col)) {
|
||||
return {
|
||||
message: `Column "${col}" does not exist in table "${params.table}".`,
|
||||
availableColumns
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate dateColumn if specified
|
||||
if (params.dateColumn && !availableColumns.includes(params.dateColumn)) {
|
||||
return {
|
||||
message: `Date column "${params.dateColumn}" does not exist in table "${params.table}".`,
|
||||
availableColumns
|
||||
};
|
||||
}
|
||||
|
||||
// Validate filterColumn if specified
|
||||
if (params.filterColumn && !availableColumns.includes(params.filterColumn)) {
|
||||
return {
|
||||
message: `Filter column "${params.filterColumn}" does not exist in table "${params.table}".`,
|
||||
availableColumns
|
||||
};
|
||||
}
|
||||
|
||||
// Validate orderBy if specified
|
||||
if (params.orderBy && !availableColumns.includes(params.orderBy)) {
|
||||
return {
|
||||
message: `Order by column "${params.orderBy}" does not exist in table "${params.table}".`,
|
||||
availableColumns
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
80
src/codeblocks/processSqlBlock/index.ts
Normal file
80
src/codeblocks/processSqlBlock/index.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { Notice } from "obsidian";
|
||||
import { DBService } from "../../dbService";
|
||||
import { parseSqlParams, validateTable, buildSqlQuery, renderResults } from "./helpers";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const params = parseSqlParams(source);
|
||||
if (!params) {
|
||||
el.createEl("p", { text: "Missing required parameter: table" });
|
||||
el.createEl("p", { text: "Example usage:" });
|
||||
el.createEl("pre", {
|
||||
text: `table: tasks
|
||||
columns: title, status, dueDate
|
||||
dateColumn: dueDate
|
||||
startDate: 2024-01-01
|
||||
endDate: 2024-12-31
|
||||
filterColumn: status
|
||||
filterValue: active
|
||||
limit: 10
|
||||
orderBy: dueDate
|
||||
orderDirection: asc`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const validationError = await validateTable(db, params);
|
||||
if (validationError) {
|
||||
el.createEl("p", { text: validationError.message });
|
||||
if (validationError.availableColumns) {
|
||||
el.createEl("p", { text: `Available columns are: ${validationError.availableColumns.join(", ")}` });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const { query, queryParams } = buildSqlQuery(params);
|
||||
const result = db.exec(query, queryParams);
|
||||
|
||||
if (!result || result.length === 0 || result[0].values.length === 0) {
|
||||
el.createEl("p", {
|
||||
text: `No rows found in table "${params.table}"`,
|
||||
});
|
||||
|
||||
// Show the applied filters to help debug
|
||||
if (params.keyColumn && params.value) {
|
||||
el.createEl("p", { text: `Filter: ${params.keyColumn} = ${params.value}` });
|
||||
}
|
||||
if (params.dateColumn && (params.startDate || params.endDate)) {
|
||||
el.createEl("p", {
|
||||
text: `Date range: ${params.dateColumn} from ${params.startDate || 'start'} to ${params.endDate || 'end'}`
|
||||
});
|
||||
}
|
||||
if (params.filterColumn && params.filterValue) {
|
||||
el.createEl("p", { text: `Additional filter: ${params.filterColumn} = ${params.filterValue}` });
|
||||
}
|
||||
|
||||
el.createEl("p", {
|
||||
text: "Try adjusting your filters or date range.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
renderResults(result[0], el);
|
||||
|
||||
} catch (error) {
|
||||
el.createEl("p", { text: "An error occurred while processing the SQL block." });
|
||||
el.createEl("p", { text: String(error) });
|
||||
// Optionally show the query that caused the error in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const { query, queryParams } = buildSqlQuery(params);
|
||||
el.createEl("pre", { text: `Query: ${query}\nParams: ${JSON.stringify(queryParams)}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/codeblocks/processSqlBlock/types.ts
Normal file
20
src/codeblocks/processSqlBlock/types.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export interface SqlParams {
|
||||
table?: string;
|
||||
keyColumn?: string;
|
||||
value?: string;
|
||||
columns?: string;
|
||||
dateColumn?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
filterColumn?: string;
|
||||
filterValue?: string;
|
||||
orderBy?: string;
|
||||
orderDirection?: string;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
message: string;
|
||||
availableColumns?: string[];
|
||||
}
|
||||
|
||||
|
|
@ -12,7 +12,6 @@ export function createChart(type: ChartType, labels: string[], datasets: any[],
|
|||
return createBarChart(labels, datasets, options);
|
||||
case 'pie':
|
||||
return createPieChart(labels, datasets, options);
|
||||
//todo add pie and others
|
||||
default:
|
||||
return createLineChart(labels, datasets); // fallback to line chart
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import { pickTableName } from "./pickTableName";
|
||||
import { processSqlBlock } from "./processSqlBlock";
|
||||
import { processSqlChartBlock } from "./processSqlChartBlock/index";
|
||||
|
||||
export { pickTableName, processSqlBlock, processSqlChartBlock };
|
||||
export { pickTableName };
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Verify table exists
|
||||
try {
|
||||
const tableCheck = db.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name=?;`, [table]);
|
||||
if (!tableCheck || tableCheck.length === 0 || tableCheck[0].values.length === 0) {
|
||||
el.createEl("p", { text: `Table "${table}" does not exist in the database.` });
|
||||
return;
|
||||
}
|
||||
|
||||
// Get available columns for the table
|
||||
const tableInfo = db.exec(`PRAGMA table_info("${table}");`);
|
||||
const availableColumns = tableInfo[0].values.map((row: any[]) => row[1]);
|
||||
|
||||
// Verify keyColumn exists
|
||||
if (!availableColumns.includes(keyColumn)) {
|
||||
el.createEl("p", { text: `Column "${keyColumn}" does not exist in table "${table}". Available columns are: ${availableColumns.join(", ")}` });
|
||||
return;
|
||||
}
|
||||
|
||||
// Build query based on columns
|
||||
// If columns was provided, we split on commas, else use "*".
|
||||
let selectCols = "*";
|
||||
if (columns) {
|
||||
const requestedColumns = columns.split(",").map(c => c.trim());
|
||||
const invalidColumns = requestedColumns.filter(col => !availableColumns.includes(col));
|
||||
|
||||
if (invalidColumns.length > 0) {
|
||||
el.createEl("p", { text: `The following columns do not exist in table "${table}": ${invalidColumns.join(", ")}` });
|
||||
el.createEl("p", { text: `Available columns are: ${availableColumns.join(", ")}` });
|
||||
return;
|
||||
}
|
||||
selectCols = requestedColumns.join(", ");
|
||||
}
|
||||
|
||||
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 "${table}" where ${keyColumn} = ${value}`,
|
||||
});
|
||||
el.createEl("p", {
|
||||
text: "Try checking the value or using a different key column.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// result[0] => { columns: string[], values: any[][] }
|
||||
const rowObj = result[0];
|
||||
const columnsReturned = rowObj.columns;
|
||||
const rows = rowObj.values;
|
||||
|
||||
// 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");
|
||||
});
|
||||
} catch (error) {
|
||||
el.createEl("p", { text: "An error occurred while processing the SQL block." });
|
||||
el.createEl("p", { text: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the code block parameters line by line for:
|
||||
* table: <string>
|
||||
* keyColumn: <string>
|
||||
* value: <string>
|
||||
* columns: <string> (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;
|
||||
}
|
||||
Loading…
Reference in a new issue