Files
metabuilder/packages/data_table/seed/scripts/db/operations.lua
2025-12-30 22:01:42 +00:00

347 lines
9.5 KiB
Lua

-- data_table/seed/scripts/db/operations.lua
-- DBAL operations for generic data table management
-- Provides reusable CRUD helpers for any entity
-- @module data_table.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- GENERIC CRUD OPERATIONS
---------------------------------------------------------------------------
---Create a record in any entity
---@param dbal table DBAL client instance
---@param entity string Entity name
---@param data table Record data
---@return table Created record
function M.create(dbal, entity, data)
-- Auto-add timestamps if not present
if not data.createdAt then
data.createdAt = os.time() * 1000
end
return dbal:create(entity, data)
end
---Read a record by ID
---@param dbal table
---@param entity string Entity name
---@param id string Record ID
---@return table|nil Record
function M.read(dbal, entity, id)
return dbal:read(entity, id)
end
---Update a record
---@param dbal table
---@param entity string Entity name
---@param id string Record ID
---@param data table Fields to update
---@return table Updated record
function M.update(dbal, entity, id, data)
-- Auto-update timestamp if not present
if not data.updatedAt then
data.updatedAt = os.time() * 1000
end
return dbal:update(entity, id, data)
end
---Delete a record
---@param dbal table
---@param entity string Entity name
---@param id string Record ID
---@return boolean Success
function M.delete(dbal, entity, id)
return dbal:delete(entity, id)
end
---List records with filtering and pagination
---@param dbal table
---@param entity string Entity name
---@param options table List options
---@return table List result with items and total
function M.list(dbal, entity, options)
return dbal:list(entity, {
where = options.where or {},
orderBy = options.orderBy or { createdAt = 'desc' },
take = options.take or 50,
skip = options.skip or 0,
})
end
---Find first matching record
---@param dbal table
---@param entity string Entity name
---@param where table Where conditions
---@return table|nil First matching record
function M.findFirst(dbal, entity, where)
return dbal:findFirst(entity, { where = where })
end
---------------------------------------------------------------------------
-- ADVANCED QUERY HELPERS
---------------------------------------------------------------------------
---Search records by text field
---@param dbal table
---@param entity string Entity name
---@param field string Field to search
---@param query string Search query
---@param tenantId string|nil Tenant filter
---@param take number|nil Max results
---@return table[] Matching records
function M.search(dbal, entity, field, query, tenantId, take)
local where = {}
if tenantId then
where.tenantId = tenantId
end
local result = dbal:list(entity, {
where = where,
take = 1000,
})
local matches = {}
local lowerQuery = query:lower()
for _, record in ipairs(result.items or {}) do
local fieldValue = record[field]
if fieldValue and type(fieldValue) == 'string' then
if fieldValue:lower():find(lowerQuery, 1, true) then
table.insert(matches, record)
if #matches >= (take or 50) then
break
end
end
end
end
return matches
end
---Count records matching conditions
---@param dbal table
---@param entity string Entity name
---@param where table|nil Where conditions
---@return number Count
function M.count(dbal, entity, where)
local result = dbal:list(entity, {
where = where or {},
take = 100000,
})
return result.total or #(result.items or {})
end
---Check if record exists
---@param dbal table
---@param entity string Entity name
---@param where table Where conditions
---@return boolean Exists
function M.exists(dbal, entity, where)
local record = M.findFirst(dbal, entity, where)
return record ~= nil
end
---Get distinct values for a field
---@param dbal table
---@param entity string Entity name
---@param field string Field name
---@param where table|nil Where conditions
---@return table[] Distinct values
function M.distinct(dbal, entity, field, where)
local result = dbal:list(entity, {
where = where or {},
take = 10000,
})
local seen = {}
local values = {}
for _, record in ipairs(result.items or {}) do
local value = record[field]
if value ~= nil and not seen[tostring(value)] then
seen[tostring(value)] = true
table.insert(values, value)
end
end
return values
end
---------------------------------------------------------------------------
-- BULK OPERATIONS
---------------------------------------------------------------------------
---Create multiple records
---@param dbal table
---@param entity string Entity name
---@param records table[] Records to create
---@return table[] Created records
function M.createMany(dbal, entity, records)
local created = {}
for _, data in ipairs(records) do
local record = M.create(dbal, entity, data)
table.insert(created, record)
end
return created
end
---Update multiple records by IDs
---@param dbal table
---@param entity string Entity name
---@param ids table[] Record IDs
---@param data table Fields to update
---@return number Count of updated records
function M.updateMany(dbal, entity, ids, data)
local count = 0
for _, id in ipairs(ids) do
M.update(dbal, entity, id, data)
count = count + 1
end
return count
end
---Delete multiple records by IDs
---@param dbal table
---@param entity string Entity name
---@param ids table[] Record IDs
---@return number Count of deleted records
function M.deleteMany(dbal, entity, ids)
local count = 0
for _, id in ipairs(ids) do
if M.delete(dbal, entity, id) then
count = count + 1
end
end
return count
end
---Delete records matching conditions
---@param dbal table
---@param entity string Entity name
---@param where table Where conditions
---@return number Count of deleted records
function M.deleteWhere(dbal, entity, where)
local result = dbal:list(entity, {
where = where,
take = 10000,
})
local count = 0
for _, record in ipairs(result.items or {}) do
if M.delete(dbal, entity, record.id) then
count = count + 1
end
end
return count
end
---------------------------------------------------------------------------
-- TABLE CONFIGURATION
---------------------------------------------------------------------------
---@class TableConfig
---@field entity string Entity name
---@field columns table[] Column definitions
---@field defaultSort table|nil Default sort order
---@field pageSize number|nil Default page size
---@field filters table[]|nil Available filters
---Save table configuration
---@param dbal table
---@param tenantId string
---@param tableKey string Unique key for this table config
---@param config TableConfig
---@return table Saved config
function M.saveTableConfig(dbal, tenantId, tableKey, config)
local existing = dbal:findFirst('TableConfig', {
where = { tenantId = tenantId, tableKey = tableKey },
})
if existing then
return dbal:update('TableConfig', existing.id, {
config = json.encode(config),
updatedAt = os.time() * 1000,
})
end
return dbal:create('TableConfig', {
tenantId = tenantId,
tableKey = tableKey,
config = json.encode(config),
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get table configuration
---@param dbal table
---@param tenantId string
---@param tableKey string
---@return TableConfig|nil
function M.getTableConfig(dbal, tenantId, tableKey)
local record = dbal:findFirst('TableConfig', {
where = { tenantId = tenantId, tableKey = tableKey },
})
if record and record.config then
return json.decode(record.config)
end
return nil
end
---------------------------------------------------------------------------
-- USER PREFERENCES
---------------------------------------------------------------------------
---Save user column preferences
---@param dbal table
---@param userId string
---@param tableKey string
---@param columns table[] Visible columns in order
---@param columnWidths table|nil Column widths
function M.saveUserPreferences(dbal, userId, tableKey, columns, columnWidths)
local existing = dbal:findFirst('UserTablePreference', {
where = { userId = userId, tableKey = tableKey },
})
local data = {
columns = json.encode(columns),
columnWidths = columnWidths and json.encode(columnWidths) or nil,
updatedAt = os.time() * 1000,
}
if existing then
return dbal:update('UserTablePreference', existing.id, data)
end
data.userId = userId
data.tableKey = tableKey
data.createdAt = os.time() * 1000
return dbal:create('UserTablePreference', data)
end
---Get user column preferences
---@param dbal table
---@param userId string
---@param tableKey string
---@return table|nil Preferences
function M.getUserPreferences(dbal, userId, tableKey)
local record = dbal:findFirst('UserTablePreference', {
where = { userId = userId, tableKey = tableKey },
})
if record then
return {
columns = record.columns and json.decode(record.columns) or nil,
columnWidths = record.columnWidths and json.decode(record.columnWidths) or nil,
}
end
return nil
end
return M