diff --git a/modules/http/http.lua b/modules/http/http.lua index dfe6073..c091357 100644 --- a/modules/http/http.lua +++ b/modules/http/http.lua @@ -1,4 +1,3 @@ -local str = require("string") local json = require("json") local http = {} @@ -19,29 +18,29 @@ Response.__index = Response local function parse_cookies(cookie_header) local cookies = {} - if str.is_empty(cookie_header) then + if string.is_empty(cookie_header) then return cookies end -- Split by semicolon and parse each cookie - local cookie_pairs = str.split(cookie_header, ";") + local cookie_pairs = string.split(cookie_header, ";") for _, cookie_pair in ipairs(cookie_pairs) do - local trimmed = str.trim(cookie_pair) - if not str.is_empty(trimmed) then - local parts = str.split(trimmed, "=") + local trimmed = string.trim(cookie_pair) + if not string.is_empty(trimmed) then + local parts = string.split(trimmed, "=") if #parts >= 2 then - local name = str.trim(parts[1]) - local value = str.trim(parts[2]) + local name = string.trim(parts[1]) + local value = string.trim(parts[2]) -- URL decode the value local success, decoded = pcall(function() - return str.url_decode(value) + return string.url_decode(value) end) cookies[name] = success and decoded or value elseif #parts == 1 then -- Cookie without value - cookies[str.trim(parts[1])] = "" + cookies[string.trim(parts[1])] = "" end end end @@ -54,17 +53,17 @@ end -- ====================================================================== local function split_path(path) - if str.is_empty(path) or path == "/" then + if string.is_empty(path) or path == "/" then return {} end -- Remove leading/trailing slashes and split - local clean_path = str.trim(path, "/") - if str.is_empty(clean_path) then + local clean_path = string.trim(path, "/") + if string.is_empty(clean_path) then return {} end - return str.split(clean_path, "/") + return string.split(clean_path, "/") end local function match_route(method, path) @@ -86,12 +85,12 @@ local function match_route(method, path) for j = i, #path_segments do table.insert(remaining, path_segments[j]) end - params["*"] = str.join(remaining, "/") + params["*"] = string.join(remaining, "/") break - elseif str.starts_with(route_seg, ":") then + elseif string.starts_with(route_seg, ":") then -- Parameter segment if i <= #path_segments then - local param_name = str.slice(route_seg, 2, -1) + local param_name = string.slice(route_seg, 2, -1) params[param_name] = path_segments[i] else match = false @@ -135,7 +134,7 @@ function _http_handle_request(req_table, res_table) end local mw = _G._http_middleware[index] - if mw.path == nil or str.starts_with(req.path, mw.path) then + if mw.path == nil or string.starts_with(req.path, mw.path) then mw.handler(req, res, function() run_middleware(index + 1) end) @@ -203,7 +202,7 @@ end function Server:_add_route(method, path, handler) -- Ensure path starts with / - if not str.starts_with(path, "/") then + if not string.starts_with(path, "/") then path = "/" .. path end @@ -321,7 +320,7 @@ function Request.new(req_table) end function Request:get(header_name) - local lower_name = str.lower(header_name) + local lower_name = string.lower(header_name) return self.headers[header_name] or self.headers[lower_name] end @@ -359,7 +358,7 @@ function Request:cookie_matches(name, pattern) if not cookie_value then return false end - return str.match(pattern, cookie_value) + return string.match(pattern, cookie_value) end function Request:get_cookies_by_names(names) @@ -381,7 +380,7 @@ function Request:has_auth_cookies() end function Request:json() - if str.is_empty(self.body) then + if string.is_empty(self.body) then return nil end @@ -398,27 +397,27 @@ end function Request:is_json() local content_type = self:get("content-type") or "" - return str.contains(content_type, "application/json") + return string.contains(content_type, "application/json") end function Request:is_form() local content_type = self:get("content-type") or "" - return str.contains(content_type, "application/x-www-form-urlencoded") + return string.contains(content_type, "application/x-www-form-urlencoded") end function Request:is_multipart() local content_type = self:get("content-type") or "" - return str.contains(content_type, "multipart/form-data") + return string.contains(content_type, "multipart/form-data") end function Request:is_xml() local content_type = self:get("content-type") or "" - return str.contains(content_type, "application/xml") or str.contains(content_type, "text/xml") + return string.contains(content_type, "application/xml") or string.contains(content_type, "text/xml") end function Request:accepts(mime_type) local accept_header = self:get("accept") or "" - return str.contains(accept_header, mime_type) or str.contains(accept_header, "*/*") + return string.contains(accept_header, mime_type) or string.contains(accept_header, "*/*") end function Request:user_agent() @@ -431,7 +430,7 @@ end function Request:is_secure() local proto = self:get("x-forwarded-proto") - return proto == "https" or str.starts_with(self:get("host") or "", "https://") + return proto == "https" or string.starts_with(self:get("host") or "", "https://") end -- ====================================================================== @@ -565,8 +564,8 @@ function Response:cookie(name, value, options) local cookie_value = tostring(value) -- URL encode the cookie value if it contains special characters - if str.match("[;,\\s]", cookie_value) then - cookie_value = str.url_encode(cookie_value) + if string.match("[;,\\s]", cookie_value) then + cookie_value = string.url_encode(cookie_value) end local cookie = name .. "=" .. cookie_value @@ -638,13 +637,13 @@ function Response:download(data, filename, content_type) self:type(content_type) elseif filename then -- Try to guess content type from extension - if str.ends_with(filename, ".pdf") then + if string.ends_with(filename, ".pdf") then self:type("application/pdf") - elseif str.ends_with(filename, ".zip") then + elseif string.ends_with(filename, ".zip") then self:type("application/zip") - elseif str.ends_with(filename, ".json") then + elseif string.ends_with(filename, ".json") then self:type("application/json") - elseif str.ends_with(filename, ".csv") then + elseif string.ends_with(filename, ".csv") then self:type("text/csv") else self:type("application/octet-stream") @@ -675,7 +674,7 @@ function http.cors(options) res:header("Access-Control-Allow-Credentials", "true") end - if str.iequals(req.method, "OPTIONS") then + if string.iequals(req.method, "OPTIONS") then res:status(204):send("") else next() @@ -687,7 +686,7 @@ function http.static(root_path, url_prefix) url_prefix = url_prefix or "/" -- Ensure prefix starts with / - if not str.starts_with(url_prefix, "/") then + if not string.starts_with(url_prefix, "/") then url_prefix = "/" .. url_prefix end @@ -706,7 +705,7 @@ end function http.json_parser() return function(req, res, next) - if req:is_json() and not str.is_empty(req.body) then + if req:is_json() and not string.is_empty(req.body) then local success, data = pcall(function() return req:json() end) @@ -733,11 +732,11 @@ function http.logger(format) local duration = (os.clock() - start_time) * 1000 local status = res._table.status or 200 - local log_message = str.template(format, { + local log_message = string.template(format, { method = req.method, path = req.path, status = status, - ["response-time"] = str.format("%.2f", duration), + ["response-time"] = string.format("%.2f", duration), ["user-agent"] = req:user_agent(), ip = req:ip() }) @@ -749,9 +748,9 @@ end function http.compression() return function(req, res, next) local accept_encoding = req:get("accept-encoding") or "" - if str.contains(accept_encoding, "gzip") then + if string.contains(accept_encoding, "gzip") then res:header("Content-Encoding", "gzip") - elseif str.contains(accept_encoding, "deflate") then + elseif string.contains(accept_encoding, "deflate") then res:header("Content-Encoding", "deflate") end next() diff --git a/modules/mysql/mysql.lua b/modules/mysql/mysql.lua index cbab9fb..d675f65 100644 --- a/modules/mysql/mysql.lua +++ b/modules/mysql/mysql.lua @@ -1,4 +1,3 @@ -local str = require("string") local tbl = require("table") local mysql = {} @@ -25,7 +24,7 @@ function Connection:query(query_str, ...) if not self._id then error("Connection is closed") end - query_str = str.normalize_whitespace(query_str) + query_str = string.normalize_whitespace(query_str) return moonshark.sql_query(self._id, query_str, ...) end @@ -33,7 +32,7 @@ function Connection:exec(query_str, ...) if not self._id then error("Connection is closed") end - query_str = str.normalize_whitespace(query_str) + query_str = string.normalize_whitespace(query_str) return moonshark.sql_exec(self._id, query_str, ...) end @@ -85,20 +84,20 @@ function Connection:begin() if not tx.active then error("Transaction is not active") end - if str.is_blank(name) then + if string.is_blank(name) then error("Savepoint name cannot be empty") end - return tx.conn:exec(str.template("SAVEPOINT ${name}", {name = name})) + return tx.conn:exec(string.template("SAVEPOINT ${name}", {name = name})) end, rollback_to = function(tx, name) if not tx.active then error("Transaction is not active") end - if str.is_blank(name) then + if string.is_blank(name) then error("Savepoint name cannot be empty") end - return tx.conn:exec(str.template("ROLLBACK TO SAVEPOINT ${name}", {name = name})) + return tx.conn:exec(string.template("ROLLBACK TO SAVEPOINT ${name}", {name = name})) end, query = function(tx, query_str, ...) @@ -135,7 +134,7 @@ end -- Simplified MySQL-specific query builder helpers function Connection:insert(table_name, data) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -143,7 +142,7 @@ function Connection:insert(table_name, data) local values = tbl.values(data) local placeholders = tbl.map(keys, function() return "?" end) - local query = str.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", { + local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", { table = table_name, columns = tbl.concat(keys, ", "), placeholders = tbl.concat(placeholders, ", ") @@ -153,7 +152,7 @@ function Connection:insert(table_name, data) end function Connection:upsert(table_name, data, update_data) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -164,10 +163,10 @@ function Connection:upsert(table_name, data, update_data) -- Use update_data if provided, otherwise update with same data local update_source = update_data or data local updates = tbl.map(tbl.keys(update_source), function(key) - return str.template("${key} = VALUES(${key})", {key = key}) + return string.template("${key} = VALUES(${key})", {key = key}) end) - local query = str.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updates}", { + local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updates}", { table = table_name, columns = tbl.concat(keys, ", "), placeholders = tbl.concat(placeholders, ", "), @@ -178,7 +177,7 @@ function Connection:upsert(table_name, data, update_data) end function Connection:replace(table_name, data) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -186,7 +185,7 @@ function Connection:replace(table_name, data) local values = tbl.values(data) local placeholders = tbl.map(keys, function() return "?" end) - local query = str.template("REPLACE INTO ${table} (${columns}) VALUES (${placeholders})", { + local query = string.template("REPLACE INTO ${table} (${columns}) VALUES (${placeholders})", { table = table_name, columns = tbl.concat(keys, ", "), placeholders = tbl.concat(placeholders, ", ") @@ -196,20 +195,20 @@ function Connection:replace(table_name, data) end function Connection:update(table_name, data, where_clause, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - if str.is_blank(where_clause) then + if string.is_blank(where_clause) then error("WHERE clause cannot be empty for UPDATE") end local keys = tbl.keys(data) local values = tbl.values(data) local sets = tbl.map(keys, function(key) - return str.template("${key} = ?", {key = key}) + return string.template("${key} = ?", {key = key}) end) - local query = str.template("UPDATE ${table} SET ${sets} WHERE ${where}", { + local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", { table = table_name, sets = tbl.concat(sets, ", "), where = where_clause @@ -223,14 +222,14 @@ function Connection:update(table_name, data, where_clause, ...) end function Connection:delete(table_name, where_clause, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - if str.is_blank(where_clause) then + if string.is_blank(where_clause) then error("WHERE clause cannot be empty for DELETE") end - local query = str.template("DELETE FROM ${table} WHERE ${where}", { + local query = string.template("DELETE FROM ${table} WHERE ${where}", { table = table_name, where = where_clause }) @@ -238,7 +237,7 @@ function Connection:delete(table_name, where_clause, ...) end function Connection:select(table_name, columns, where_clause, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -248,15 +247,15 @@ function Connection:select(table_name, columns, where_clause, ...) end local query - if where_clause and not str.is_blank(where_clause) then - query = str.template("SELECT ${columns} FROM ${table} WHERE ${where}", { + if where_clause and not string.is_blank(where_clause) then + query = string.template("SELECT ${columns} FROM ${table} WHERE ${where}", { columns = columns, table = table_name, where = where_clause }) return self:query(query, ...) else - query = str.template("SELECT ${columns} FROM ${table}", { + query = string.template("SELECT ${columns} FROM ${table}", { columns = columns, table = table_name }) @@ -266,19 +265,19 @@ end -- MySQL schema helpers function Connection:database_exists(database_name) - if str.is_blank(database_name) then + if string.is_blank(database_name) then return false end local result = self:query_value( "SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?", - str.trim(database_name) + string.trim(database_name) ) return result ~= nil end function Connection:table_exists(table_name, database_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then return false end @@ -289,13 +288,13 @@ function Connection:table_exists(table_name, database_name) local result = self:query_value( "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?", - str.trim(database_name), str.trim(table_name) + string.trim(database_name), string.trim(table_name) ) return result ~= nil end function Connection:column_exists(table_name, column_name, database_name) - if str.is_blank(table_name) or str.is_blank(column_name) then + if string.is_blank(table_name) or string.is_blank(column_name) then return false end @@ -307,19 +306,19 @@ function Connection:column_exists(table_name, column_name, database_name) local result = self:query_value([[ SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ? - ]], str.trim(database_name), str.trim(table_name), str.trim(column_name)) + ]], string.trim(database_name), string.trim(table_name), string.trim(column_name)) return result ~= nil end function Connection:create_database(database_name, charset, collation) - if str.is_blank(database_name) then + if string.is_blank(database_name) then error("Database name cannot be empty") end - local charset_clause = charset and str.template(" CHARACTER SET ${charset}", {charset = charset}) or "" - local collation_clause = collation and str.template(" COLLATE ${collation}", {collation = collation}) or "" + local charset_clause = charset and string.template(" CHARACTER SET ${charset}", {charset = charset}) or "" + local collation_clause = collation and string.template(" COLLATE ${collation}", {collation = collation}) or "" - local query = str.template("CREATE DATABASE IF NOT EXISTS ${database}${charset}${collation}", { + local query = string.template("CREATE DATABASE IF NOT EXISTS ${database}${charset}${collation}", { database = database_name, charset = charset_clause, collation = collation_clause @@ -328,25 +327,25 @@ function Connection:create_database(database_name, charset, collation) end function Connection:drop_database(database_name) - if str.is_blank(database_name) then + if string.is_blank(database_name) then error("Database name cannot be empty") end - local query = str.template("DROP DATABASE IF EXISTS ${database}", {database = database_name}) + local query = string.template("DROP DATABASE IF EXISTS ${database}", {database = database_name}) return self:exec(query) end function Connection:create_table(table_name, schema, engine, charset) - if str.is_blank(table_name) or str.is_blank(schema) then + if string.is_blank(table_name) or string.is_blank(schema) then error("Table name and schema cannot be empty") end - local engine_clause = engine and str.template(" ENGINE=${engine}", {engine = str.upper(engine)}) or "" - local charset_clause = charset and str.template(" CHARACTER SET ${charset}", {charset = charset}) or "" + local engine_clause = engine and string.template(" ENGINE=${engine}", {engine = string.upper(engine)}) or "" + local charset_clause = charset and string.template(" CHARACTER SET ${charset}", {charset = charset}) or "" - local query = str.template("CREATE TABLE IF NOT EXISTS ${table} (${schema})${engine}${charset}", { + local query = string.template("CREATE TABLE IF NOT EXISTS ${table} (${schema})${engine}${charset}", { table = table_name, - schema = str.trim(schema), + schema = string.trim(schema), engine = engine_clause, charset = charset_clause }) @@ -354,34 +353,34 @@ function Connection:create_table(table_name, schema, engine, charset) end function Connection:drop_table(table_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - local query = str.template("DROP TABLE IF EXISTS ${table}", {table = table_name}) + local query = string.template("DROP TABLE IF EXISTS ${table}", {table = table_name}) return self:exec(query) end function Connection:add_column(table_name, column_def, position) - if str.is_blank(table_name) or str.is_blank(column_def) then + if string.is_blank(table_name) or string.is_blank(column_def) then error("Table name and column definition cannot be empty") end - local position_clause = position and str.template(" ${position}", {position = position}) or "" - local query = str.template("ALTER TABLE ${table} ADD COLUMN ${column}${position}", { + local position_clause = position and string.template(" ${position}", {position = position}) or "" + local query = string.template("ALTER TABLE ${table} ADD COLUMN ${column}${position}", { table = table_name, - column = str.trim(column_def), + column = string.trim(column_def), position = position_clause }) return self:exec(query) end function Connection:drop_column(table_name, column_name) - if str.is_blank(table_name) or str.is_blank(column_name) then + if string.is_blank(table_name) or string.is_blank(column_name) then error("Table name and column name cannot be empty") end - local query = str.template("ALTER TABLE ${table} DROP COLUMN ${column}", { + local query = string.template("ALTER TABLE ${table} DROP COLUMN ${column}", { table = table_name, column = column_name }) @@ -389,23 +388,23 @@ function Connection:drop_column(table_name, column_name) end function Connection:modify_column(table_name, column_def) - if str.is_blank(table_name) or str.is_blank(column_def) then + if string.is_blank(table_name) or string.is_blank(column_def) then error("Table name and column definition cannot be empty") end - local query = str.template("ALTER TABLE ${table} MODIFY COLUMN ${column}", { + local query = string.template("ALTER TABLE ${table} MODIFY COLUMN ${column}", { table = table_name, - column = str.trim(column_def) + column = string.trim(column_def) }) return self:exec(query) end function Connection:rename_table(old_name, new_name) - if str.is_blank(old_name) or str.is_blank(new_name) then + if string.is_blank(old_name) or string.is_blank(new_name) then error("Old and new table names cannot be empty") end - local query = str.template("RENAME TABLE ${old} TO ${new}", { + local query = string.template("RENAME TABLE ${old} TO ${new}", { old = old_name, new = new_name }) @@ -413,15 +412,15 @@ function Connection:rename_table(old_name, new_name) end function Connection:create_index(index_name, table_name, columns, unique, type) - if str.is_blank(index_name) or str.is_blank(table_name) then + if string.is_blank(index_name) or string.is_blank(table_name) then error("Index name and table name cannot be empty") end local unique_clause = unique and "UNIQUE " or "" - local type_clause = type and str.template(" USING ${type}", {type = str.upper(type)}) or "" + local type_clause = type and string.template(" USING ${type}", {type = string.upper(type)}) or "" local columns_str = type(columns) == "table" and tbl.concat(columns, ", ") or tostring(columns) - local query = str.template("CREATE ${unique}INDEX ${index} ON ${table} (${columns})${type}", { + local query = string.template("CREATE ${unique}INDEX ${index} ON ${table} (${columns})${type}", { unique = unique_clause, index = index_name, table = table_name, @@ -432,11 +431,11 @@ function Connection:create_index(index_name, table_name, columns, unique, type) end function Connection:drop_index(index_name, table_name) - if str.is_blank(index_name) or str.is_blank(table_name) then + if string.is_blank(index_name) or string.is_blank(table_name) then error("Index name and table name cannot be empty") end - local query = str.template("DROP INDEX ${index} ON ${table}", { + local query = string.template("DROP INDEX ${index} ON ${table}", { index = index_name, table = table_name }) @@ -445,51 +444,51 @@ end -- MySQL maintenance functions function Connection:optimize(table_name) - local table_clause = table_name and str.template(" ${table}", {table = table_name}) or "" - return self:query(str.template("OPTIMIZE TABLE${table}", {table = table_clause})) + local table_clause = table_name and string.template(" ${table}", {table = table_name}) or "" + return self:query(string.template("OPTIMIZE TABLE${table}", {table = table_clause})) end function Connection:repair(table_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty for REPAIR") end - return self:query(str.template("REPAIR TABLE ${table}", {table = table_name})) + return self:query(string.template("REPAIR TABLE ${table}", {table = table_name})) end function Connection:check_table(table_name, options) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty for CHECK") end local options_clause = "" if options then local valid_options = {"QUICK", "FAST", "MEDIUM", "EXTENDED", "CHANGED"} - local options_upper = str.upper(options) + local options_upper = string.upper(options) if tbl.contains(valid_options, options_upper) then - options_clause = str.template(" ${options}", {options = options_upper}) + options_clause = string.template(" ${options}", {options = options_upper}) end end - return self:query(str.template("CHECK TABLE ${table}${options}", { + return self:query(string.template("CHECK TABLE ${table}${options}", { table = table_name, options = options_clause })) end function Connection:analyze_table(table_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty for ANALYZE") end - return self:query(str.template("ANALYZE TABLE ${table}", {table = table_name})) + return self:query(string.template("ANALYZE TABLE ${table}", {table = table_name})) end -- MySQL settings and introspection function Connection:show(what) - if str.is_blank(what) then + if string.is_blank(what) then error("SHOW parameter cannot be empty") end - return self:query(str.template("SHOW ${what}", {what = str.upper(what)})) + return self:query(string.template("SHOW ${what}", {what = string.upper(what)})) end function Connection:current_database() @@ -509,36 +508,36 @@ function Connection:list_databases() end function Connection:list_tables(database_name) - if database_name and not str.is_blank(database_name) then - return self:query(str.template("SHOW TABLES FROM ${database}", {database = database_name})) + if database_name and not string.is_blank(database_name) then + return self:query(string.template("SHOW TABLES FROM ${database}", {database = database_name})) else return self:query("SHOW TABLES") end end function Connection:describe_table(table_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - return self:query(str.template("DESCRIBE ${table}", {table = table_name})) + return self:query(string.template("DESCRIBE ${table}", {table = table_name})) end function Connection:show_create_table(table_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - return self:query(str.template("SHOW CREATE TABLE ${table}", {table = table_name})) + return self:query(string.template("SHOW CREATE TABLE ${table}", {table = table_name})) end function Connection:show_indexes(table_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - return self:query(str.template("SHOW INDEXES FROM ${table}", {table = table_name})) + return self:query(string.template("SHOW INDEXES FROM ${table}", {table = table_name})) end function Connection:show_table_status(table_name) - if table_name and not str.is_blank(table_name) then + if table_name and not string.is_blank(table_name) then return self:query("SHOW TABLE STATUS LIKE ?", table_name) else return self:query("SHOW TABLE STATUS") @@ -547,12 +546,12 @@ end -- MySQL user and privilege management function Connection:create_user(username, password, host) - if str.is_blank(username) or str.is_blank(password) then + if string.is_blank(username) or string.is_blank(password) then error("Username and password cannot be empty") end host = host or "%" - local query = str.template("CREATE USER '${username}'@'${host}' IDENTIFIED BY ?", { + local query = string.template("CREATE USER '${username}'@'${host}' IDENTIFIED BY ?", { username = username, host = host }) @@ -560,12 +559,12 @@ function Connection:create_user(username, password, host) end function Connection:drop_user(username, host) - if str.is_blank(username) then + if string.is_blank(username) then error("Username cannot be empty") end host = host or "%" - local query = str.template("DROP USER IF EXISTS '${username}'@'${host}'", { + local query = string.template("DROP USER IF EXISTS '${username}'@'${host}'", { username = username, host = host }) @@ -573,16 +572,16 @@ function Connection:drop_user(username, host) end function Connection:grant(privileges, database, table_name, username, host) - if str.is_blank(privileges) or str.is_blank(database) or str.is_blank(username) then + if string.is_blank(privileges) or string.is_blank(database) or string.is_blank(username) then error("Privileges, database, and username cannot be empty") end host = host or "%" table_name = table_name or "*" - local object = str.template("${database}.${table}", {database = database, table = table_name}) + local object = string.template("${database}.${table}", {database = database, table = table_name}) - local query = str.template("GRANT ${privileges} ON ${object} TO '${username}'@'${host}'", { - privileges = str.upper(privileges), + local query = string.template("GRANT ${privileges} ON ${object} TO '${username}'@'${host}'", { + privileges = string.upper(privileges), object = object, username = username, host = host @@ -591,16 +590,16 @@ function Connection:grant(privileges, database, table_name, username, host) end function Connection:revoke(privileges, database, table_name, username, host) - if str.is_blank(privileges) or str.is_blank(database) or str.is_blank(username) then + if string.is_blank(privileges) or string.is_blank(database) or string.is_blank(username) then error("Privileges, database, and username cannot be empty") end host = host or "%" table_name = table_name or "*" - local object = str.template("${database}.${table}", {database = database, table = table_name}) + local object = string.template("${database}.${table}", {database = database, table = table_name}) - local query = str.template("REVOKE ${privileges} ON ${object} FROM '${username}'@'${host}'", { - privileges = str.upper(privileges), + local query = string.template("REVOKE ${privileges} ON ${object} FROM '${username}'@'${host}'", { + privileges = string.upper(privileges), object = object, username = username, host = host @@ -614,31 +613,31 @@ end -- MySQL variables and configuration function Connection:set_variable(name, value, global) - if str.is_blank(name) then + if string.is_blank(name) then error("Variable name cannot be empty") end local scope = global and "GLOBAL " or "SESSION " - return self:exec(str.template("SET ${scope}${name} = ?", { + return self:exec(string.template("SET ${scope}${name} = ?", { scope = scope, name = name }), value) end function Connection:get_variable(name, global) - if str.is_blank(name) then + if string.is_blank(name) then error("Variable name cannot be empty") end local scope = global and "global." or "session." - return self:query_value(str.template("SELECT @@${scope}${name}", { + return self:query_value(string.template("SELECT @@${scope}${name}", { scope = scope, name = name })) end function Connection:show_variables(pattern) - if pattern and not str.is_blank(pattern) then + if pattern and not string.is_blank(pattern) then return self:query("SHOW VARIABLES LIKE ?", pattern) else return self:query("SHOW VARIABLES") @@ -646,7 +645,7 @@ function Connection:show_variables(pattern) end function Connection:show_status(pattern) - if pattern and not str.is_blank(pattern) then + if pattern and not string.is_blank(pattern) then return self:query("SHOW STATUS LIKE ?", pattern) else return self:query("SHOW STATUS") @@ -655,11 +654,11 @@ end -- Connection management function mysql.connect(dsn) - if str.is_blank(dsn) then + if string.is_blank(dsn) then error("DSN cannot be empty") end - local conn_id = moonshark.sql_connect("mysql", str.trim(dsn)) + local conn_id = moonshark.sql_connect("mysql", string.trim(dsn)) if conn_id then local conn = {_id = conn_id} setmetatable(conn, Connection) @@ -719,8 +718,8 @@ function mysql.migrate(dsn, migrations, database_name) end -- Use specified database if provided - if database_name and not str.is_blank(database_name) then - conn:exec(str.template("USE ${database}", {database = database_name})) + if database_name and not string.is_blank(database_name) then + conn:exec(string.template("USE ${database}", {database = database_name})) end -- Create migrations table @@ -737,7 +736,7 @@ function mysql.migrate(dsn, migrations, database_name) local error_msg = "" for _, migration in ipairs(migrations) do - if not migration.name or str.is_blank(migration.name) then + if not migration.name or string.is_blank(migration.name) then error_msg = "Migration must have a non-empty name" success = false break @@ -745,7 +744,7 @@ function mysql.migrate(dsn, migrations, database_name) -- Check if migration already applied local existing = conn:query_value("SELECT id FROM _migrations WHERE name = ?", - str.trim(migration.name)) + string.trim(migration.name)) if not existing then local ok, err = pcall(function() if type(migration.up) == "string" then @@ -758,11 +757,11 @@ function mysql.migrate(dsn, migrations, database_name) end) if ok then - conn:exec("INSERT INTO _migrations (name) VALUES (?)", str.trim(migration.name)) - print(str.template("Applied migration: ${name}", {name = migration.name})) + conn:exec("INSERT INTO _migrations (name) VALUES (?)", string.trim(migration.name)) + print(string.template("Applied migration: ${name}", {name = migration.name})) else success = false - error_msg = str.template("Migration '${name}' failed: ${error}", { + error_msg = string.template("Migration '${name}' failed: ${error}", { name = migration.name, error = err or "unknown error" }) @@ -789,7 +788,7 @@ function mysql.to_array(results, column_name) return {} end - if str.is_blank(column_name) then + if string.is_blank(column_name) then error("Column name cannot be empty") end @@ -801,7 +800,7 @@ function mysql.to_map(results, key_column, value_column) return {} end - if str.is_blank(key_column) then + if string.is_blank(key_column) then error("Key column name cannot be empty") end @@ -818,7 +817,7 @@ function mysql.group_by(results, column_name) return {} end - if str.is_blank(column_name) then + if string.is_blank(column_name) then error("Column name cannot be empty") end @@ -838,19 +837,19 @@ function mysql.print_results(results) -- Calculate column widths local widths = {} for _, col in ipairs(columns) do - widths[col] = str.length(col) + widths[col] = string.length(col) end for _, row in ipairs(results) do for _, col in ipairs(columns) do local value = tostring(row[col] or "") - widths[col] = math.max(widths[col], str.length(value)) + widths[col] = math.max(widths[col], string.length(value)) end end -- Print header - local header_parts = tbl.map(columns, function(col) return str.pad_right(col, widths[col]) end) - local separator_parts = tbl.map(columns, function(col) return str.repeat_("-", widths[col]) end) + local header_parts = tbl.map(columns, function(col) return string.pad_right(col, widths[col]) end) + local separator_parts = tbl.map(columns, function(col) return string.repeat_("-", widths[col]) end) print(tbl.concat(header_parts, " | ")) print(tbl.concat(separator_parts, "-+-")) @@ -859,7 +858,7 @@ function mysql.print_results(results) for _, row in ipairs(results) do local value_parts = tbl.map(columns, function(col) local value = tostring(row[col] or "") - return str.pad_right(value, widths[col]) + return string.pad_right(value, widths[col]) end) print(tbl.concat(value_parts, " | ")) end @@ -870,14 +869,14 @@ function mysql.escape_string(str_val) if type(str_val) ~= "string" then return tostring(str_val) end - return str.replace(str_val, "'", "\\'") + return string.replace(str_val, "'", "\\'") end function mysql.escape_identifier(name) - if str.is_blank(name) then + if string.is_blank(name) then error("Identifier name cannot be empty") end - return str.template("`${name}`", {name = str.replace(name, "`", "``")}) + return string.template("`${name}`", {name = string.replace(name, "`", "``")}) end -- DSN builder helper @@ -888,10 +887,10 @@ function mysql.build_dsn(options) local parts = {} - if options.username and not str.is_blank(options.username) then + if options.username and not string.is_blank(options.username) then tbl.insert(parts, options.username) - if options.password and not str.is_blank(options.password) then - parts[#parts] = str.template("${user}:${pass}", { + if options.password and not string.is_blank(options.password) then + parts[#parts] = string.template("${user}:${pass}", { user = parts[#parts], pass = options.password }) @@ -899,22 +898,22 @@ function mysql.build_dsn(options) parts[#parts] = parts[#parts] .. "@" end - if options.protocol and not str.is_blank(options.protocol) then - tbl.insert(parts, str.template("${protocol}(", {protocol = options.protocol})) - if options.host and not str.is_blank(options.host) then + if options.protocol and not string.is_blank(options.protocol) then + tbl.insert(parts, string.template("${protocol}(", {protocol = options.protocol})) + if options.host and not string.is_blank(options.host) then tbl.insert(parts, options.host) if options.port then - parts[#parts] = str.template("${host}:${port}", { + parts[#parts] = string.template("${host}:${port}", { host = parts[#parts], port = tostring(options.port) }) end end parts[#parts] = parts[#parts] .. ")" - elseif options.host and not str.is_blank(options.host) then - local host_part = str.template("tcp(${host}", {host = options.host}) + elseif options.host and not string.is_blank(options.host) then + local host_part = string.template("tcp(${host}", {host = options.host}) if options.port then - host_part = str.template("${host}:${port}", { + host_part = string.template("${host}:${port}", { host = host_part, port = tostring(options.port) }) @@ -922,27 +921,27 @@ function mysql.build_dsn(options) tbl.insert(parts, host_part .. ")") end - if options.database and not str.is_blank(options.database) then - tbl.insert(parts, str.template("/${database}", {database = options.database})) + if options.database and not string.is_blank(options.database) then + tbl.insert(parts, string.template("/${database}", {database = options.database})) end -- Add parameters local params = {} - if options.charset and not str.is_blank(options.charset) then - tbl.insert(params, str.template("charset=${charset}", {charset = options.charset})) + if options.charset and not string.is_blank(options.charset) then + tbl.insert(params, string.template("charset=${charset}", {charset = options.charset})) end if options.parseTime ~= nil then - tbl.insert(params, str.template("parseTime=${parse}", {parse = tostring(options.parseTime)})) + tbl.insert(params, string.template("parseTime=${parse}", {parse = tostring(options.parseTime)})) end - if options.timeout and not str.is_blank(options.timeout) then - tbl.insert(params, str.template("timeout=${timeout}", {timeout = options.timeout})) + if options.timeout and not string.is_blank(options.timeout) then + tbl.insert(params, string.template("timeout=${timeout}", {timeout = options.timeout})) end - if options.tls and not str.is_blank(options.tls) then - tbl.insert(params, str.template("tls=${tls}", {tls = options.tls})) + if options.tls and not string.is_blank(options.tls) then + tbl.insert(params, string.template("tls=${tls}", {tls = options.tls})) end if #params > 0 then - tbl.insert(parts, str.template("?${params}", {params = tbl.concat(params, "&")})) + tbl.insert(parts, string.template("?${params}", {params = tbl.concat(params, "&")})) end return tbl.concat(parts, "") diff --git a/modules/postgres/postgres.lua b/modules/postgres/postgres.lua index e563201..709ddb3 100644 --- a/modules/postgres/postgres.lua +++ b/modules/postgres/postgres.lua @@ -1,4 +1,3 @@ -local str = require("string") local tbl = require("table") local postgres = {} @@ -25,7 +24,7 @@ function Connection:query(query_str, ...) if not self._id then error("Connection is closed") end - query_str = str.normalize_whitespace(query_str) + query_str = string.normalize_whitespace(query_str) return moonshark.sql_query(self._id, query_str, ...) end @@ -33,7 +32,7 @@ function Connection:exec(query_str, ...) if not self._id then error("Connection is closed") end - query_str = str.normalize_whitespace(query_str) + query_str = string.normalize_whitespace(query_str) return moonshark.sql_exec(self._id, query_str, ...) end @@ -85,20 +84,20 @@ function Connection:begin() if not tx.active then error("Transaction is not active") end - if str.is_blank(name) then + if string.is_blank(name) then error("Savepoint name cannot be empty") end - return tx.conn:exec(str.template("SAVEPOINT ${name}", {name = name})) + return tx.conn:exec(string.template("SAVEPOINT ${name}", {name = name})) end, rollback_to = function(tx, name) if not tx.active then error("Transaction is not active") end - if str.is_blank(name) then + if string.is_blank(name) then error("Savepoint name cannot be empty") end - return tx.conn:exec(str.template("ROLLBACK TO SAVEPOINT ${name}", {name = name})) + return tx.conn:exec(string.template("ROLLBACK TO SAVEPOINT ${name}", {name = name})) end, query = function(tx, query_str, ...) @@ -140,7 +139,7 @@ local function build_postgres_params(data) local placeholders = {} for i = 1, #keys do - tbl.insert(placeholders, str.template("$${num}", {num = tostring(i)})) + tbl.insert(placeholders, string.template("$${num}", {num = tostring(i)})) end return keys, values, placeholders, #keys @@ -148,20 +147,20 @@ end -- Simplified query builders using table utilities function Connection:insert(table_name, data, returning) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end local keys, values, placeholders = build_postgres_params(data) - local query = str.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", { + local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", { table = table_name, columns = tbl.concat(keys, ", "), placeholders = tbl.concat(placeholders, ", ") }) - if returning and not str.is_blank(returning) then - query = str.template("${query} RETURNING ${returning}", { + if returning and not string.is_blank(returning) then + query = string.template("${query} RETURNING ${returning}", { query = query, returning = returning }) @@ -172,25 +171,25 @@ function Connection:insert(table_name, data, returning) end function Connection:upsert(table_name, data, conflict_columns, returning) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end local keys, values, placeholders = build_postgres_params(data) local updates = tbl.map(keys, function(key) - return str.template("${key} = EXCLUDED.${key}", {key = key}) + return string.template("${key} = EXCLUDED.${key}", {key = key}) end) local conflict_clause = "" if conflict_columns then if type(conflict_columns) == "string" then - conflict_clause = str.template("(${columns})", {columns = conflict_columns}) + conflict_clause = string.template("(${columns})", {columns = conflict_columns}) else - conflict_clause = str.template("(${columns})", {columns = tbl.concat(conflict_columns, ", ")}) + conflict_clause = string.template("(${columns})", {columns = tbl.concat(conflict_columns, ", ")}) end end - local query = str.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT ${conflict} DO UPDATE SET ${updates}", { + local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT ${conflict} DO UPDATE SET ${updates}", { table = table_name, columns = tbl.concat(keys, ", "), placeholders = tbl.concat(placeholders, ", "), @@ -198,8 +197,8 @@ function Connection:upsert(table_name, data, conflict_columns, returning) updates = tbl.concat(updates, ", ") }) - if returning and not str.is_blank(returning) then - query = str.template("${query} RETURNING ${returning}", { + if returning and not string.is_blank(returning) then + query = string.template("${query} RETURNING ${returning}", { query = query, returning = returning }) @@ -210,10 +209,10 @@ function Connection:upsert(table_name, data, conflict_columns, returning) end function Connection:update(table_name, data, where_clause, returning, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - if str.is_blank(where_clause) then + if string.is_blank(where_clause) then error("WHERE clause cannot be empty for UPDATE") end @@ -223,7 +222,7 @@ function Connection:update(table_name, data, where_clause, returning, ...) local sets = {} for i, key in ipairs(keys) do - tbl.insert(sets, str.template("${key} = $${num}", { + tbl.insert(sets, string.template("${key} = $${num}", { key = key, num = tostring(i) })) @@ -235,18 +234,18 @@ function Connection:update(table_name, data, where_clause, returning, ...) for i = 1, #where_args do param_count = param_count + 1 tbl.insert(values, where_args[i]) - where_clause_with_params = str.replace(where_clause_with_params, "?", - str.template("$${num}", {num = tostring(param_count)}), 1) + where_clause_with_params = string.replace(where_clause_with_params, "?", + string.template("$${num}", {num = tostring(param_count)}), 1) end - local query = str.template("UPDATE ${table} SET ${sets} WHERE ${where}", { + local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", { table = table_name, sets = tbl.concat(sets, ", "), where = where_clause_with_params }) - if returning and not str.is_blank(returning) then - query = str.template("${query} RETURNING ${returning}", { + if returning and not string.is_blank(returning) then + query = string.template("${query} RETURNING ${returning}", { query = query, returning = returning }) @@ -257,10 +256,10 @@ function Connection:update(table_name, data, where_clause, returning, ...) end function Connection:delete(table_name, where_clause, returning, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - if str.is_blank(where_clause) then + if string.is_blank(where_clause) then error("WHERE clause cannot be empty for DELETE") end @@ -270,17 +269,17 @@ function Connection:delete(table_name, where_clause, returning, ...) local where_clause_with_params = where_clause for i = 1, #where_args do tbl.insert(values, where_args[i]) - where_clause_with_params = str.replace(where_clause_with_params, "?", - str.template("$${num}", {num = tostring(i)}), 1) + where_clause_with_params = string.replace(where_clause_with_params, "?", + string.template("$${num}", {num = tostring(i)}), 1) end - local query = str.template("DELETE FROM ${table} WHERE ${where}", { + local query = string.template("DELETE FROM ${table} WHERE ${where}", { table = table_name, where = where_clause_with_params }) - if returning and not str.is_blank(returning) then - query = str.template("${query} RETURNING ${returning}", { + if returning and not string.is_blank(returning) then + query = string.template("${query} RETURNING ${returning}", { query = query, returning = returning }) @@ -291,7 +290,7 @@ function Connection:delete(table_name, where_clause, returning, ...) end function Connection:select(table_name, columns, where_clause, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -301,25 +300,25 @@ function Connection:select(table_name, columns, where_clause, ...) end local query - if where_clause and not str.is_blank(where_clause) then + if where_clause and not string.is_blank(where_clause) then -- Handle WHERE clause parameters local where_args = {...} local values = {} local where_clause_with_params = where_clause for i = 1, #where_args do tbl.insert(values, where_args[i]) - where_clause_with_params = str.replace(where_clause_with_params, "?", - str.template("$${num}", {num = tostring(i)}), 1) + where_clause_with_params = string.replace(where_clause_with_params, "?", + string.template("$${num}", {num = tostring(i)}), 1) end - query = str.template("SELECT ${columns} FROM ${table} WHERE ${where}", { + query = string.template("SELECT ${columns} FROM ${table} WHERE ${where}", { columns = columns, table = table_name, where = where_clause_with_params }) return self:query(query, unpack(values)) else - query = str.template("SELECT ${columns} FROM ${table}", { + query = string.template("SELECT ${columns} FROM ${table}", { columns = columns, table = table_name }) @@ -329,20 +328,20 @@ end -- Enhanced PostgreSQL schema helpers function Connection:table_exists(table_name, schema_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then return false end schema_name = schema_name or "public" local result = self:query_value( "SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = $2", - str.trim(schema_name), str.trim(table_name) + string.trim(schema_name), string.trim(table_name) ) return result ~= nil end function Connection:column_exists(table_name, column_name, schema_name) - if str.is_blank(table_name) or str.is_blank(column_name) then + if string.is_blank(table_name) or string.is_blank(column_name) then return false end @@ -350,29 +349,29 @@ function Connection:column_exists(table_name, column_name, schema_name) local result = self:query_value([[ SELECT column_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND column_name = $3 - ]], str.trim(schema_name), str.trim(table_name), str.trim(column_name)) + ]], string.trim(schema_name), string.trim(table_name), string.trim(column_name)) return result ~= nil end function Connection:create_table(table_name, schema) - if str.is_blank(table_name) or str.is_blank(schema) then + if string.is_blank(table_name) or string.is_blank(schema) then error("Table name and schema cannot be empty") end - local query = str.template("CREATE TABLE IF NOT EXISTS ${table} (${schema})", { + local query = string.template("CREATE TABLE IF NOT EXISTS ${table} (${schema})", { table = table_name, - schema = str.trim(schema) + schema = string.trim(schema) }) return self:exec(query) end function Connection:drop_table(table_name, cascade) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end local cascade_clause = cascade and " CASCADE" or "" - local query = str.template("DROP TABLE IF EXISTS ${table}${cascade}", { + local query = string.template("DROP TABLE IF EXISTS ${table}${cascade}", { table = table_name, cascade = cascade_clause }) @@ -380,24 +379,24 @@ function Connection:drop_table(table_name, cascade) end function Connection:add_column(table_name, column_def) - if str.is_blank(table_name) or str.is_blank(column_def) then + if string.is_blank(table_name) or string.is_blank(column_def) then error("Table name and column definition cannot be empty") end - local query = str.template("ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${column}", { + local query = string.template("ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${column}", { table = table_name, - column = str.trim(column_def) + column = string.trim(column_def) }) return self:exec(query) end function Connection:drop_column(table_name, column_name, cascade) - if str.is_blank(table_name) or str.is_blank(column_name) then + if string.is_blank(table_name) or string.is_blank(column_name) then error("Table name and column name cannot be empty") end local cascade_clause = cascade and " CASCADE" or "" - local query = str.template("ALTER TABLE ${table} DROP COLUMN IF EXISTS ${column}${cascade}", { + local query = string.template("ALTER TABLE ${table} DROP COLUMN IF EXISTS ${column}${cascade}", { table = table_name, column = column_name, cascade = cascade_clause @@ -406,15 +405,15 @@ function Connection:drop_column(table_name, column_name, cascade) end function Connection:create_index(index_name, table_name, columns, unique, method) - if str.is_blank(index_name) or str.is_blank(table_name) then + if string.is_blank(index_name) or string.is_blank(table_name) then error("Index name and table name cannot be empty") end local unique_clause = unique and "UNIQUE " or "" - local method_clause = method and str.template(" USING ${method}", {method = str.upper(method)}) or "" + local method_clause = method and string.template(" USING ${method}", {method = string.upper(method)}) or "" local columns_str = type(columns) == "table" and tbl.concat(columns, ", ") or tostring(columns) - local query = str.template("CREATE ${unique}INDEX IF NOT EXISTS ${index} ON ${table}${method} (${columns})", { + local query = string.template("CREATE ${unique}INDEX IF NOT EXISTS ${index} ON ${table}${method} (${columns})", { unique = unique_clause, index = index_name, table = table_name, @@ -425,12 +424,12 @@ function Connection:create_index(index_name, table_name, columns, unique, method end function Connection:drop_index(index_name, cascade) - if str.is_blank(index_name) then + if string.is_blank(index_name) then error("Index name cannot be empty") end local cascade_clause = cascade and " CASCADE" or "" - local query = str.template("DROP INDEX IF EXISTS ${index}${cascade}", { + local query = string.template("DROP INDEX IF EXISTS ${index}${cascade}", { index = index_name, cascade = cascade_clause }) @@ -440,32 +439,32 @@ end -- PostgreSQL-specific functions function Connection:vacuum(table_name, analyze) local analyze_clause = analyze and " ANALYZE" or "" - local table_clause = table_name and str.template(" ${table}", {table = table_name}) or "" - return self:exec(str.template("VACUUM${analyze}${table}", { + local table_clause = table_name and string.template(" ${table}", {table = table_name}) or "" + return self:exec(string.template("VACUUM${analyze}${table}", { analyze = analyze_clause, table = table_clause })) end function Connection:analyze(table_name) - local table_clause = table_name and str.template(" ${table}", {table = table_name}) or "" - return self:exec(str.template("ANALYZE${table}", {table = table_clause})) + local table_clause = table_name and string.template(" ${table}", {table = table_name}) or "" + return self:exec(string.template("ANALYZE${table}", {table = table_clause})) end function Connection:reindex(name, type) - if str.is_blank(name) then + if string.is_blank(name) then error("Name cannot be empty for REINDEX") end type = type or "INDEX" local valid_types = {"INDEX", "TABLE", "SCHEMA", "DATABASE", "SYSTEM"} - local type_upper = str.upper(type) + local type_upper = string.upper(type) if not tbl.contains(valid_types, type_upper) then - error(str.template("Invalid REINDEX type: ${type}", {type = type})) + error(string.template("Invalid REINDEX type: ${type}", {type = type})) end - return self:exec(str.template("REINDEX ${type} ${name}", { + return self:exec(string.template("REINDEX ${type} ${name}", { type = type_upper, name = name })) @@ -473,17 +472,17 @@ end -- PostgreSQL settings and introspection function Connection:show(setting) - if str.is_blank(setting) then + if string.is_blank(setting) then error("Setting name cannot be empty") end - return self:query_value(str.template("SHOW ${setting}", {setting = setting})) + return self:query_value(string.template("SHOW ${setting}", {setting = setting})) end function Connection:set(setting, value) - if str.is_blank(setting) then + if string.is_blank(setting) then error("Setting name cannot be empty") end - return self:exec(str.template("SET ${setting} = ${value}", { + return self:exec(string.template("SET ${setting} = ${value}", { setting = setting, value = tostring(value) })) @@ -508,11 +507,11 @@ end function Connection:list_tables(schema_name) schema_name = schema_name or "public" return self:query("SELECT tablename FROM pg_tables WHERE schemaname = $1 ORDER BY tablename", - str.trim(schema_name)) + string.trim(schema_name)) end function Connection:describe_table(table_name, schema_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -522,64 +521,64 @@ function Connection:describe_table(table_name, schema_name) FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 ORDER BY ordinal_position - ]], str.trim(schema_name), str.trim(table_name)) + ]], string.trim(schema_name), string.trim(table_name)) end -- JSON/JSONB helpers function Connection:json_extract(column, path) - if str.is_blank(column) or str.is_blank(path) then + if string.is_blank(column) or string.is_blank(path) then error("Column and path cannot be empty") end - return str.template("${column}->'${path}'", {column = column, path = path}) + return string.template("${column}->'${path}'", {column = column, path = path}) end function Connection:json_extract_text(column, path) - if str.is_blank(column) or str.is_blank(path) then + if string.is_blank(column) or string.is_blank(path) then error("Column and path cannot be empty") end - return str.template("${column}->>'${path}'", {column = column, path = path}) + return string.template("${column}->>'${path}'", {column = column, path = path}) end function Connection:jsonb_contains(column, value) - if str.is_blank(column) or str.is_blank(value) then + if string.is_blank(column) or string.is_blank(value) then error("Column and value cannot be empty") end - return str.template("${column} @> '${value}'", {column = column, value = value}) + return string.template("${column} @> '${value}'", {column = column, value = value}) end function Connection:jsonb_contained_by(column, value) - if str.is_blank(column) or str.is_blank(value) then + if string.is_blank(column) or string.is_blank(value) then error("Column and value cannot be empty") end - return str.template("${column} <@ '${value}'", {column = column, value = value}) + return string.template("${column} <@ '${value}'", {column = column, value = value}) end -- Array helpers function Connection:array_contains(column, value) - if str.is_blank(column) then + if string.is_blank(column) then error("Column cannot be empty") end - return str.template("$1 = ANY(${column})", {column = column}) + return string.template("$1 = ANY(${column})", {column = column}) end function Connection:array_length(column) - if str.is_blank(column) then + if string.is_blank(column) then error("Column cannot be empty") end - return str.template("array_length(${column}, 1)", {column = column}) + return string.template("array_length(${column}, 1)", {column = column}) end -- Connection management function postgres.parse_dsn(dsn) - if str.is_blank(dsn) then + if string.is_blank(dsn) then return nil, "DSN cannot be empty" end local parts = {} - for pair in str.trim(dsn):gmatch("[^%s]+") do + for pair in string.trim(dsn):gmatch("[^%s]+") do local key, value = pair:match("([^=]+)=(.+)") if key and value then - parts[str.trim(key)] = str.trim(value) + parts[string.trim(key)] = string.trim(value) end end @@ -587,11 +586,11 @@ function postgres.parse_dsn(dsn) end function postgres.connect(dsn) - if str.is_blank(dsn) then + if string.is_blank(dsn) then error("DSN cannot be empty") end - local conn_id = moonshark.sql_connect("postgres", str.trim(dsn)) + local conn_id = moonshark.sql_connect("postgres", string.trim(dsn)) if conn_id then local conn = {_id = conn_id} setmetatable(conn, Connection) @@ -664,14 +663,14 @@ function postgres.migrate(dsn, migrations, schema) local error_msg = "" for _, migration in ipairs(migrations) do - if not migration.name or str.is_blank(migration.name) then + if not migration.name or string.is_blank(migration.name) then error_msg = "Migration must have a non-empty name" success = false break end local existing = conn:query_value("SELECT id FROM _migrations WHERE name = $1", - str.trim(migration.name)) + string.trim(migration.name)) if not existing then local ok, err = pcall(function() if type(migration.up) == "string" then @@ -684,11 +683,11 @@ function postgres.migrate(dsn, migrations, schema) end) if ok then - conn:exec("INSERT INTO _migrations (name) VALUES ($1)", str.trim(migration.name)) - print(str.template("Applied migration: ${name}", {name = migration.name})) + conn:exec("INSERT INTO _migrations (name) VALUES ($1)", string.trim(migration.name)) + print(string.template("Applied migration: ${name}", {name = migration.name})) else success = false - error_msg = str.template("Migration '${name}' failed: ${error}", { + error_msg = string.template("Migration '${name}' failed: ${error}", { name = migration.name, error = err or "unknown error" }) @@ -715,7 +714,7 @@ function postgres.to_array(results, column_name) return {} end - if str.is_blank(column_name) then + if string.is_blank(column_name) then error("Column name cannot be empty") end @@ -727,7 +726,7 @@ function postgres.to_map(results, key_column, value_column) return {} end - if str.is_blank(key_column) then + if string.is_blank(key_column) then error("Key column name cannot be empty") end @@ -744,7 +743,7 @@ function postgres.group_by(results, column_name) return {} end - if str.is_blank(column_name) then + if string.is_blank(column_name) then error("Column name cannot be empty") end @@ -764,19 +763,19 @@ function postgres.print_results(results) -- Calculate column widths local widths = {} for _, col in ipairs(columns) do - widths[col] = str.length(col) + widths[col] = string.length(col) end for _, row in ipairs(results) do for _, col in ipairs(columns) do local value = tostring(row[col] or "") - widths[col] = math.max(widths[col], str.length(value)) + widths[col] = math.max(widths[col], string.length(value)) end end -- Print header - local header_parts = tbl.map(columns, function(col) return str.pad_right(col, widths[col]) end) - local separator_parts = tbl.map(columns, function(col) return str.repeat_("-", widths[col]) end) + local header_parts = tbl.map(columns, function(col) return string.pad_right(col, widths[col]) end) + local separator_parts = tbl.map(columns, function(col) return string.repeat_("-", widths[col]) end) print(tbl.concat(header_parts, " | ")) print(tbl.concat(separator_parts, "-+-")) @@ -785,22 +784,22 @@ function postgres.print_results(results) for _, row in ipairs(results) do local value_parts = tbl.map(columns, function(col) local value = tostring(row[col] or "") - return str.pad_right(value, widths[col]) + return string.pad_right(value, widths[col]) end) print(tbl.concat(value_parts, " | ")) end end function postgres.escape_identifier(name) - if str.is_blank(name) then + if string.is_blank(name) then error("Identifier name cannot be empty") end - return str.template('"${name}"', {name = str.replace(name, '"', '""')}) + return string.template('"${name}"', {name = string.replace(name, '"', '""')}) end function postgres.escape_literal(value) if type(value) == "string" then - return str.template("'${value}'", {value = str.replace(value, "'", "''")}) + return string.template("'${value}'", {value = string.replace(value, "'", "''")}) end return tostring(value) end diff --git a/modules/sqlite/sqlite.lua b/modules/sqlite/sqlite.lua index 1e281e4..6677608 100644 --- a/modules/sqlite/sqlite.lua +++ b/modules/sqlite/sqlite.lua @@ -1,4 +1,3 @@ -local str = require("string") local tbl = require("table") local sqlite = {} @@ -25,7 +24,7 @@ function Connection:query(query_str, ...) if not self._id then error("Connection is closed") end - query_str = str.normalize_whitespace(query_str) + query_str = string.normalize_whitespace(query_str) return moonshark.sql_query(self._id, query_str, ...) end @@ -33,7 +32,7 @@ function Connection:exec(query_str, ...) if not self._id then error("Connection is closed") end - query_str = str.normalize_whitespace(query_str) + query_str = string.normalize_whitespace(query_str) return moonshark.sql_exec(self._id, query_str, ...) end @@ -115,7 +114,7 @@ end -- Simplified query builders using table utilities function Connection:insert(table_name, data) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -123,7 +122,7 @@ function Connection:insert(table_name, data) local values = tbl.values(data) local placeholders = tbl.map(keys, function() return "?" end) - local query = str.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", { + local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", { table = table_name, columns = tbl.concat(keys, ", "), placeholders = tbl.concat(placeholders, ", ") @@ -133,7 +132,7 @@ function Connection:insert(table_name, data) end function Connection:upsert(table_name, data, conflict_columns) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -141,19 +140,19 @@ function Connection:upsert(table_name, data, conflict_columns) local values = tbl.values(data) local placeholders = tbl.map(keys, function() return "?" end) local updates = tbl.map(keys, function(key) - return str.template("${key} = excluded.${key}", {key = key}) + return string.template("${key} = excluded.${key}", {key = key}) end) local conflict_clause = "" if conflict_columns then if type(conflict_columns) == "string" then - conflict_clause = str.template("(${columns})", {columns = conflict_columns}) + conflict_clause = string.template("(${columns})", {columns = conflict_columns}) else - conflict_clause = str.template("(${columns})", {columns = tbl.concat(conflict_columns, ", ")}) + conflict_clause = string.template("(${columns})", {columns = tbl.concat(conflict_columns, ", ")}) end end - local query = str.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT ${conflict} DO UPDATE SET ${updates}", { + local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT ${conflict} DO UPDATE SET ${updates}", { table = table_name, columns = tbl.concat(keys, ", "), placeholders = tbl.concat(placeholders, ", "), @@ -165,20 +164,20 @@ function Connection:upsert(table_name, data, conflict_columns) end function Connection:update(table_name, data, where_clause, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - if str.is_blank(where_clause) then + if string.is_blank(where_clause) then error("WHERE clause cannot be empty for UPDATE") end local keys = tbl.keys(data) local values = tbl.values(data) local sets = tbl.map(keys, function(key) - return str.template("${key} = ?", {key = key}) + return string.template("${key} = ?", {key = key}) end) - local query = str.template("UPDATE ${table} SET ${sets} WHERE ${where}", { + local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", { table = table_name, sets = tbl.concat(sets, ", "), where = where_clause @@ -192,14 +191,14 @@ function Connection:update(table_name, data, where_clause, ...) end function Connection:delete(table_name, where_clause, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - if str.is_blank(where_clause) then + if string.is_blank(where_clause) then error("WHERE clause cannot be empty for DELETE") end - local query = str.template("DELETE FROM ${table} WHERE ${where}", { + local query = string.template("DELETE FROM ${table} WHERE ${where}", { table = table_name, where = where_clause }) @@ -207,7 +206,7 @@ function Connection:delete(table_name, where_clause, ...) end function Connection:select(table_name, columns, where_clause, ...) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end @@ -217,15 +216,15 @@ function Connection:select(table_name, columns, where_clause, ...) end local query - if where_clause and not str.is_blank(where_clause) then - query = str.template("SELECT ${columns} FROM ${table} WHERE ${where}", { + if where_clause and not string.is_blank(where_clause) then + query = string.template("SELECT ${columns} FROM ${table} WHERE ${where}", { columns = columns, table = table_name, where = where_clause }) return self:query(query, ...) else - query = str.template("SELECT ${columns} FROM ${table}", { + query = string.template("SELECT ${columns} FROM ${table}", { columns = columns, table = table_name }) @@ -235,73 +234,73 @@ end -- Schema helpers function Connection:table_exists(table_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then return false end local result = self:query_value( "SELECT name FROM sqlite_master WHERE type='table' AND name=?", - str.trim(table_name) + string.trim(table_name) ) return result ~= nil end function Connection:column_exists(table_name, column_name) - if str.is_blank(table_name) or str.is_blank(column_name) then + if string.is_blank(table_name) or string.is_blank(column_name) then return false end - local result = self:query(str.template("PRAGMA table_info(${table})", {table = table_name})) + local result = self:query(string.template("PRAGMA table_info(${table})", {table = table_name})) if result then return tbl.any(result, function(row) - return str.iequals(row.name, str.trim(column_name)) + return string.iequals(row.name, string.trim(column_name)) end) end return false end function Connection:create_table(table_name, schema) - if str.is_blank(table_name) or str.is_blank(schema) then + if string.is_blank(table_name) or string.is_blank(schema) then error("Table name and schema cannot be empty") end - local query = str.template("CREATE TABLE IF NOT EXISTS ${table} (${schema})", { + local query = string.template("CREATE TABLE IF NOT EXISTS ${table} (${schema})", { table = table_name, - schema = str.trim(schema) + schema = string.trim(schema) }) return self:exec(query) end function Connection:drop_table(table_name) - if str.is_blank(table_name) then + if string.is_blank(table_name) then error("Table name cannot be empty") end - local query = str.template("DROP TABLE IF EXISTS ${table}", {table = table_name}) + local query = string.template("DROP TABLE IF EXISTS ${table}", {table = table_name}) return self:exec(query) end function Connection:add_column(table_name, column_def) - if str.is_blank(table_name) or str.is_blank(column_def) then + if string.is_blank(table_name) or string.is_blank(column_def) then error("Table name and column definition cannot be empty") end - local query = str.template("ALTER TABLE ${table} ADD COLUMN ${column}", { + local query = string.template("ALTER TABLE ${table} ADD COLUMN ${column}", { table = table_name, - column = str.trim(column_def) + column = string.trim(column_def) }) return self:exec(query) end function Connection:create_index(index_name, table_name, columns, unique) - if str.is_blank(index_name) or str.is_blank(table_name) then + if string.is_blank(index_name) or string.is_blank(table_name) then error("Index name and table name cannot be empty") end local unique_clause = unique and "UNIQUE " or "" local columns_str = type(columns) == "table" and tbl.concat(columns, ", ") or tostring(columns) - local query = str.template("CREATE ${unique}INDEX IF NOT EXISTS ${index} ON ${table} (${columns})", { + local query = string.template("CREATE ${unique}INDEX IF NOT EXISTS ${index} ON ${table} (${columns})", { unique = unique_clause, index = index_name, table = table_name, @@ -311,11 +310,11 @@ function Connection:create_index(index_name, table_name, columns, unique) end function Connection:drop_index(index_name) - if str.is_blank(index_name) then + if string.is_blank(index_name) then error("Index name cannot be empty") end - local query = str.template("DROP INDEX IF EXISTS ${index}", {index = index_name}) + local query = string.template("DROP INDEX IF EXISTS ${index}", {index = index_name}) return self:exec(query) end @@ -334,29 +333,29 @@ end function Connection:foreign_keys(enabled) local value = enabled and "ON" or "OFF" - return self:exec(str.template("PRAGMA foreign_keys = ${value}", {value = value})) + return self:exec(string.template("PRAGMA foreign_keys = ${value}", {value = value})) end function Connection:journal_mode(mode) mode = mode or "WAL" local valid_modes = {"DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"} - if not tbl.contains(tbl.map(valid_modes, str.upper), str.upper(mode)) then + if not tbl.contains(tbl.map(valid_modes, string.upper), string.upper(mode)) then error("Invalid journal mode: " .. mode) end - return self:query(str.template("PRAGMA journal_mode = ${mode}", {mode = str.upper(mode)})) + return self:query(string.template("PRAGMA journal_mode = ${mode}", {mode = string.upper(mode)})) end function Connection:synchronous(level) level = level or "NORMAL" local valid_levels = {"OFF", "NORMAL", "FULL", "EXTRA"} - if not tbl.contains(valid_levels, str.upper(level)) then + if not tbl.contains(valid_levels, string.upper(level)) then error("Invalid synchronous level: " .. level) end - return self:exec(str.template("PRAGMA synchronous = ${level}", {level = str.upper(level)})) + return self:exec(string.template("PRAGMA synchronous = ${level}", {level = string.upper(level)})) end function Connection:cache_size(size) @@ -364,18 +363,18 @@ function Connection:cache_size(size) if type(size) ~= "number" then error("Cache size must be a number") end - return self:exec(str.template("PRAGMA cache_size = ${size}", {size = tostring(size)})) + return self:exec(string.template("PRAGMA cache_size = ${size}", {size = tostring(size)})) end function Connection:temp_store(mode) mode = mode or "MEMORY" local valid_modes = {"DEFAULT", "FILE", "MEMORY"} - if not tbl.contains(valid_modes, str.upper(mode)) then + if not tbl.contains(valid_modes, string.upper(mode)) then error("Invalid temp_store mode: " .. mode) end - return self:exec(str.template("PRAGMA temp_store = ${mode}", {mode = str.upper(mode)})) + return self:exec(string.template("PRAGMA temp_store = ${mode}", {mode = string.upper(mode)})) end -- Connection management @@ -383,8 +382,8 @@ function sqlite.open(database_path) database_path = database_path or ":memory:" if database_path ~= ":memory:" then - database_path = str.trim(database_path) - if str.is_blank(database_path) then + database_path = string.trim(database_path) + if string.is_blank(database_path) then database_path = ":memory:" end end @@ -404,7 +403,7 @@ sqlite.connect = sqlite.open function sqlite.query(database_path, query_str, ...) local conn = sqlite.open(database_path) if not conn then - error(str.template("Failed to open SQLite database: ${path}", { + error(string.template("Failed to open SQLite database: ${path}", { path = database_path or ":memory:" })) end @@ -417,7 +416,7 @@ end function sqlite.exec(database_path, query_str, ...) local conn = sqlite.open(database_path) if not conn then - error(str.template("Failed to open SQLite database: ${path}", { + error(string.template("Failed to open SQLite database: ${path}", { path = database_path or ":memory:" })) end @@ -465,14 +464,14 @@ function sqlite.migrate(database_path, migrations) local error_msg = "" for _, migration in ipairs(migrations) do - if not migration.name or str.is_blank(migration.name) then + if not migration.name or string.is_blank(migration.name) then error_msg = "Migration must have a non-empty name" success = false break end local existing = conn:query_value("SELECT id FROM _migrations WHERE name = ?", - str.trim(migration.name)) + string.trim(migration.name)) if not existing then local ok, err = pcall(function() if type(migration.up) == "string" then @@ -485,11 +484,11 @@ function sqlite.migrate(database_path, migrations) end) if ok then - conn:exec("INSERT INTO _migrations (name) VALUES (?)", str.trim(migration.name)) - print(str.template("Applied migration: ${name}", {name = migration.name})) + conn:exec("INSERT INTO _migrations (name) VALUES (?)", string.trim(migration.name)) + print(string.template("Applied migration: ${name}", {name = migration.name})) else success = false - error_msg = str.template("Migration '${name}' failed: ${error}", { + error_msg = string.template("Migration '${name}' failed: ${error}", { name = migration.name, error = err or "unknown error" }) @@ -516,7 +515,7 @@ function sqlite.to_array(results, column_name) return {} end - if str.is_blank(column_name) then + if string.is_blank(column_name) then error("Column name cannot be empty") end @@ -528,7 +527,7 @@ function sqlite.to_map(results, key_column, value_column) return {} end - if str.is_blank(key_column) then + if string.is_blank(key_column) then error("Key column name cannot be empty") end @@ -545,7 +544,7 @@ function sqlite.group_by(results, column_name) return {} end - if str.is_blank(column_name) then + if string.is_blank(column_name) then error("Column name cannot be empty") end @@ -563,18 +562,18 @@ function sqlite.print_results(results) tbl.sort(columns) -- Calculate column widths - local widths = tbl.map_values(tbl.to_map(columns, function(col) return col end, function(col) return str.length(col) end), function(width) return width end) + local widths = tbl.map_values(tbl.to_map(columns, function(col) return col end, function(col) return string.length(col) end), function(width) return width end) for _, row in ipairs(results) do for _, col in ipairs(columns) do local value = tostring(row[col] or "") - widths[col] = math.max(widths[col], str.length(value)) + widths[col] = math.max(widths[col], string.length(value)) end end -- Print header - local header_parts = tbl.map(columns, function(col) return str.pad_right(col, widths[col]) end) - local separator_parts = tbl.map(columns, function(col) return str.repeat_("-", widths[col]) end) + local header_parts = tbl.map(columns, function(col) return string.pad_right(col, widths[col]) end) + local separator_parts = tbl.map(columns, function(col) return string.repeat_("-", widths[col]) end) print(tbl.concat(header_parts, " | ")) print(tbl.concat(separator_parts, "-+-")) @@ -583,7 +582,7 @@ function sqlite.print_results(results) for _, row in ipairs(results) do local value_parts = tbl.map(columns, function(col) local value = tostring(row[col] or "") - return str.pad_right(value, widths[col]) + return string.pad_right(value, widths[col]) end) print(tbl.concat(value_parts, " | ")) end