notex.nvim/lua/notex/query/init.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