Skip to content

Schema 定义

Schema 是 Sleet ORM 的核心概念,它定义了数据库表的结构。本节将详细介绍如何定义和使用 Schema。

基本概念

在 Sleet 中,Schema 使用 sl.table() 函数定义,它接受表名和列定义对象:

lua
local sl = Sleet

local tableName = sl.table('table_name', {
    -- 列定义
    column1 = sl.columnType().modifiers(),
    column2 = sl.columnType().modifiers(),
    -- ...
})

表定义

基本表定义

lua
-- server/schema.lua
local sl = Sleet

-- 定义玩家表
local players = sl.table('players', {
    id = sl.serial().primaryKey().comment('主键ID'),
    name = sl.varchar(255).notNull().comment('玩家名称'),
    email = sl.varchar(100).unique().comment('邮箱地址')
})

-- 导出供其他模块使用
return { players = players }

复杂表定义

lua
local players = sl.table('players', {
    -- 主键
    id = sl.serial().primaryKey().comment('玩家唯一标识'),
    
    -- 标识符
    identifier = sl.varchar(64).notNull().unique().comment('Steam/Discord ID'),
    license = sl.char(40).unique().comment('FiveM License'),
    
    -- 基本信息
    name = sl.varchar(255).notNull().comment('显示名称'),
    age = sl.tinyint().comment('年龄'),
    gender = sl.varchar(10).default('unknown').comment('性别'),
    
    -- 金钱系统
    money = sl.decimal(10, 2).default(1000.00).comment('现金'),
    bank = sl.decimal(12, 2).default(5000.00).comment('银行余额'),
    
    -- 状态标志
    is_active = sl.boolean().default(true).comment('账户激活状态'),
    is_admin = sl.boolean().default(false).comment('管理员权限'),
    is_banned = sl.boolean().default(false).comment('封禁状态'),
    
    -- 扩展数据
    metadata = sl.json().comment('扩展元数据'),
    settings = sl.json().comment('用户设置'),
    
    -- 时间戳
    created_at = sl.timestamp().defaultNow().comment('创建时间'),
    updated_at = sl.timestamp().defaultNow().onUpdate(sl.sql('NOW()')).comment('更新时间'),
    last_seen = sl.timestamp().comment('最后登录时间'),
    
    -- 软删除
    deleted_at = sl.timestamp().softDelete().comment('删除时间戳')
})

列类型

数值类型

lua
-- 整数类型
id = sl.serial().primaryKey()           -- 自增主键
user_id = sl.int().notNull()           -- 标准整数
experience = sl.bigint().default(0)     -- 大整数
level = sl.smallint().default(1)        -- 小整数
status = sl.tinyint().default(0)        -- 微整数

-- 浮点类型
rating = sl.float()                     -- 单精度浮点
coordinates_x = sl.double()             -- 双精度浮点
price = sl.decimal(10, 2)              -- 定点数(推荐用于货币)

字符串类型

lua
-- 变长字符串
name = sl.varchar(255).notNull()        -- 变长字符串
title = sl.varchar(100)                 -- 较短的变长字符串

-- 定长字符串
country_code = sl.char(2)               -- 定长字符串
license_plate = sl.char(8)              -- 车牌号等

-- 文本类型
description = sl.text()                 -- 文本
bio = sl.mediumtext()                   -- 中等文本
log_content = sl.longtext()             -- 长文本

时间类型

lua
-- 时间戳(推荐)
created_at = sl.timestamp().defaultNow()
updated_at = sl.timestamp().onUpdate(sl.sql('NOW()'))

-- 日期时间
scheduled_at = sl.datetime()            -- 不带时区的日期时间
meeting_date = sl.date()                -- 仅日期

其他类型

lua
-- 布尔值
is_active = sl.boolean().default(true)

-- JSON 数据
metadata = sl.json()                    -- 存储复杂数据结构

列修饰符

约束修饰符

lua
-- 主键约束
id = sl.serial().primaryKey()

-- 非空约束
name = sl.varchar(255).notNull()

-- 唯一约束
email = sl.varchar(100).unique()
identifier = sl.varchar(64).notNull().unique()  -- 组合使用

默认值

