mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
fix: fixing issue with config and extra logs (#209)
This commit is contained in:
parent
c038366783
commit
33e19df3db
8 changed files with 172 additions and 94 deletions
5
.changeset/poor-foxes-fall.md
Normal file
5
.changeset/poor-foxes-fall.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"sqlseal": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fixing issue with config table not found on first run
|
||||||
|
|
@ -2,8 +2,6 @@ import { App } from "obsidian";
|
||||||
import { DatabaseProvider } from "./sqlocal";
|
import { DatabaseProvider } from "./sqlocal";
|
||||||
|
|
||||||
export const databaseFactory = async (app: App, provider: DatabaseProvider) => {
|
export const databaseFactory = async (app: App, provider: DatabaseProvider) => {
|
||||||
console.log('#### DATABASE CREATION')
|
|
||||||
|
|
||||||
const db = await provider.get(null)
|
const db = await provider.get(null)
|
||||||
await db.connect()
|
await db.connect()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,55 @@ import { sanitise } from "../../../utils/sanitiseColumn";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import wasmUrl from 'virtual:wa-sqlite-wasm-url';
|
import wasmUrl from 'virtual:wa-sqlite-wasm-url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry an async operation with exponential backoff
|
||||||
|
* @param operation - The async operation to retry
|
||||||
|
* @param maxRetries - Maximum number of retry attempts (default: 3)
|
||||||
|
* @param baseDelay - Base delay in milliseconds for exponential backoff (default: 50)
|
||||||
|
* @param errorMatcher - Optional function to determine if error should trigger retry
|
||||||
|
* @returns The result of the successful operation
|
||||||
|
* @throws The last error if all retries fail
|
||||||
|
*/
|
||||||
|
async function retryWithBackoff<T>(
|
||||||
|
operation: () => Promise<T>,
|
||||||
|
maxRetries: number = 3,
|
||||||
|
baseDelay: number = 50,
|
||||||
|
errorMatcher?: (error: Error) => boolean
|
||||||
|
): Promise<T> {
|
||||||
|
let lastError: Error;
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
return await operation();
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error;
|
||||||
|
|
||||||
|
// If errorMatcher provided, only retry matching errors
|
||||||
|
if (errorMatcher && !errorMatcher(error as Error)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't retry on last attempt
|
||||||
|
if (attempt === maxRetries) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate delay with exponential backoff
|
||||||
|
const delay = baseDelay * Math.pow(2, attempt - 1);
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn(
|
||||||
|
`SqlocalWorkerDatabase: Retry attempt ${attempt}/${maxRetries} ` +
|
||||||
|
`after error: ${lastError.message}. Waiting ${delay}ms...`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError!;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Worker-side database implementation that runs wa-sqlite operations
|
* Worker-side database implementation that runs wa-sqlite operations
|
||||||
* in a Web Worker to avoid blocking the main thread.
|
* in a Web Worker to avoid blocking the main thread.
|
||||||
|
|
@ -147,7 +196,9 @@ export class SqlocalWorkerDatabase {
|
||||||
|
|
||||||
registerCustomFunction(name: string, argsCount = 1) {
|
registerCustomFunction(name: string, argsCount = 1) {
|
||||||
if (!this.connection) {
|
if (!this.connection) {
|
||||||
console.warn('SqlocalWorkerDatabase: Database not connected, cannot register custom function');
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('SqlocalWorkerDatabase: Database not connected, cannot register custom function');
|
||||||
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,94 +575,107 @@ export class SqlocalWorkerDatabase {
|
||||||
async select(statement: string, frontmatter: Record<string, unknown>) {
|
async select(statement: string, frontmatter: Record<string, unknown>) {
|
||||||
if (!this.connection) throw new Error('Database not connected');
|
if (!this.connection) throw new Error('Database not connected');
|
||||||
if (this.isRecreating) {
|
if (this.isRecreating) {
|
||||||
console.warn('SqlocalWorkerDatabase: Database is being recreated, cannot execute select');
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('SqlocalWorkerDatabase: Database is being recreated, cannot execute select');
|
||||||
|
}
|
||||||
return { data: [], columns: [], executionTime: 0 };
|
return { data: [], columns: [], executionTime: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Wrap the select operation with retry logic
|
||||||
// Replace frontmatter placeholders in the query
|
return retryWithBackoff(
|
||||||
let processedStatement = statement;
|
async () => {
|
||||||
const params: any[] = [];
|
try {
|
||||||
|
// Replace frontmatter placeholders in the query
|
||||||
|
let processedStatement = statement;
|
||||||
|
const params: any[] = [];
|
||||||
|
|
||||||
// Support both {{key}} and @key parameter formats for compatibility
|
// Support both {{key}} and @key parameter formats for compatibility
|
||||||
for (const [key, value] of Object.entries(frontmatter)) {
|
for (const [key, value] of Object.entries(frontmatter)) {
|
||||||
// Handle {{key}} format (used in user queries)
|
// Handle {{key}} format (used in user queries)
|
||||||
const doubleBracePlaceholder = `{{${key}}}`;
|
const doubleBracePlaceholder = `{{${key}}}`;
|
||||||
const doubleBraceRegex = new RegExp(doubleBracePlaceholder.replace(/[{}]/g, '\\$&'), 'g');
|
const doubleBraceRegex = new RegExp(doubleBracePlaceholder.replace(/[{}]/g, '\\$&'), 'g');
|
||||||
const doubleBraceMatches = (processedStatement.match(doubleBraceRegex) || []).length;
|
const doubleBraceMatches = (processedStatement.match(doubleBraceRegex) || []).length;
|
||||||
if (doubleBraceMatches > 0) {
|
if (doubleBraceMatches > 0) {
|
||||||
processedStatement = processedStatement.replace(doubleBraceRegex, '?');
|
processedStatement = processedStatement.replace(doubleBraceRegex, '?');
|
||||||
for (let i = 0; i < doubleBraceMatches; i++) {
|
for (let i = 0; i < doubleBraceMatches; i++) {
|
||||||
params.push(value);
|
params.push(value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Handle @key format (used in repository queries)
|
|
||||||
const atPlaceholder = `@${key}`;
|
|
||||||
const atRegex = new RegExp(`@${key}\\b`, 'g');
|
|
||||||
const atMatches = (processedStatement.match(atRegex) || []).length;
|
|
||||||
if (atMatches > 0) {
|
|
||||||
processedStatement = processedStatement.replace(atRegex, '?');
|
|
||||||
for (let i = 0; i < atMatches; i++) {
|
|
||||||
params.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
const data: any[] = [];
|
|
||||||
let columns: string[] = [];
|
|
||||||
|
|
||||||
// Create string in WASM memory
|
|
||||||
const str = this.sqlite3.str_new(this.connection, processedStatement);
|
|
||||||
let prepared = null;
|
|
||||||
try {
|
|
||||||
prepared = await this.sqlite3.prepare_v2(this.connection, this.sqlite3.str_value(str));
|
|
||||||
|
|
||||||
if (prepared && prepared.stmt) {
|
|
||||||
await this.sqlite3.bind_collection(prepared.stmt, params);
|
|
||||||
|
|
||||||
// Get column names
|
|
||||||
const columnCount = await this.sqlite3.column_count(prepared.stmt);
|
|
||||||
|
|
||||||
for (let i = 0; i < columnCount; i++) {
|
|
||||||
columns.push(await this.sqlite3.column_name(prepared.stmt, i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all rows
|
|
||||||
let stepResult;
|
|
||||||
while ((stepResult = await this.sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) {
|
|
||||||
const row: any = {};
|
|
||||||
for (let i = 0; i < columnCount; i++) {
|
|
||||||
const columnName = columns[i];
|
|
||||||
row[columnName] = await this.sqlite3.column(prepared.stmt, i);
|
|
||||||
}
|
}
|
||||||
data.push(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
// Finalize statement before finishing string
|
|
||||||
if (prepared && prepared.stmt) {
|
|
||||||
try {
|
|
||||||
await this.sqlite3.finalize(prepared.stmt);
|
|
||||||
} catch (finalizeError) {
|
|
||||||
console.error('SqlocalWorkerDatabase: Error finalizing statement:', finalizeError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.sqlite3.str_finish(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
const executionTime = performance.now() - startTime;
|
// Handle @key format (used in repository queries)
|
||||||
return {
|
const atPlaceholder = `@${key}`;
|
||||||
data,
|
const atRegex = new RegExp(`@${key}\\b`, 'g');
|
||||||
columns,
|
const atMatches = (processedStatement.match(atRegex) || []).length;
|
||||||
executionTime
|
if (atMatches > 0) {
|
||||||
};
|
processedStatement = processedStatement.replace(atRegex, '?');
|
||||||
} catch (error) {
|
for (let i = 0; i < atMatches; i++) {
|
||||||
console.error('SqlocalWorkerDatabase: Error executing select:', error);
|
params.push(value);
|
||||||
console.error('SqlocalWorkerDatabase: Failed statement:', statement);
|
}
|
||||||
throw error;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
const data: any[] = [];
|
||||||
|
let columns: string[] = [];
|
||||||
|
|
||||||
|
// Create string in WASM memory
|
||||||
|
const str = this.sqlite3.str_new(this.connection, processedStatement);
|
||||||
|
let prepared = null;
|
||||||
|
try {
|
||||||
|
prepared = await this.sqlite3.prepare_v2(this.connection, this.sqlite3.str_value(str));
|
||||||
|
|
||||||
|
if (prepared && prepared.stmt) {
|
||||||
|
await this.sqlite3.bind_collection(prepared.stmt, params);
|
||||||
|
|
||||||
|
// Get column names
|
||||||
|
const columnCount = await this.sqlite3.column_count(prepared.stmt);
|
||||||
|
|
||||||
|
for (let i = 0; i < columnCount; i++) {
|
||||||
|
columns.push(await this.sqlite3.column_name(prepared.stmt, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all rows
|
||||||
|
let stepResult;
|
||||||
|
while ((stepResult = await this.sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) {
|
||||||
|
const row: any = {};
|
||||||
|
for (let i = 0; i < columnCount; i++) {
|
||||||
|
const columnName = columns[i];
|
||||||
|
row[columnName] = await this.sqlite3.column(prepared.stmt, i);
|
||||||
|
}
|
||||||
|
data.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Finalize statement before finishing string
|
||||||
|
if (prepared && prepared.stmt) {
|
||||||
|
try {
|
||||||
|
await this.sqlite3.finalize(prepared.stmt);
|
||||||
|
} catch (finalizeError) {
|
||||||
|
console.error('SqlocalWorkerDatabase: Error finalizing statement:', finalizeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.sqlite3.str_finish(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionTime = performance.now() - startTime;
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
executionTime
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('SqlocalWorkerDatabase: Error executing select:', error);
|
||||||
|
console.error('SqlocalWorkerDatabase: Failed statement:', statement);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
3, // maxRetries
|
||||||
|
50, // baseDelay (50ms, 100ms, 200ms pattern)
|
||||||
|
(error: Error) => {
|
||||||
|
// Only retry on "no such table" errors
|
||||||
|
return error.message.toLowerCase().includes('no such table');
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async explain(statement: string, frontmatter: Record<string, unknown>) {
|
async explain(statement: string, frontmatter: Record<string, unknown>) {
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,5 @@ export const mainInit = (
|
||||||
apiInit();
|
apiInit();
|
||||||
globalTablesInit();
|
globalTablesInit();
|
||||||
explorerInit();
|
explorerInit();
|
||||||
|
|
||||||
console.log('🚀 SQL Seal initialized with wa-sqlite test command available');
|
|
||||||
console.log('📋 Use Ctrl/Cmd+P -> "Test wa-sqlite Implementation" to test wa-sqlite');
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ const obsidian = new Registrator(process.env.NODE_ENV === 'development' ? { logg
|
||||||
.export('app', 'plugin', 'vault')
|
.export('app', 'plugin', 'vault')
|
||||||
|
|
||||||
|
|
||||||
export const mainModule = new Registrator({logger: console.log})
|
export const mainModule = new Registrator(process.env.NODE_ENV === 'development' ? {logger: console.log} : undefined)
|
||||||
.module('obsidian', obsidian)
|
.module('obsidian', obsidian)
|
||||||
.module('db', db)
|
.module('db', db)
|
||||||
.module('editor', editor)
|
.module('editor', editor)
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ export class Sync {
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
this.configRepo = new ConfigurationRepository(this.db)
|
this.configRepo = new ConfigurationRepository(this.db)
|
||||||
|
await this.configRepo.init()
|
||||||
|
|
||||||
let version
|
let version
|
||||||
try {
|
try {
|
||||||
|
|
@ -78,10 +79,9 @@ export class Sync {
|
||||||
|
|
||||||
if (version < SQLSEAL_DATABASE_VERSION) {
|
if (version < SQLSEAL_DATABASE_VERSION) {
|
||||||
await this.db.recreateDatabase()
|
await this.db.recreateDatabase()
|
||||||
|
await this.configRepo.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.configRepo.init()
|
|
||||||
|
|
||||||
this.tableDefinitionsRepo = new TableDefinitionsRepository(this.db)
|
this.tableDefinitionsRepo = new TableDefinitionsRepository(this.db)
|
||||||
await this.tableDefinitionsRepo.init()
|
await this.tableDefinitionsRepo.init()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,6 @@ export class ModernCellParser {
|
||||||
|
|
||||||
// FIXME: this should be extracted to separate class / function but for now it's fine.
|
// FIXME: this should be extracted to separate class / function but for now it's fine.
|
||||||
registerDbFunctions(db: SqlocalDatabaseProxy) {
|
registerDbFunctions(db: SqlocalDatabaseProxy) {
|
||||||
console.trace('register db functions called')
|
|
||||||
this.functions.forEach(funct => {
|
this.functions.forEach(funct => {
|
||||||
db.registerCustomFunction(funct.name, funct.sqlFunctionArgumentsCount)
|
db.registerCustomFunction(funct.name, funct.sqlFunctionArgumentsCount)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@ export class Logger {
|
||||||
} else {
|
} else {
|
||||||
this.console = {
|
this.console = {
|
||||||
log: () => { },
|
log: () => { },
|
||||||
error: () => { }
|
error: () => { },
|
||||||
|
warn: () => { },
|
||||||
|
debug: () => { },
|
||||||
|
trace: () => { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private console: Pick<typeof console, 'log' | 'error'>
|
private console: Pick<typeof console, 'log' | 'error' | 'warn' | 'debug' | 'trace'>
|
||||||
|
|
||||||
log(...args: any[]) {
|
log(...args: any[]) {
|
||||||
this.console.log(...args)
|
this.console.log(...args)
|
||||||
|
|
@ -19,4 +22,16 @@ export class Logger {
|
||||||
error(...args: any[]) {
|
error(...args: any[]) {
|
||||||
this.console.error(...args)
|
this.console.error(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn(...args: any[]) {
|
||||||
|
this.console.warn(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(...args: any[]) {
|
||||||
|
this.console.debug(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
trace(...args: any[]) {
|
||||||
|
this.console.trace(...args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue