UNPKG

@braindb/remark-dataview

Version:

markdown-graph-content-layer-database

258 lines (257 loc) 9.31 kB
import nodeSql from "node-sql-parser"; const parser = new nodeSql.Parser(); const opt = { database: "Sqlite" }; import { lex as lexMeta, parse as parseMeta } from "fenceparser"; export const processMeta = (meta) => meta ? parseMeta(lexMeta(meta)) : {}; export function parse(query) { const ast = parser.astify(query, opt); if (Array.isArray(ast) && ast.length != 1) throw new Error("Use only one select statement"); const statement = Array.isArray(ast) ? ast[0] : ast; if (statement.type !== "select") throw new Error("Use only one select statement"); return statement; } // https://github.com/taozhi8833998/node-sql-parser/blob/master/src/column.js function columnName(col) { if (col.as) { if (typeof col.as === "string") return col.as; else return parser.exprToSQL(col.as, opt); } return columnNameExpr(col.expr); } function columnNameExpr(expr) { // @ts-ignore if (expr.type === "column_ref") return expr.column; return parser.exprToSQL(expr, opt); } export function transform(query) { const columns = []; const newQueryColumns = []; query.columns.forEach((col) => { if (col.expr.type == "function") { const func = col.expr; if (func.name.name[0].type === "default" && func.name.name[0].value === "dv_md") { if (func.args?.type === "expr_list" && Array.isArray(func.args?.value) && func.args.value.length === 1) { columns.push({ dv: true, name: columnName(col), func: func.name.name[0].value, args: func.args.value.map((arg) => columnNameExpr(arg)), }); func.args.value.forEach((value) => newQueryColumns.push({ as: null, expr: value })); return; } else { throw new Error("dv_md requires exactly one param"); } } if (func.name.name[0].type === "default" && func.name.name[0].value === "dv_link") { if (func.args?.type === "expr_list" && Array.isArray(func.args?.value) && (func.args.value.length === 2 || func.args.value.length === 0)) { const args = func.args.value.length === 2 ? func.args.value : [ { type: "column_ref", column: "url", table: "documents", }, { type: "binary_expr", operator: "->>", left: { type: "column_ref", table: "documents", column: "frontmatter", }, right: { type: "single_quote_string", value: "$.title" }, }, ]; columns.push({ dv: true, name: columnName(col), func: func.name.name[0].value, args: args.map((arg) => columnNameExpr(arg)), }); args.forEach((value) => newQueryColumns.push({ as: null, expr: value })); return; } else { throw new Error("dv_link requires 0 or 2 params"); } } if (func.name.name[0].type === "default" && func.name.name[0].value === "dv_task") { if (func.args?.type === "expr_list" && Array.isArray(func.args?.value) && (func.args.value.length === 2 || func.args.value.length === 0)) { const args = func.args.value.length === 2 ? func.args.value : [ { type: "column_ref", column: "ast", table: "tasks", }, { type: "column_ref", column: "checked", table: "tasks", }, ]; columns.push({ dv: true, name: columnName(col), func: func.name.name[0].value, args: args.map((arg) => columnNameExpr(arg)), }); args.forEach((value) => newQueryColumns.push({ as: null, expr: value })); return; } else { throw new Error("dv_task requires 0 or 2 params"); } } } columns.push({ dv: false, name: columnName(col) }); newQueryColumns.push(col); }); return { query: parser.sqlify({ ...query, columns: newQueryColumns }, opt), columns, }; } const text = (value) => ({ type: "text", value }); // const emphasis = (children: PhrasingContent[]): Emphasis => ({ // type: "emphasis", // children, // }); const strong = (children) => ({ type: "strong", children, }); const paragraph = (children) => ({ type: "paragraph", children, }); const link = (url, children) => ({ type: "link", title: null, url: url, children, }); const list = (children) => ({ type: "list", ordered: false, start: null, spread: false, children, }); const listItem = (children, checked = null) => ({ type: "listItem", spread: false, checked, children, }); const root = (children, className) => ({ type: "root", children, data: { hName: "div", hProperties: { className, }, }, }); const columnToMdast = (column, row) => { if (column.dv === false) return [text(String(row[column.name]))]; switch (column.func) { case "dv_md": return JSON.parse(row[column.args[0]])?.children || []; case "dv_link": return [link(row[column.args[0]], [text(row[column.args[1]])])]; case "dv_task": return [ listItem(JSON.parse(row[column.args[0]])?.children || [], Boolean(row[column.args[1]])), ]; default: throw new Error(`Unknown function ${column.name}`); } }; const handleStar = (columns, rows) => { const sqlCols = new Set(columns.flatMap((col) => (col.dv === false ? col.name : col.args))); if (sqlCols.has("*")) { const resCols = Object.keys(rows[0]).filter((col) => !sqlCols.has(col)); const starPos = columns.findIndex((col) => col.name === "*"); return [ ...columns.slice(0, starPos), ...resCols.map((name) => ({ dv: false, name })), ...columns.slice(starPos + 1), ]; } return columns; }; export const generateTable = (columns, rows) => { columns = handleStar(columns, rows); if (rows.length === 0) return "empty"; const first = rows[0]; if (first === null || typeof first !== "object") return "wrong result"; const align = columns.map(() => "left"); return { type: "table", align, children: [ { type: "tableRow", children: columns.map((value) => ({ type: "tableCell", children: [text(value.name)], })), }, ...rows.map((row) => ({ type: "tableRow", children: columns.map((column) => ({ type: "tableCell", children: columnToMdast(column, row), })), })), ], }; }; export const generateList = (columns, rows, options = {}) => { columns = handleStar(columns, rows); if (columns.length === 1) return list(rows.map((row) => listItem(columnToMdast(columns[0], row)))); if (columns.length === 2) { const grouped = {}; const firstName = columns[0].dv == true ? columns[0].args[0] : columns[0].name; rows.forEach((row) => { const first = row[firstName]; grouped[first] = grouped[first] || []; grouped[first].push(row); }); return root(Object.values(grouped).flatMap((group) => { const first = columnToMdast(columns[0], group[0]); return [ paragraph([strong(first)]), list(group.map((row) => { const val = columnToMdast(columns[1], row); return val[0].type === "listItem" ? val[0] : listItem(val); })), ]; }), options.root_class); } throw new Error("List expects 1 or 2 columns"); };