lua
-- 静态默认值
money = sl.int().default(1000)
is_active = sl.boolean().default(true)
role = sl.varchar(20).default('user')

-- 动态默认值
created_at = sl.timestamp().defaultNow()           -- 当前时间戳
uuid = sl.varchar(36).default(sl.sql('UUID()'))   -- MySQL UUID 函数

自动更新

lua
-- 更新时自动设置值
updated_at = sl.timestamp().defaultNow().onUpdate(sl.sql('NOW()'))
version = sl.int().default(1).onUpdate(sl.sql('`version` + 1'))
last_modified_by = sl.varchar(64).onUpdate('system')

软删除

lua
-- 软删除字段
deleted_at = sl.timestamp().softDelete()

-- 使用软删除的表会在 DELETE 操作时自动转为 UPDATE
-- 查询时会自动过滤已删除记录

外键关系

lua
-- 定义外键引用
player_id = sl.int().notNull().references(players.id)
category_id = sl.int().references(categories.id)

-- 注意:这主要用于 CLI 工具生成 SQL 时的参考

注释

lua
-- 添加列注释(会传播到 IDE 和生成的 SQL)
id = sl.serial().primaryKey().comment('玩家唯一标识符')
money = sl.decimal(10, 2).default(1000.00).comment('玩家现金余额')

Schema 模式

单文件 Schema

lua
-- server/schema.lua
local sl = Sleet

-- 定义所有表
local players = sl.table('players', { /* ... */ })
local items = sl.table('items', { /* ... */ })
local transactions = sl.table('transactions', { /* ... */ })

-- 统一导出
return {
    players = players,
    items = items,
    transactions = transactions
}

分模块 Schema

lua
-- server/schemas/players.lua
local sl = Sleet

return sl.table('players', {
    id = sl.serial().primaryKey(),
    identifier = sl.varchar(64).notNull().unique(),
    name = sl.varchar(255).notNull(),
    -- ... 其他字段
})
lua
-- server/schemas/items.lua  
local sl = Sleet

return sl.table('items', {
    id = sl.serial().primaryKey(),
    name = sl.varchar(100).notNull(),
    price = sl.decimal(8, 2),
    -- ... 其他字段
})
lua
-- server/schema.lua(主文件)
return {
    players = require 'server.schemas.players',
    items = require 'server.schemas.items'
}

类型推断

当使用 sleet generate CLI 工具后,Sleet 会为每个表生成对应的类型定义:

lua
-- 生成的类型(.sleet/types.lua)
---@class PlayersRecord
---@field id integer 玩家唯一标识符  
---@field identifier string Steam/Discord ID
---@field name string 显示名称
---@field money number 现金
---@field is_active boolean 账户激活状态
---@field created_at string 创建时间

---@class PlayersTable
---@field id ColumnDef<integer>
---@field identifier ColumnDef<string>
---@field name ColumnDef<string>
-- ... 其他字段

使用时自动获得类型推断:

lua
local sl = Sleet
local s = require 'server.schema'
local db = sl.connect()

-- 自动推断返回 PlayersRecord[]
local players = db.select().from(s.players).execute()
local player = players[1]  -- player: PlayersRecord

-- 字段访问有类型检查
print(player.name)      -- string
print(player.money)     -- number  
print(player.is_active) -- boolean

高级特性

条件字段

lua
-- 根据环境使用不同的字段定义
local function createUserTable(isProd)
    local fields = {
        id = sl.serial().primaryKey(),
        name = sl.varchar(255).notNull()
    }
    
    if not isProd then
        -- 开发环境添加调试字段
        fields.debug_info = sl.json()
    end
    
    return sl.table('users', fields)
end

表继承模式

lua
-- 基础字段
local function baseFields()
    return {
        id = sl.serial().primaryKey(),
        created_at = sl.timestamp().defaultNow(),
        updated_at = sl.timestamp().defaultNow().onUpdate(sl.sql('NOW()'))
    }
end

-- 继承基础字段
local players = sl.table('players', vim.tbl_extend('force', baseFields(), {
    identifier = sl.varchar(64).notNull().unique(),
    name = sl.varchar(255).notNull()
}))

下一步

Released under the MIT License.