mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
feat: adding flags and ability to run explain
This commit is contained in:
parent
790670205e
commit
3363316928
6 changed files with 187 additions and 27 deletions
|
|
@ -9,11 +9,15 @@ import { transformQuery } from "../sql/sqlTransformer";
|
|||
import { displayError, displayNotice } from "../utils/ui";
|
||||
import SqlSealPlugin from "../main";
|
||||
import { registerObservers } from "../utils/registerObservers";
|
||||
import { IntermediateContent, parseIntermediateContent } from "src/grammar/parseIntermediateContent";
|
||||
|
||||
export class CodeblockProcessor extends MarkdownRenderChild {
|
||||
|
||||
registrator: OmnibusRegistrator
|
||||
renderer: RenderReturn
|
||||
private flags: IntermediateContent['flags']
|
||||
private extrasEl: HTMLElement
|
||||
private explainEl: HTMLElement
|
||||
|
||||
constructor(
|
||||
private el: HTMLElement,
|
||||
|
|
@ -25,6 +29,7 @@ export class CodeblockProcessor extends MarkdownRenderChild {
|
|||
private app: App,
|
||||
private sync: Sync) {
|
||||
super(el)
|
||||
|
||||
this.registrator = this.sync.getRegistrator()
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +46,23 @@ export class CodeblockProcessor extends MarkdownRenderChild {
|
|||
}
|
||||
}
|
||||
|
||||
this.renderer = this.rendererRegistry.prepareRender(results.intermediateContent)(this.el)
|
||||
const { intermediateContent } = results
|
||||
const config = parseIntermediateContent(intermediateContent, {
|
||||
flags: {
|
||||
refresh: this.plugin.settings.enableDynamicUpdates,
|
||||
explain: false
|
||||
}
|
||||
})
|
||||
|
||||
this.flags = config.flags
|
||||
|
||||
this.extrasEl = this.el.createDiv({ cls: 'sqlseal-extras-container' })
|
||||
if (config.flags.explain) {
|
||||
this.explainEl = this.extrasEl.createEl('pre', { cls: 'sqlseal-extras-explain-container' })
|
||||
}
|
||||
const rendererEl = this.el.createDiv({ cls: 'sqlseal-renderer-container' })
|
||||
|
||||
this.renderer = this.rendererRegistry.prepareRender(config.renderer.toLowerCase(), config.rendererArguments)(rendererEl)
|
||||
|
||||
// FIXME: probably should save the one before transform and perform transform every time we execute it.
|
||||
this.query = results.queryPart
|
||||
|
|
@ -63,7 +84,7 @@ export class CodeblockProcessor extends MarkdownRenderChild {
|
|||
const res = transformQuery(this.query, registeredTablesForContext)
|
||||
const transformedQuery = res.sql
|
||||
|
||||
if (this.plugin.settings.enableDynamicUpdates) {
|
||||
if (this.flags.refresh) {
|
||||
registerObservers({
|
||||
bus: this.registrator,
|
||||
callback: () => this.render(),
|
||||
|
|
@ -78,6 +99,13 @@ export class CodeblockProcessor extends MarkdownRenderChild {
|
|||
return
|
||||
}
|
||||
const fileCache = this.app.metadataCache.getFileCache(file)
|
||||
|
||||
if (this.flags.explain) {
|
||||
// Rendering explain
|
||||
const result = await this.db.explain(transformedQuery, fileCache?.frontmatter ?? { })
|
||||
this.explainEl.textContent = result
|
||||
}
|
||||
|
||||
const { data, columns } = await this.db.select(transformedQuery, fileCache?.frontmatter ?? {})
|
||||
this.renderer.render({ data, columns })
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -96,4 +96,22 @@ export class SqlSealDatabase {
|
|||
const result = await this.db.select(statement, frontmatter)
|
||||
return result
|
||||
}
|
||||
|
||||
async explain(statement: string, frontmatter: Record<string, unknown>) {
|
||||
const explainResults = await this.db.explainQuery(statement, frontmatter)
|
||||
let strResult = '';
|
||||
const map = new Map<number, number>()
|
||||
const INDENT_INCREASE = 4
|
||||
map.set(0, -INDENT_INCREASE)
|
||||
for (const result of explainResults) {
|
||||
const parent = parseInt((result.parent as unknown as string) ?? 0, 10)
|
||||
const indent = map.get(parent)! + INDENT_INCREASE
|
||||
for (let i=0;i<indent;i++) {
|
||||
strResult += ' '
|
||||
}
|
||||
strResult += result.detail + "\n"
|
||||
map.set(result.id as number, indent)
|
||||
}
|
||||
return strResult
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { parseLanguage } from './newParser'
|
||||
jest.mock('../datamodel/syncStrategy/MarkdownTableSyncStrategy')
|
||||
|
||||
describe('Parser', () => {
|
||||
it('should properly parse select only', () => {
|
||||
|
|
|
|||
78
src/grammar/parseIntermediateContent.test.ts
Normal file
78
src/grammar/parseIntermediateContent.test.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { parseIntermediateContent } from "./parseIntermediateContent"
|
||||
|
||||
describe('Parse Intermediate Content', () => {
|
||||
it('should properly parse content without any flags', () => {
|
||||
expect(parseIntermediateContent(`
|
||||
|
||||
HTML`, {})).toEqual({
|
||||
renderer: 'HTML',
|
||||
rendererArguments: '',
|
||||
flags: {
|
||||
explain: false,
|
||||
refresh: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should properly set no refresh', () => {
|
||||
expect(parseIntermediateContent(`
|
||||
NO REFRESH
|
||||
HTML`, {})).toEqual({
|
||||
renderer: 'HTML',
|
||||
rendererArguments: '',
|
||||
flags: {
|
||||
explain: false,
|
||||
refresh: false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should properly set explain', () => {
|
||||
expect(parseIntermediateContent(`
|
||||
EXPLAIN
|
||||
HTML`, {})).toEqual({
|
||||
renderer: 'HTML',
|
||||
rendererArguments: '',
|
||||
flags: {
|
||||
explain: true,
|
||||
refresh: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should properly set both flags', () => {
|
||||
expect(parseIntermediateContent(`
|
||||
EXPLAIN
|
||||
NO REFRESH
|
||||
HTML`, {})).toEqual({
|
||||
renderer: 'HTML',
|
||||
rendererArguments: '',
|
||||
flags: {
|
||||
explain: true,
|
||||
refresh: false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should use initial values when calculating', () => {
|
||||
expect(parseIntermediateContent(`HTML`, {
|
||||
flags: { explain: true, refresh: false }
|
||||
})).toEqual({
|
||||
flags: { explain: true, refresh: false },
|
||||
renderer: 'HTML',
|
||||
rendererArguments: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow for flag modifiers to be used in lower case', () => {
|
||||
expect(parseIntermediateContent(`
|
||||
explain
|
||||
no refresh
|
||||
grid
|
||||
`, {})).toEqual({
|
||||
flags: { explain: true, refresh: false },
|
||||
renderer: 'GRID',
|
||||
rendererArguments: ''
|
||||
})
|
||||
})
|
||||
})
|
||||
58
src/grammar/parseIntermediateContent.ts
Normal file
58
src/grammar/parseIntermediateContent.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { assign } from "lodash"
|
||||
|
||||
export interface IntermediateContent {
|
||||
flags: {
|
||||
refresh: boolean,
|
||||
explain: boolean
|
||||
},
|
||||
renderer: string
|
||||
rendererArguments: string
|
||||
}
|
||||
|
||||
const defaultContent = () => ({
|
||||
flags: {
|
||||
refresh: true,
|
||||
explain: false
|
||||
},
|
||||
renderer: 'GRID',
|
||||
rendererArguments: ''
|
||||
} satisfies IntermediateContent)
|
||||
|
||||
const FLAGS = {
|
||||
'REFRESH': (content: IntermediateContent) => {
|
||||
content.flags.refresh = true
|
||||
},
|
||||
'NO REFRESH': (content: IntermediateContent) => {
|
||||
content.flags.refresh = false
|
||||
},
|
||||
'EXPLAIN': (content: IntermediateContent) => {
|
||||
content.flags.explain = true
|
||||
}
|
||||
} as Record<string, any>
|
||||
|
||||
export const parseIntermediateContent = (content: string, initialConfig: Partial<IntermediateContent>) => {
|
||||
const config = assign(defaultContent(), initialConfig)
|
||||
let processingFlags = true
|
||||
const rendererArguments = []
|
||||
for (const row of content.split('\n')) {
|
||||
const trimmedRow = row.trim().toUpperCase()
|
||||
if (!trimmedRow) {
|
||||
continue // empty row
|
||||
}
|
||||
if (processingFlags) {
|
||||
if (!FLAGS[trimmedRow]) {
|
||||
processingFlags = false
|
||||
const [renderer, ...rest ] = trimmedRow.split(' ')
|
||||
config.renderer = renderer.toUpperCase()
|
||||
rendererArguments.push(rest.join(' '))
|
||||
} else {
|
||||
const fn = FLAGS[trimmedRow]
|
||||
fn(config)
|
||||
}
|
||||
} else {
|
||||
rendererArguments.push(trimmedRow)
|
||||
}
|
||||
}
|
||||
config.rendererArguments = rendererArguments.join('\n')
|
||||
return config
|
||||
}
|
||||
|
|
@ -43,35 +43,14 @@ export class RendererRegistry {
|
|||
this.renderers.delete(uniqueName)
|
||||
}
|
||||
|
||||
splitConfig(config: string) {
|
||||
if (config.length === 0) {
|
||||
return {
|
||||
type: this.default,
|
||||
config: ''
|
||||
}
|
||||
}
|
||||
const firstSpace = config.indexOf(" ")
|
||||
if (firstSpace < 0) {
|
||||
return {
|
||||
type: config.toLowerCase(),
|
||||
config: ''
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: config.substring(0, firstSpace).toLowerCase(),
|
||||
config: config.substring(firstSpace)
|
||||
}
|
||||
}
|
||||
|
||||
prepareRender(inputConfig: string) {
|
||||
const { type, config } = this.splitConfig(inputConfig)
|
||||
prepareRender(type: string, config: string) {
|
||||
if (!this.renderersByKey.has(type)) {
|
||||
throw new Error(`Renderer does not exist for ${type}`)
|
||||
}
|
||||
const rendererConfig = this.renderersByKey.get(type)!
|
||||
const rendererConfig = this.renderersByKey.get(type.toLowerCase())!
|
||||
const elConfig = rendererConfig.validateConfig(config)
|
||||
return (el: HTMLElement) => {
|
||||
return rendererConfig.render(elConfig, el)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue