🎉 initial commit
This commit is contained in:
124
devices-api/db.lua
Normal file
124
devices-api/db.lua
Normal file
@@ -0,0 +1,124 @@
|
||||
-- PostgreSQL connection pool using pgmoon
|
||||
local pgmoon = require("pgmoon")
|
||||
local cjson = require("cjson")
|
||||
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user