RuleGo RuleGo
🏠首页
  • 快速入门
  • 规则链
  • 标准组件
  • 扩展组件
  • 自定义组件
  • 可视化
  • RuleGo-Server
  • AOP
  • 触发器
  • 高级主题
  • 性能
  • 标准组件
  • 扩展组件
  • 自定义组件
  • 流式计算
  • 组件市场
  • 概述
  • 快速入门
  • 路由
  • DSL
  • API
  • Options
  • 组件
🔥编辑器 (opens new window)
  • 可视化编辑器 (opens new window)
  • RuleGo-Server (opens new window)
  • 🌊StreamSQL
  • 🤖智能体框架
  • 🦀TPCLAW 智能体平台 (opens new window)
  • ❓问答

    • FAQ
💖支持
👥加入社区
  • Github (opens new window)
  • Gitee (opens new window)
  • GitCode (opens new window)
  • 更新日志 (opens new window)
  • English
  • 简体中文
🏠首页
  • 快速入门
  • 规则链
  • 标准组件
  • 扩展组件
  • 自定义组件
  • 可视化
  • RuleGo-Server
  • AOP
  • 触发器
  • 高级主题
  • 性能
  • 标准组件
  • 扩展组件
  • 自定义组件
  • 流式计算
  • 组件市场
  • 概述
  • 快速入门
  • 路由
  • DSL
  • API
  • Options
  • 组件
🔥编辑器 (opens new window)
  • 可视化编辑器 (opens new window)
  • RuleGo-Server (opens new window)
  • 🌊StreamSQL
  • 🤖智能体框架
  • 🦀TPCLAW 智能体平台 (opens new window)
  • ❓问答

    • FAQ
💖支持
👥加入社区
  • Github (opens new window)
  • Gitee (opens new window)
  • GitCode (opens new window)
  • 更新日志 (opens new window)
  • English
  • 简体中文

广告采用随机轮播方式显示 ❤️成为赞助商
  • 概述
  • 快速开始
  • 核心概念
  • SQL参考
  • API参考
  • RuleGo集成
  • 加入社区讨论
  • Schema 校验
    • 概述
    • 定义并注册 Schema
    • 校验输入
    • Strict 模式
    • Registry 用法
    • 完整示例
    • DataType 参考
    • 📚 相关文档
  • 分析函数
  • 进阶示例
  • 函数

  • 案例集锦

目录

Schema 校验

# Schema 校验

github.com/rulego/streamsql/schema 包提供一个运行期类型化记录 schema 注册表,用于在数据进入流之前对 map[string]interface{} 做校验,把不合规的输入挡在边缘。

# 概述

流处理管道对上游数据的字段名和类型通常有隐式假设:聚合函数期望数值,时间窗口期望时间戳,GROUP BY 期望键存在。一旦上游送来缺字段或类型错乱的行,错误往往要等到窗口触发或聚合计算时才暴露,排查困难。

Schema 校验解决的就是这个问题——在调用 ssql.Emit 之前,先用一份显式声明的字段定义把每行 map 过一遍,不合规就拒绝或记录,让脏数据进不了流。

与 SQL WHERE 的区别

WHERE 是行级过滤器,作用在已经进入流的数据上,按条件保留或丢弃行;Schema 校验作用在流的入口之前,按字段定义判断整行结构是否合法。两者互补:Schema 守门,WHERE 筛选。

# 定义并注册 Schema

package schema

type FieldDef struct {
    Name     string
    Type     DataType
    Required bool
    Default  interface{} // 非 nil 时屏蔽 required 缺失错误;用带类型的值,如 float64(0)
}

