UNPKG

jev-ql

Version:

PostgreSQL-compatible query language powered by TypeSafe Jev System One models

217 lines (193 loc) • 6.79 kB
import { parse, tokenize } from './parser.js'; import { JevClient, BaseSemanticEngine, TypeSafeJevEngine, LLMStructuredEngine, EmbeddingEngine, HeuristicEngine, WebMLKitEngine, registerEngine, createEngine } from './jev.js'; import { DataAdapter } from './adapters.js'; import { Executor } from './executor.js'; import { formatTable, formatCSV, parseCSV } from './utils.js'; import { pipelineToSQL, isPipelineQuery } from './pipeline.js'; import { createQueryPlan, splitWhereClause, defaultEvalLiteral } from './planner.js'; class JevQLDatabase { constructor(initialData = null, options = {}) { this.options = { ...options }; this.client = new JevClient(this.options); this.adapter = new DataAdapter(); this.executor = new Executor(this.client, this.adapter, this.options); if (initialData) { if (Array.isArray(initialData)) { this.adapter.registerTable('data', initialData); } else if (typeof initialData === 'object') { for (const [name, tbl] of Object.entries(initialData)) { this.adapter.registerTable(name, tbl); } } } } register(name, data) { this.adapter.registerTable(name, data); return this; } registerTable(name, data) { return this.register(name, data); } async query(queryText, data = null) { const sql = isPipelineQuery(queryText) ? pipelineToSQL(queryText) : queryText; const ast = parse(sql); return this.executor.execute(ast, data); } async explain(queryText, data = null) { const sql = isPipelineQuery(queryText) ? pipelineToSQL(queryText) : queryText; const ast = parse(sql.startsWith('EXPLAIN') ? sql : `EXPLAIN ${sql}`); return this.executor.execute(ast, data); } async analyze(queryText, data = null) { const sql = isPipelineQuery(queryText) ? pipelineToSQL(queryText) : queryText; const ast = parse(`EXPLAIN ANALYZE ${sql.replace(/^EXPLAIN\s+(ANALYZE\s+)?/i, '')}`); return this.executor.execute(ast, data); } table(rows, options = {}) { return formatTable(rows, options); } } /** * Main jevql function - adapts dynamically based on arguments: * * 1. Tagged Template Literal: * const rows = await jevql`from ${tickets} | filter status == 'open' | take 10`; * * 2. Direct query: * const rows = await jevql('SELECT * FROM "tickets.json"'); * const rows = await jevql('from tickets | filter status == "open"', tickets); * * 3. Database instance: * const db = jevql(tickets); * * 4. Curried query: * const filterUrgent = jevql('SELECT * FROM data WHERE NOUL(body, "Urgent?") > 0.8'); */ export default function jevql(firstArg, ...rest) { // Case 0: Tagged Template Literal: jevql`from ${tickets} | ...` if (Array.isArray(firstArg) && firstArg.raw !== undefined) { const strings = firstArg; const values = rest; let queryStr = ''; let extractedData = null; for (let i = 0; i < strings.length; i++) { queryStr += strings[i]; if (i < values.length) { const val = values[i]; if (Array.isArray(val) || (val && typeof val === 'object' && !val.type && !val.name)) { extractedData = val; queryStr += 'data'; } else if (typeof val === 'number' || typeof val === 'boolean') { queryStr += String(val); } else if (typeof val === 'string') { queryStr += `'${val.replace(/'/g, "''")}'`; } else { queryStr += String(val); } } } const sql = isPipelineQuery(queryStr) ? pipelineToSQL(queryStr) : queryStr; const db = new JevQLDatabase(extractedData, {}); return db.query(sql); } const secondArg = rest[0] !== undefined ? rest[0] : null; let options = rest[1] !== undefined ? rest[1] : {}; // Check if firstArg is a query (either SQL or Pipeline) const isQuery = typeof firstArg === 'string' && ( firstArg.trim().toUpperCase().startsWith('SELECT') || firstArg.trim().toUpperCase().startsWith('EXPLAIN') || isPipelineQuery(firstArg) ); if (isQuery) { const sql = isPipelineQuery(firstArg) ? pipelineToSQL(firstArg) : firstArg; if (secondArg != null && !Array.isArray(secondArg) && typeof secondArg === 'object' && !secondArg[0] && !secondArg.length) { if (secondArg.apiKey || secondArg.cache !== undefined || secondArg.concurrency) { options = secondArg; } } if (secondArg != null && (Array.isArray(secondArg) || secondArg.length !== undefined)) { const db = new JevQLDatabase(secondArg, options); return db.query(sql); } const ast = parse(sql); // If the query explicitly specifies a file source (e.g. FROM "tickets.json"), execute immediately if (ast.from?.source?.type === 'FileSource') { const db = new JevQLDatabase(null, options); return db.query(sql); } // Otherwise, return a curried query function return async (data, callOptions = {}) => { const mergedOpts = { ...options, ...callOptions }; const db = new JevQLDatabase(data, mergedOpts); return db.query(sql); }; } // Case 2: jevql(data, options) -> returns queryable DB instance const opts = typeof secondArg === 'object' && secondArg !== null ? secondArg : options; return new JevQLDatabase(firstArg, opts); } jevql.with = function(options = {}) { return function(firstArg, ...rest) { if (Array.isArray(firstArg) && firstArg.raw !== undefined) { const strings = firstArg; const values = rest; let queryStr = ''; let extractedData = null; for (let i = 0; i < strings.length; i++) { queryStr += strings[i]; if (i < values.length) { const val = values[i]; if (Array.isArray(val) || (val && typeof val === 'object' && !val.type && !val.name)) { extractedData = val; queryStr += 'data'; } else if (typeof val === 'number' || typeof val === 'boolean') { queryStr += String(val); } else if (typeof val === 'string') { queryStr += `'${val.replace(/'/g, "''")}'`; } else { queryStr += String(val); } } } const sql = isPipelineQuery(queryStr) ? pipelineToSQL(queryStr) : queryStr; const db = new JevQLDatabase(extractedData, options); return db.query(sql); } return jevql(firstArg, ...rest, options); }; }; export { jevql, JevQLDatabase, parse, tokenize, isPipelineQuery, pipelineToSQL, createQueryPlan, splitWhereClause, defaultEvalLiteral, JevClient, DataAdapter, Executor, BaseSemanticEngine, TypeSafeJevEngine, LLMStructuredEngine, EmbeddingEngine, HeuristicEngine, WebMLKitEngine, registerEngine, createEngine, formatTable, formatCSV, parseCSV };