SpacetimeDB 默认值(Default Values)完全指南:让 Schema 演进不再丢失数据
发布时间:2026/9/12 21:27:41 作者:尧图编辑部 阅读量:1,286
完全指南:让 Schema 演进不再丢失数据)
SpacetimeDB 默认值Default Values完全指南让 Schema 演进不再丢失数据【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB导读默认值Default Values是 SpacetimeDB 实现自动迁移Automatic Migrations的关键机制当你在已发布数据库的表末尾新增一个带默认值的列并重新spacetime publish模块后数据库中已存在的每一行都会自动被填充为该默认值无需手工写迁移脚本。本文以官方核心概念文档 Default Values 为骨架结合仓库内 Rust/TypeScript/C#/C 四种 SDK 的源码实现系统讲解默认值的定义方式、限制条件、常见误区与实际应用场景读完你将能安全地完成加列不加锁、数据零丢失的在线 Schema 演进。注意带默认值的新列必须添加在表定义的【末尾】在表中间插入新列是不被支持的会在发布时直接失败。一、默认值在自动迁移中的角色在 SpacetimeDB 中当你对已有数据库执行spacetime publish {database-name}时系统会尝试把现有数据库 Schema 自动迁移到新模块定义的形态。所谓 Schema指模块代码中声明的表、reducer、procedure、view 及其依赖类型的集合。根据 Automatic Migrations 文档默认值恰好处于可能有破坏性Potentially Breaking与禁止Forbidden两类变更的边界上允许向表末尾添加带默认值的新列——已有行会被自动填充但未更新版本的客户端将感知不到新列禁止添加不带默认值的新列——因为已有行无法被填充发布会失败禁止在表中间添加新列。因此默认值不是可选项而是向已存在数据的表添加新列这一操作能否成功的硬性前提。它让为玩家表新增score字段为订单表新增status字段这类高频演进需求变成一次普通的重新发布。二、在四种服务端语言中定义默认值默认值可以链式作用于任何列类型构建器 / 属性 / 宏上但要求值必须与列类型严格匹配类型不匹配在编译期即报错。以下四个示例分别来自 Default Values 原文档四段代码等价。TypeScript.default(value)const player table( { name: player, public: true }, { id: t.u64().primaryKey().autoInc(), name: t.string(), // 新添加的带默认值的列 score: t.u32().default(0), isActive: t.bool().default(true), bio: t.string().default(), } );.default(value)方法可以链式调用在任意列类型构建器上。从源码看type_builders.ts 中每种列构建器U32ColumnBuilder、BoolColumnBuilder、StringColumnBuilder、U64ColumnBuilder、UuidColumnBuilder、TimestampColumnBuilder等 20 余种都实现了default()其本质是向列的元数据中写入{ defaultValue: value }返回值类型也随之收紧为SetFieldM, defaultValue, Type从而在类型系统层面保证默认值与列类型一致。值得注意的细节整数类型如t.u64()的默认值需要用bigint字面量如0n而小整数类型t.u8()、t.u32()直接用numbert.identity()、t.connectionId()、t.timestamp()、t.scheduleAt()等特殊类型同样支持默认值。C#[SpacetimeDB.Default(value)][SpacetimeDB.Table(Accessor Player, Public true)] public partial struct Player { [SpacetimeDB.PrimaryKey] [SpacetimeDB.AutoInc] public ulong Id; public string Name; // 新添加的带默认值的列 [SpacetimeDB.Default(0u)] public uint Score; [SpacetimeDB.Default(true)] public bool IsActive; [SpacetimeDB.Default()] public string Bio; }[SpacetimeDB.Default(value)]特性声明默认值值会按列类型被序列化。其底层实现在 Runtime/Attrs.cs 中/// Specifies a default value for a table column. /// If a column is added to an existing table while republishing of a module, /// the specified default value will be used to populate existing rows. [AttributeUsage(AttributeTargets.Field)] public sealed class DefaultAttribute(object value) : Internal.ColumnAttribute该特性只能标注在**字段Field**上构造函数接收原始值对象其Value属性会根据值类型做字符串化处理——例如null会输出为nullbool、数值、字符串各有对应处理分支。Rust#[default(value)]#[spacetimedb::table(accessor player, public)] pub struct Player { #[primary_key] #[auto_inc] id: u64, name: String, // 新添加的带默认值的列 #[default(0)] score: u32, #[default(true)] is_active: bool, #[default()] bio: String, }#[default(value)]属性声明默认值表达式必须可 const 求值能在const上下文中使用因此传入的必须是字面量或常量表达式不能是运行时计算值。结合 bindings-macro/src/table.rs 的宏实现可以看清 Rust 侧默认值的完整处理链路属性解析ColumnAttr::parse识别default属性parse_default_attr通过attr.parse_args::syn::Expr()提取表达式table.rs合法性校验如果某列同时带有default与auto_inc/primary_key/unique宏直接编译失败报错信息为invalid combination: auto_inc, unique index or primary key cannot have a default valuetable.rs编译期类型检查宏会为每个带默认值的列生成let _check: #ty #val;字符串列则生成let _check: static str #val;把类型不匹配提前到编译期拦截table.rs运行时序列化宏生成get_default_col_values()方法返回Vecspacetimedb::table::ColumnDefault其中每个默认值通过sats::algebraic_value::ser::ValueSerializer序列化为代数值Algebraic Value在发布时随 Schema 一起提交给数据库table.rs。也就是说你写的#[default(0)]最终会成为数据库迁移时针对col_id所对应列的填充指令。CFIELD_Default宏struct Player { uint64_t id; std::string name; uint32_t score; bool is_active; std::string bio; }; SPACETIMEDB_STRUCT(Player, id, name, score, is_active, bio) SPACETIMEDB_TABLE(Player, player, Public) FIELD_PrimaryKeyAutoInc(player, id) FIELD_Default(player, score, 0u) FIELD_Default(player, is_active, true) FIELD_Default(player, bio, std::string())C 端在表注册之后通过FIELD_Default(table, field, value)宏声明各列的默认值。这些默认值会在新增列触发的 Schema 迁移期间被应用。注意C 模块能力随版本演进相关能力请以CppModuleVersionNotice提示的版本要求为准。三、限制条件默认值不能与哪些属性共存默认值不能与以下属性组合使用主键Primary keys唯一约束Unique constraints自增列Auto-increment这一限制的根本原因在于主键、唯一约束、自增都由数据库代为管理列值这与提供一个静态默认值的语义直接冲突。两种机制都要决定这个列的值从哪来不能同时生效。在 Rust 侧这一限制由过程宏在编译期强制见上文 table.rs 的报错自增列文档 Auto-Increment 的结尾也明确写到Auto-incrementcannotbe combined with default values, since both attempt to populate the column automatically.四、TypeScript 的非直觉错误信息排障指南在 TypeScript 中上述限制在编译期强制但报错信息并不直观。如果你违反了规则你会看到Expected 3 arguments, but got 2.这个报错意味着你的某一列出现了.default()与.primaryKey()、.unique()或.autoInc()的非法组合。例如下面的代码就会触发该错误// ERROR: default() primaryKey() is not allowed const badTable table( { name: bad }, { id: t.u64().default(0n).primaryKey() } // - 触发 Expected 3 arguments );修复方法移除.default()或移除约束.primaryKey()/.unique()/.autoInc()二者留其一即可。这条报错的成因与 SDK 的类型设计有关合法的列构建器会因元数据类型的不同而拥有不同的重载签名当default()与主键/唯一/自增元数据发生冲突时类型推断会让table()的调用落回到一个期望 3 个参数的不匹配重载上从而产生这条令人困惑的信息。排查时可逐个检查新加的列凡是用到default()的列都不要附加上述约束。五、典型应用场景Schema 演进为应用新增功能而不丢失既有数据。例如游戏上线后为player表新增score列老玩家数据自动得到score 0可选字段为历史上未被记录的字段提供合理默认值例如bio: string().default()避免出现空值判断逻辑功能开关Feature Flags新增default(false)的布尔列通过逐步把某行置为true来灰度上线新功能这正是 Automatic Migrations 文档推荐的最佳实践之一。六、与自增列划清边界默认值与自增列是两个互斥的自动填充机制理解两者的分工有助于设计正确的表结构默认值在新增列 自动迁移场景下为已有行填充静态值自增列autoInc()/#[auto_inc]/[SpacetimeDB.AutoInc]在插入新行时由内部序列sequence生成递增整数适用于主键/编号字段。一个常见且正确的组合是主键 自增#[spacetimedb::table(accessor post, public)] pub struct Post { #[primary_key] #[auto_inc] id: u64, // ... }而默认值 主键 / 唯一 / 自增则是非法组合。在设计表时请先明确某列的值由数据库生成还是静态指定再决定使用哪种机制。七、生产环境使用建议结合 Automatic Migrations 文档的迁移策略使用默认值进行 Schema 演进时有几点实操建议新列一律加到表尾无论是哪种语言带默认值的新列都必须追加在表定义末尾不要在中间插入否则发布失败先测试再上线在包含样例数据的开发库上演练一次迁移确认已有行的默认值填充符合预期再发布到生产注意客户端兼容性自动迁移会保持活跃客户端连接但未更新版本的客户端不会感知新列涉及新列的逻辑需同步更新并重新生成客户端 bindings开发期可--delete-dataspacetime publish --delete-data会清空数据重建库仅限开发测试使用严禁用于生产复杂的结构性变更走增量迁移若需要删除非空表、修改列类型/顺序等自动迁移不支持的操作请参考 Incremental Migrations 的生产级模式。小结默认值是 SpacetimeDB 无停机 Schema 演进的基石只要遵循新列加在表尾 必须带默认值 不与主键/唯一/自增共存三条规则就能通过一次spacetime publish平滑地为已有数据补充新字段。在 Rust 中#[default(...)]宏提供了编译期类型检查与序列化的完整保障在 TypeScript 中要留意Expected 3 arguments这一非直觉报错的真实含义在 C# / C 中则分别通过[SpacetimeDB.Default]特性与FIELD_Default宏声明。结合本文给出的源码路径你可以在 bindings-macro/src/table.rs、type_builders.ts 与 Runtime/Attrs.cs 中进一步验证这些机制的具体实现。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考