365 lines
No EOL
9.1 KiB
Lua
365 lines
No EOL
9.1 KiB
Lua
-- Query engine coordination module
|
|
local M = {}
|
|
|
|
local query_parser = require('notex.query.parser')
|
|
local query_executor = require('notex.query.executor')
|
|
local database = require('notex.database.schema')
|
|
local utils = require('notex.utils')
|
|
|
|
-- Execute query from string
|
|
function M.execute_query(query_string, options)
|
|
options = options or {}
|
|
local start_time = utils.timer("Query execution")
|
|
|
|
-- Parse query
|
|
local parsed_query = query_parser.parse(query_string)
|
|
if #parsed_query.parse_errors > 0 then
|
|
return {
|
|
success = false,
|
|
error_type = "parse_error",
|
|
errors = parsed_query.parse_errors,
|
|
query_string = query_string
|
|
}
|
|
end
|
|
|
|
-- Validate query
|
|
local valid, validation_errors = query_executor.validate_query(parsed_query)
|
|
if not valid then
|
|
return {
|
|
success = false,
|
|
error_type = "validation_error",
|
|
errors = validation_errors,
|
|
query_string = query_string
|
|
}
|
|
end
|
|
|
|
-- Execute query
|
|
local result = query_executor.execute(parsed_query, options)
|
|
|
|
start_time()
|
|
|
|
-- Add metadata
|
|
result.query_string = query_string
|
|
result.parsed_query = parsed_query
|
|
|
|
return result
|
|
end
|
|
|
|
-- Execute saved query
|
|
function M.execute_saved_query(query_name, options)
|
|
options = options or {}
|
|
|
|
-- Get saved query from database
|
|
local ok, query_result = database.queries.get_by_name(query_name)
|
|
if not ok then
|
|
return {
|
|
success = false,
|
|
error_type = "database_error",
|
|
error = "Failed to retrieve saved query: " .. query_result
|
|
}
|
|
end
|
|
|
|
if not query_result then
|
|
return {
|
|
success = false,
|
|
error_type = "not_found",
|
|
error = "Saved query not found: " .. query_name
|
|
}
|
|
end
|
|
|
|
-- Update usage statistics
|
|
database.queries.update_usage(query_result.id)
|
|
|
|
-- Execute the query
|
|
local result = M.execute_query(query_result.definition, options)
|
|
result.query_name = query_name
|
|
result.saved_query_id = query_result.id
|
|
|
|
return result
|
|
end
|
|
|
|
-- Save query for reuse
|
|
function M.save_query(query_name, query_string, options)
|
|
options = options or {}
|
|
|
|
-- Validate query before saving
|
|
local parsed_query = query_parser.parse(query_string)
|
|
if #parsed_query.parse_errors > 0 then
|
|
return {
|
|
success = false,
|
|
error_type = "parse_error",
|
|
errors = parsed_query.parse_errors
|
|
}
|
|
end
|
|
|
|
-- Check if query already exists
|
|
local existing_query, get_err = database.queries.get_by_name(query_name)
|
|
if get_err then
|
|
return {
|
|
success = false,
|
|
error_type = "database_error",
|
|
error = "Failed to check existing query: " .. get_err
|
|
}
|
|
end
|
|
|
|
local query_id = utils.generate_id()
|
|
local current_time = os.time()
|
|
|
|
local query_data = {
|
|
id = existing_query and existing_query.id or query_id,
|
|
name = query_name,
|
|
definition = query_string,
|
|
created_at = existing_query and existing_query.created_at or current_time
|
|
}
|
|
|
|
local ok, err
|
|
if existing_query then
|
|
ok, err = database.queries.update(query_data)
|
|
else
|
|
ok, err = database.queries.create(query_data)
|
|
end
|
|
|
|
if not ok then
|
|
return {
|
|
success = false,
|
|
error_type = "database_error",
|
|
error = "Failed to save query: " .. err
|
|
}
|
|
end
|
|
|
|
return {
|
|
success = true,
|
|
query_id = query_data.id,
|
|
query_name = query_name,
|
|
action = existing_query and "updated" or "created"
|
|
}
|
|
end
|
|
|
|
-- List saved queries
|
|
function M.list_saved_queries(options)
|
|
options = options or {}
|
|
|
|
local ok, queries = database.queries.get_all()
|
|
if not ok then
|
|
return {
|
|
success = false,
|
|
error_type = "database_error",
|
|
error = "Failed to retrieve saved queries: " .. queries
|
|
}
|
|
end
|
|
|
|
-- Add metadata to each query
|
|
for _, query in ipairs(queries) do
|
|
query.definition_preview = query.definition:sub(1, 100) .. (#query.definition > 100 and "..." or "")
|
|
query.last_used_formatted = query.last_used > 0 and os.date("%Y-%m-%d %H:%M", query.last_used) or "Never"
|
|
end
|
|
|
|
return {
|
|
success = true,
|
|
queries = queries,
|
|
total_count = #queries
|
|
}
|
|
end
|
|
|
|
-- Delete saved query
|
|
function M.delete_saved_query(query_name)
|
|
local ok, query_result = database.queries.get_by_name(query_name)
|
|
if not ok then
|
|
return {
|
|
success = false,
|
|
error_type = "database_error",
|
|
error = "Failed to find query: " .. query_result
|
|
}
|
|
end
|
|
|
|
if not query_result then
|
|
return {
|
|
success = false,
|
|
error_type = "not_found",
|
|
error = "Query not found: " .. query_name
|
|
}
|
|
end
|
|
|
|
local delete_ok, delete_err = database.queries.delete(query_result.id)
|
|
if not delete_ok then
|
|
return {
|
|
success = false,
|
|
error_type = "database_error",
|
|
error = "Failed to delete query: " .. delete_err
|
|
}
|
|
end
|
|
|
|
return {
|
|
success = true,
|
|
query_name = query_name,
|
|
deleted_query_id = query_result.id
|
|
}
|
|
end
|
|
|
|
-- Get query suggestions
|
|
function M.get_suggestions(partial_query, cursor_pos)
|
|
cursor_pos = cursor_pos or #partial_query
|
|
|
|
-- Parse partial query to get context
|
|
local parsed_query = query_parser.parse(partial_query)
|
|
|
|
-- Get suggestions based on context
|
|
local suggestions = query_executor.get_suggestions(parsed_query, {
|
|
cursor_pos = cursor_pos
|
|
})
|
|
|
|
return {
|
|
success = true,
|
|
suggestions = suggestions,
|
|
cursor_pos = cursor_pos
|
|
}
|
|
end
|
|
|
|
-- Validate query syntax
|
|
function M.validate_query_syntax(query_string)
|
|
local parsed_query = query_parser.parse(query_string)
|
|
|
|
return {
|
|
valid = #parsed_query.parse_errors == 0,
|
|
errors = parsed_query.parse_errors,
|
|
parsed_query = parsed_query
|
|
}
|
|
end
|
|
|
|
-- Explain query
|
|
function M.explain_query(query_string, options)
|
|
options = options or {}
|
|
|
|
local parsed_query = query_parser.parse(query_string)
|
|
if #parsed_query.parse_errors > 0 then
|
|
return {
|
|
success = false,
|
|
error_type = "parse_error",
|
|
errors = parsed_query.parse_errors
|
|
}
|
|
end
|
|
|
|
local explanation = query_executor.explain_query(parsed_query, options)
|
|
|
|
return explanation
|
|
end
|
|
|
|
-- Format query string
|
|
function M.format_query(query_string)
|
|
local parsed_query = query_parser.parse(query_string)
|
|
if #parsed_query.parse_errors > 0 then
|
|
return query_string, parsed_query.parse_errors
|
|
end
|
|
|
|
-- Rebuild query with proper formatting
|
|
local formatted_parts = {}
|
|
|
|
-- Add filters
|
|
if next(parsed_query.filters) then
|
|
local filter_parts = {}
|
|
for key, value in pairs(parsed_query.filters) do
|
|
if type(value) == "string" then
|
|
table.insert(filter_parts, string.format('%s: "%s"', key, value))
|
|
elseif type(value) == "table" then
|
|
table.insert(filter_parts, string.format('%s: [%s]', key, vim.inspect(value)))
|
|
else
|
|
table.insert(filter_parts, string.format('%s: %s', key, tostring(value)))
|
|
end
|
|
end
|
|
table.insert(formatted_parts, table.concat(filter_parts, "\n"))
|
|
end
|
|
|
|
-- Add conditions
|
|
if parsed_query.conditions then
|
|
table.insert(formatted_parts, "WHERE " .. M.format_conditions(parsed_query.conditions))
|
|
end
|
|
|
|
-- Add order by
|
|
if parsed_query.order_by then
|
|
table.insert(formatted_parts, string.format("ORDER BY %s %s", parsed_query.order_by.field, parsed_query.order_by.direction))
|
|
end
|
|
|
|
-- Add group by
|
|
if parsed_query.group_by then
|
|
table.insert(formatted_parts, "GROUP BY " .. parsed_query.group_by)
|
|
end
|
|
|
|
-- Add limit
|
|
if parsed_query.limit then
|
|
table.insert(formatted_parts, "LIMIT " .. parsed_query.limit)
|
|
end
|
|
|
|
local formatted_query = table.concat(formatted_parts, "\n")
|
|
|
|
return formatted_query, {}
|
|
end
|
|
|
|
-- Format conditions recursively
|
|
function M.format_conditions(conditions)
|
|
if conditions.type == "comparison" then
|
|
return string.format("%s %s %s", conditions.field, conditions.operator, tostring(conditions.value))
|
|
elseif conditions.type == "existence" then
|
|
return conditions.field
|
|
elseif conditions.clauses then
|
|
local clause_parts = {}
|
|
for _, clause in ipairs(conditions.clauses) do
|
|
table.insert(clause_parts, M.format_conditions(clause))
|
|
end
|
|
local operator = conditions.type:upper()
|
|
return "(" .. table.concat(clause_parts, " " .. operator .. " ") .. ")"
|
|
end
|
|
|
|
return ""
|
|
end
|
|
|
|
-- Get query statistics
|
|
function M.get_query_statistics(options)
|
|
options = options or {}
|
|
|
|
local stats = {
|
|
total_queries = 0,
|
|
saved_queries = 0,
|
|
recent_queries = {},
|
|
popular_queries = {},
|
|
average_execution_time = 0
|
|
}
|
|
|
|
-- Get saved queries count
|
|
local ok, saved_queries = database.queries.get_all()
|
|
if ok then
|
|
stats.saved_queries = #saved_queries
|
|
|
|
-- Get popular queries
|
|
local popular = {}
|
|
for _, query in ipairs(saved_queries) do
|
|
if query.use_count > 0 then
|
|
table.insert(popular, {
|
|
name = query.name,
|
|
use_count = query.use_count,
|
|
last_used = query.last_used
|
|
})
|
|
end
|
|
end
|
|
|
|
table.sort(popular, function(a, b) return a.use_count > b.use_count end)
|
|
stats.popular_queries = vim.list_slice(popular, 1, 10)
|
|
end
|
|
|
|
return {
|
|
success = true,
|
|
statistics = stats
|
|
}
|
|
end
|
|
|
|
-- Initialize query engine
|
|
function M.init(database_path)
|
|
local ok, err = require('notex.database.init').init(database_path)
|
|
if not ok then
|
|
return false, "Failed to initialize database for query engine: " .. err
|
|
end
|
|
|
|
utils.log("INFO", "Query engine initialized")
|
|
return true, "Query engine initialized successfully"
|
|
end
|
|
|
|
return M |