124 lines
3.3 KiB
Lua
124 lines
3.3 KiB
Lua
-- PostgreSQL connection pool using pgmoon
|
|
local pgmoon = require("pgmoon")
|
|
|
|
local DB_HOST = os.getenv("DB_HOST") or "localhost"
|
|
local DB_PORT = tonumber(os.getenv("DB_PORT")) or 5432
|
|
local DB_NAME = os.getenv("DB_NAME") or "handheld_devices"
|
|
local DB_USER = os.getenv("DB_USER") or "devices_user"
|
|
local DB_PASSWORD = os.getenv("DB_PASSWORD") or "devices_password"
|
|
local DB_POOL_SIZE = tonumber(os.getenv("DB_POOL_SIZE")) or 10
|
|
local DB_CONNECT_TIMEOUT_MS = tonumber(os.getenv("DB_CONNECT_TIMEOUT_MS")) or 5000
|
|
local DB_QUERY_TIMEOUT_MS = tonumber(os.getenv("DB_QUERY_TIMEOUT_MS")) or 10000
|
|
|
|
local config = {
|
|
host = DB_HOST,
|
|
port = tostring(DB_PORT),
|
|
database = DB_NAME,
|
|
user = DB_USER,
|
|
password = DB_PASSWORD,
|
|
socket_type = "luasocket",
|
|
}
|
|
|
|
local pool = {
|
|
available = {},
|
|
in_use = {},
|
|
max_size = DB_POOL_SIZE,
|
|
}
|
|
|
|
local function create_connection()
|
|
local pg = pgmoon.new(config)
|
|
pg:settimeout(DB_CONNECT_TIMEOUT_MS)
|
|
local ok, err = pg:connect()
|
|
if not ok then
|
|
return nil, err
|
|
end
|
|
pg:settimeout(DB_QUERY_TIMEOUT_MS)
|
|
return pg
|
|
end
|
|
|
|
local function get_connection()
|
|
local conn = table.remove(pool.available)
|
|
if conn then
|
|
table.insert(pool.in_use, conn)
|
|
return conn
|
|
end
|
|
if #pool.in_use >= pool.max_size then
|
|
return nil, "connection pool exhausted"
|
|
end
|
|
local pg, err = create_connection()
|
|
if not pg then
|
|
return nil, err
|
|
end
|
|
table.insert(pool.in_use, pg)
|
|
return pg
|
|
end
|
|
|
|
local function release_connection(conn)
|
|
for i, c in ipairs(pool.in_use) do
|
|
if c == conn then
|
|
table.remove(pool.in_use, i)
|
|
if #pool.available < pool.max_size then
|
|
table.insert(pool.available, conn)
|
|
else
|
|
pcall(function() conn:disconnect() end)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Execute with connection from pool; auto-release on return
|
|
local function with_connection(fn)
|
|
local conn, err = get_connection()
|
|
if not conn then
|
|
return nil, err
|
|
end
|
|
local ok, result, result_err = pcall(function()
|
|
return fn(conn)
|
|
end)
|
|
release_connection(conn)
|
|
if not ok then
|
|
return nil, result
|
|
end
|
|
return result, result_err
|
|
end
|
|
|
|
-- Retry with exponential backoff
|
|
local function with_retry(fn, max_attempts)
|
|
max_attempts = max_attempts or 3
|
|
local attempt = 0
|
|
local last_err
|
|
while attempt < max_attempts do
|
|
attempt = attempt + 1
|
|
local result, err = fn()
|
|
if result ~= nil or (err and not (err:match("connection") or err:match("timeout"))) then
|
|
return result, err
|
|
end
|
|
last_err = err
|
|
if attempt < max_attempts then
|
|
local delay = math.min(2 ^ attempt * 100, 5000)
|
|
require("socket").sleep(delay / 1000)
|
|
end
|
|
end
|
|
return nil, last_err
|
|
end
|
|
|
|
local function ping()
|
|
return with_connection(function(conn)
|
|
local res, err = conn:query("SELECT 1")
|
|
if res and type(res) == "table" and (res[1] or #res >= 1) then
|
|
return true
|
|
end
|
|
return nil, err or "ping failed"
|
|
end)
|
|
end
|
|
|
|
return {
|
|
config = config,
|
|
get_connection = get_connection,
|
|
release_connection = release_connection,
|
|
with_connection = with_connection,
|
|
with_retry = with_retry,
|
|
ping = ping,
|
|
}
|