add timestamp features, fix sqlite positional parameters

This commit is contained in:
Sky Johnson 2025-06-05 11:34:19 -05:00
parent bf8ce59b73
commit cf38b947e1
5 changed files with 135 additions and 2 deletions

View File

@ -55,6 +55,9 @@ var renderLuaCode string
//go:embed lua/session.lua
var sessionLuaCode string
//go:embed lua/timestamp.lua
var timestampLuaCode string
// Module represents a Lua module to load
type Module struct {
name string
@ -78,6 +81,7 @@ var modules = []Module{
{"time", timeLuaCode, false},
{"math", mathLuaCode, false},
{"env", envLuaCode, true},
{"timestamp", timestampLuaCode, false},
}
// loadModule loads a single module into the Lua state

View File

@ -18,7 +18,7 @@ function cookie_set(name, value, options)
end
function cookie_get(name)
return __ctx._request_cookies and __ctx._request_cookies[name]
return __ctx.cookies and __ctx.cookies[name]
end
function cookie_delete(name, path, domain)

93
runner/lua/timestamp.lua Normal file
View File

@ -0,0 +1,93 @@
-- timestamp.lua
local timestamp = {}
-- Standard format presets using Lua format codes
local FORMATS = {
iso = "%Y-%m-%dT%H:%M:%SZ",
datetime = "%Y-%m-%d %H:%M:%S",
us_date = "%m/%d/%Y",
us_datetime = "%m/%d/%Y %I:%M:%S %p",
date = "%Y-%m-%d",
time = "%H:%M:%S",
time12 = "%I:%M:%S %p",
readable = "%B %d, %Y %I:%M:%S %p",
compact = "%Y%m%d_%H%M%S"
}
-- Parse input to unix timestamp and microseconds
local function parse_input(input)
local unix_time, micros = 0, 0
if type(input) == "string" then
local frac, secs = input:match("^(0%.%d+)%s+(%d+)$")
if frac and secs then
unix_time = tonumber(secs)
micros = math.floor((tonumber(frac) * 1000000) + 0.5)
else
unix_time = tonumber(input) or 0
end
elseif type(input) == "number" then
unix_time = math.floor(input)
micros = math.floor(((input - unix_time) * 1000000) + 0.5)
end
return unix_time, micros
end
-- Remove leading zeros from number string
local function no_leading_zero(s)
return s:gsub("^0+", "") or "0"
end
-- Main format function
function timestamp.format(input, fmt)
fmt = fmt or "datetime"
local format_str = FORMATS[fmt] or fmt
local unix_time, micros = parse_input(input)
local result = os.date(format_str, unix_time)
-- Handle microseconds if format contains dot
if format_str:find("%.") then
result = result .. string.format(".%06d", micros)
end
return result
end
-- US date/time with no leading zeros
function timestamp.us_datetime_no_zero(input)
local unix_time, micros = parse_input(input)
local month = no_leading_zero(os.date("%m", unix_time))
local day = no_leading_zero(os.date("%d", unix_time))
local year = os.date("%Y", unix_time)
local hour = no_leading_zero(os.date("%I", unix_time))
local min = os.date("%M", unix_time)
local sec = os.date("%S", unix_time)
local ampm = os.date("%p", unix_time)
return string.format("%s/%s/%s %s:%s:%s %s", month, day, year, hour, min, sec, ampm)
end
-- Quick preset functions
function timestamp.iso(input) return timestamp.format(input, "iso") end
function timestamp.datetime(input) return timestamp.format(input, "datetime") end
function timestamp.us_date(input) return timestamp.format(input, "us_date") end
function timestamp.us_datetime(input) return timestamp.us_datetime_no_zero(input) end
function timestamp.date(input) return timestamp.format(input, "date") end
function timestamp.time(input) return timestamp.format(input, "time") end
function timestamp.time12(input) return timestamp.format(input, "time12") end
function timestamp.readable(input) return timestamp.format(input, "readable") end
-- Microsecond precision variants
function timestamp.datetime_micro(input)
return timestamp.format(input, "%Y-%m-%d %H:%M:%S.") .. string.format("%06d", select(2, parse_input(input)))
end
function timestamp.iso_micro(input)
return timestamp.format(input, "%Y-%m-%dT%H:%M:%S.") .. string.format("%06dZ", select(2, parse_input(input)))
end
-- Register global convenience function
_G.format_time = timestamp.format
return timestamp

View File

@ -244,6 +244,28 @@ func setupParams(state *luajit.State, paramIndex int, execOpts *sqlitex.ExecOpti
return fmt.Errorf("invalid parameters: %w", err)
}
// Handle direct array types
if arrParams, ok := paramsAny.([]any); ok {
execOpts.Args = arrParams
return nil
}
if strArr, ok := paramsAny.([]string); ok {
args := make([]any, len(strArr))
for i, v := range strArr {
args[i] = v
}
execOpts.Args = args
return nil
}
if floatArr, ok := paramsAny.([]float64); ok {
args := make([]any, len(floatArr))
for i, v := range floatArr {
args[i] = v
}
execOpts.Args = args
return nil
}
params, ok := paramsAny.(map[string]any)
if !ok {
return fmt.Errorf("unsupported parameter type: %T", paramsAny)

View File

@ -140,6 +140,13 @@ func (r *Runner) buildHTTPContext(ctx *fasthttp.RequestCtx, params *router.Param
})
luaCtx.Set("headers", headers)
// Cookies
cookies := r.ctxPool.Get().(map[string]any)
ctx.Request.Header.VisitAllCookie(func(key, value []byte) {
cookies[string(key)] = string(value)
})
luaCtx.Set("cookies", cookies)
// Route parameters
if params != nil && len(params.Keys) > 0 {
paramMap := r.paramsPool.Get().(map[string]any)
@ -187,8 +194,8 @@ func (r *Runner) buildHTTPContext(ctx *fasthttp.RequestCtx, params *router.Param
return luaCtx
}
// Releases the HTTP context's maps back to their pool
func (r *Runner) releaseHTTPContext(luaCtx *Context) {
// Return pooled maps
if headers, ok := luaCtx.Get("headers").(map[string]any); ok {
for k := range headers {
delete(headers, k)
@ -196,6 +203,13 @@ func (r *Runner) releaseHTTPContext(luaCtx *Context) {
r.ctxPool.Put(headers)
}
if cookies, ok := luaCtx.Get("cookies").(map[string]any); ok {
for k := range cookies {
delete(cookies, k)
}
r.ctxPool.Put(cookies)
}
if params, ok := luaCtx.Get("params").(map[string]any); ok && len(params) > 0 {
for k := range params {
delete(params, k)