type Schema struct {
    Name   string      // 在同一个 Registry 内唯一
    Fields []FieldDef  // 有序
    Strict bool        // 为 true 时,未知键视为错误
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

下面声明一个名为 iot 的 schema:必填的 id(int)、可选的 note(string)、带默认值的 score(float)以及一个任意类型的 meta 字段。

package main

import "github.com/rulego/streamsql/schema"

func main() {
    iot := schema.Schema{
        Name: "iot",
        Fields: []schema.FieldDef{
            {Name: "id",    Type: schema.TypeInt,   Required: true},
            {Name: "note",  Type: schema.TypeString},
            {Name: "score", Type: schema.TypeFloat, Default: float64(0)},
            {Name: "meta",  Type: schema.TypeAny},
        },
    }
    if err := schema.Default.Register(iot); err != nil {
        panic(err)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Default 与 nil

Default 是否生效取决于它是否为 nil。要让零值作为默认值,必须用带类型的字面量装进 interface:float64(0)、int(0)、false 都是非 nil,能屏蔽 required 缺失错误;而裸的 nil 表示"没有默认值",必填字段缺键仍会报错。注意类型也要对上——给 TypeFloat 字段写 int(0) 虽然能屏蔽缺失错误,但取值时类型不匹配。

# 校验输入

func (s *Schema) Validate(data map[string]interface{}) error
1

Validate 一次性聚合所有问题返回,不会遇到第一个错误就停:

通过的一行:

err := schema.Default.MustGet("iot").Validate(map[string]interface{}{
    "id":    42,
    "note":  "ok",
    "score": 9.5,
    "meta":  nil,   // TypeAny 接受 nil
})
// err == nil
1
2
3
4
5
6
7

不通过的一行(同时多处错误,聚合成一条返回):

err := schema.Default.MustGet("iot").Validate(map[string]interface{}{
    // id 缺失(必填,无默认)
    "note":  123,    // 期望 string,得到 int
    "score": "high", // 期望 float,得到 string
})
// schema "iot": required field "id" is missing; schema "iot": field "note" expects string, got int; schema "iot": field "score" expects float, got string
1
2
3
4
5
6

Validate 在发现多于一个问题时返回 *MultiError:

type MultiError struct{ Errors []error }

func (m *MultiError) Append(err error)       // 非 nil 才追加
func (m *MultiError) Err() error             // 无错误返回 nil,否则返回 m 自身
func (m *MultiError) Error() string          // 用 "; " 连接所有子错误
1
2
3
4
5

# Strict 模式

默认(Strict=false)下,输入里未声明的键被静默忽略,方便上游逐步演进字段。开启 Strict=true 后,未知键即报错:

strict := schema.Schema{
    Name:   "strict_iot",
    Strict: true,
    Fields: iot.Fields,
}
schema.Default.Register(strict)

err := schema.Default.MustGet("strict_iot").Validate(map[string]interface{}{
    "id":    1,
    "extra": "not declared",
})
// schema "strict_iot": unknown field "extra"
1
2
3
4
5
6
7
8
9
10
11
12

# Registry 用法

type Registry struct{ ... }  // sync.RWMutex,并发安全

func NewRegistry() *Registry
func (r *Registry) Register(s Schema) error     // 名字为空或重复时返回错误
func (r *Registry) Get(name string) (Schema, bool)
func (r *Registry) MustGet(name string) Schema  // 找不到时 panic

var Default = NewRegistry()  // 包级共享注册表
1
2
3
4
5
6
7
8
  • Default:多数场景直接用,跨组件共享 schema。
  • NewRegistry():需要在测试或多租户场景隔离 schema 时,用私有注册表。
  • MustGet:仅在"schema 一定已注册"这一前置不变量成立时用(比如 init 阶段注册);否则优先 Get 处理 false 分支。

Register 不可覆盖

Register 对同名 schema 返回错误,不会静默覆盖。需要更新定义时,先换名字或新建一个 Registry。

# 完整示例

注册 schema 后,在每次 Emit 之前先校验,把不合规的行挡掉/记日志:

package main

import (
    "fmt"

    "github.com/rulego/streamsql"
    "github.com/rulego/streamsql/schema"
)

func main() {
    s := schema.Schema{
        Name: "iot",
        Fields: []schema.FieldDef{
            {Name: "deviceId",    Type: schema.TypeString, Required: true},
            {Name: "temperature", Type: schema.TypeFloat,  Required: true},
            {Name: "humidity",    Type: schema.TypeFloat,  Default: float64(0)},
            {Name: "tags",        Type: schema.TypeArray},
        },
    }
    if err := schema.Default.Register(s); err != nil {
        panic(err)
    }

    ssql := streamsql.New()
    defer ssql.Stop()
    ssql.Execute(`SELECT deviceId, AVG(temperature) AS avg_t
                  FROM stream
                  GROUP BY deviceId, TumblingWindow('5s')`)
    ssql.AddSink(func(rows []map[string]interface{}) { fmt.Println(rows) })

    row := map[string]interface{}{
        "deviceId":    "d1",
        "temperature": 25.5,
    }
    if err := schema.Default.Get("iot").Validate(row); err != nil {
        fmt.Println("invalid:", err)
        return
    }
    ssql.Emit(row)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# DataType 参考

type DataType int

const (
    TypeInt DataType = iota
    TypeInt64
    TypeFloat
    TypeBool
    TypeString
    TypeTime
    TypeArray
    TypeMap
    TypeAny
)
1
2
3
4
5
6
7
8
9
10
11
12
13
类型 字符串形式 匹配的 Go 值 说明
TypeInt int int 三种数值类型互通:TypeInt/TypeInt64/TypeFloat 字段互相接受任意数值
TypeInt64 int64 int64 见上
TypeFloat float float32、float64 见上
TypeBool bool bool
TypeString string string
TypeTime time time.Time
TypeArray array []interface{} 仅识别 []interface{};其他切片类型回退为 TypeAny
TypeMap map map[string]interface{} 仅识别 map[string]interface{}
TypeAny any 任意值(含 nil) 兜底类型;InferType 对 nil 和未识别值也返回 TypeAny
func InferType(v interface{}) DataType
1

InferType 把运行时值映射到 DataType:nil 和未识别的值返回 TypeAny,float32/float64 都映射到 TypeFloat,只识别 []interface{} 和 map[string]interface{} 两种容器。

数值类型互通

声明为 TypeInt 的字段接受 int/int64/float32/float64 中的任意一种,TypeInt64 和 TypeFloat 同理。这样上游 JSON 解析出的 float64 不会被误判为类型不匹配。

# 📚 相关文档

  • API 参考 — StreamSQL 核心 API
  • SQL 参考 — 完整 SQL 语法
在 GitHub 上编辑此页 (opens new window)
上次更新: 2026/07/11, 04:40:00
加入社区讨论
分析函数

← 加入社区讨论 分析函数→

Theme by Vdoing | Copyright © 2023-2026 RuleGo Team | Apache 2.0 License

  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式