关于 database-driver-design
面向 Swift 开发者的数据库客户端库设计技能,指导线协议、类型安全 API、连接管理与 Swift Concurrency,并强调查询安全、状态机、背压和连接池。
主要功能
- 01类型安全的命令与查询设计
- 02参数化查询安全性
- 03线协议编码与解码
- 04连接状态机与连接池
- 05支持背压的结果流
- 06Swift Concurrency 与 NIO 协同
适用场景
- 01设计数据库驱动 API
- 02防止 SQL 查询注入
- 03实现数据库线协议
- 04管理连接池和流式结果
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wendylabsinc/ claude-skills --skill database-driver-design安装后在 Claude Code 中输入/database-driver-design确认已加载。
详细介绍
数据库驱动设计
本技能提供构建生产级 Swift 数据库客户端库的专业指导,涵盖线协议实现、连接管理、类型安全的 API,以及与 Swift Concurrency 的集成。相关模式源自出色的实现:valkey-swift 和 postgres-nio。
Agent 行为准则(遵循以下规则)
- 优先使用参数化查询 - 切勿将用户输入拼接到 SQL/命令字符串中
- 使用字符串插值确保安全 - 实现
ExpressibleByStringInterpolation,将值转换为绑定参数 - 将命令设计为类型 - 每个命令/查询都应是带有关联响应类型的结构体
- 为协议实现状态机 - 复杂的连接生命周期需要明确的状态转换
- 支持背压 - 行/结果流必须遵循消费者需求
- 对齐 actor 执行器 - 使用
unownedExecutor与 NIO 事件循环对齐 - 妥善管理连接池 - 实现保活、空闲超时和优雅关闭
核心模式
命令即类型模式
将命令定义为带有关联响应类型的类型,以确保编译时安全:
public protocol DatabaseCommand: Sendable, Hashable {
associatedtype Response: Decodable
static var name: String { get }
func encode(into encoder: inout CommandEncoder)
}
public struct GET: DatabaseCommand {
public typealias Response = String?
public static var name: String { "GET" }
public let key: String
public func encode(into encoder: inout CommandEncoder) {
encoder.encode(Self.name, key)
}
}
使用字符串插值防止 SQL 注入
public struct Query: ExpressibleByStringInterpolation {
public var sql: String
public var bindings: Bindings
public struct StringInterpolation: StringInterpolationProtocol {
var sql: String = ""
var bindings: Bindings = Bindings()
public mutating func appendLiteral(_ literal: String) {
sql.append(literal)
}
public mutating func appendInterpolation<T: Encodable>(_ value: T) {
bindings.append(value)
sql.append("$\(bindings.count)")
}
}
}
// Usage: let query: Query = "SELECT * FROM users WHERE id = \(userId)"
// Result: sql = "SELECT * FROM users WHERE id = $1", bindings = [userId]
与 NIO 执行器对齐的 Actor
通过将 actor 执行器与 NIO 事件循环对齐,消除上下文切换:
public final actor Connection: Sendable {
nonisolated public let unownedExecutor: UnownedSerialExecutor
init(channel: any Channel) {
self.unownedExecutor = channel.eventLoop.executor.asUnownedSerialExecutor()
}
}
带操作的状态机模式
通过明确的状态转换和操作管理复杂的协议状态:
struct ConnectionStateMachine {
enum State {
case idle
case executing(QueryStateMachine)
case closing
case closed
case modifying // Prevents COW during mutations
}
enum Action {
case sendMessage(Message)
case forwardResult(Result)
case closeConnection
case none
}
private var state: State = .idle
mutating func handle(_ message: Message) -> Action {
switch (state, message) {
case (.idle, .query(let q)):
state = .executing(QueryStateMachine(q))
return .sendMessage(.parse(q))
// ... other transitions
}
}
}
长度前缀二进制编码
使用占位符并回填,以写入带长度前缀的数据:
extension Encodable {
func encodeRaw(into buffer: inout ByteBuffer) throws {
// Write placeholder for length (4 bytes for Int32)
let lengthIndex = buffer.writerIndex
buffer.writeInteger(Int32(0))
// Record position before encoding
let startIndex = buffer.writerIndex
// Encode the actual value
try self.encode(into: &buffer)
// Calculate and write actual length
let length = buffer.writerIndex - startIndex
buffer.setInteger(Int32(length), at: lengthIndex)
}
}
用于编码/解码的协议层级
为不同的编码保证设计分层协议:
// Base: runtime-determined type, may throw
public protocol ThrowingDynamicTypeEncodable: Sendable {
func encode(into byteBuffer: inout ByteBuffer) throws
var dataType: DataType { get }
}
// Non-throwing variant
public protocol DynamicTypeEncodable: ThrowingDynamicTypeEncodable {
func encode(into byteBuffer: inout ByteBuffer)
}
// Static type info known at compile time
public protocol StaticTypeEncodable: ThrowingDynamicTypeEncodable {
static var dataType: DataType { get }
}
// Non-throwing + static type info (most efficient)
public protocol NonThrowingEncodable: StaticTypeEncodable, DynamicTypeEncodable {}
可变参数泛型行解码
使用参数包,以类型安全的方式解码多个列:
extension Row {
func decode<each T: Decodable>(
_ types: (repeat each T).Type
) throws -> (repeat each T) {
var index = 0
return (repeat try decodeColumn((each T).self, at: &index))
}
}
// Usage: let (id, name, email) = try row.decode((Int.self, String.self, String.self))
支持背压的流式处理
为结果流实现自适应缓冲策略:
struct AdaptiveBuffer: BackPressureStrategy {
var lowWatermark: Int
var highWatermark: Int
var currentTarget: Int
mutating func didYield(bufferDepth: Int) -> Bool {
// Shrink target if buffer too deep
if bufferDepth > currentTarget * 2 {
currentTarget = max(lowWatermark, currentTarget / 2)
}
return bufferDepth < currentTarget
}
mutating func didConsume(bufferDepth: Int) -> Bool {
// Grow target if buffer drains completely
if bufferDepth == 0 {
currentTarget = min(highWatermark, currentTarget * 2)
}
return bufferDepth < currentTarget
}
}
连接池集成
使连接遵循连接池协议:
extension Connection: PooledConnection {
public typealias ConnectionID = Int
}
struct KeepAliveBehavior: ConnectionKeepAliveBehavior {
typealias Connection = Connection
let frequency: Duration
func runKeepAlive(for connection: Connection) async throws {
_ = try await connection.ping()
}
}
final class ClientMetrics: ConnectionPoolObservabilityDelegate {
func connectionCreated(id: Int) { /* metrics */ }
func connectionLeased(id: Int) { /* metrics */ }
func connectionReleased(id: Int) { /* metrics */ }
func connectionClosed(id: Int) { /* metrics */ }
}
限制深度的解析
通过限制嵌套结构的深度,防止堆栈溢出:
mutating func parseToken(maxDepth: Int = 100) throws -> Token {
guard maxDepth > 0 else {
throw ParsingError.tooDeeplyNested
}
switch tokenType {
case .array:
var elements: [Token] = []
for _ in 0..<count {
elements.append(try parseToken(maxDepth: maxDepth - 1))
}
return .array(elements)
// ... other cases
}
}
快速决策树
-
正在实现线协议编码/解码?
- 使用带占位符和回填的长度前缀消息
- 将协议令牌定义为带关联值的枚举
- 为嵌套结构设置深度限制
-
正在设计类型安全的查询 API?
- 使用
ExpressibleByStringInterpolation防止注入 - 创建
Encodable/Decodable协议层级以进行类型转换 - 使用可变参数泛型解码多列行
- 使用
-
正在管理连接?
- 使用与 NIO 执行器对齐的 actor
- 实现分层状态机
- 通过请求 ID 支持取消
-
正在实现连接池?
- 遵循
PooledConnection协议 - 实现保活行为
- 通过可观测性代理跟踪指标
- 遵循
优先分类处理手册
-
“SQL 注入漏洞”
- 在查询类型上实现
ExpressibleByStringInterpolation - 将插值的值转换为
$1, $2...参数占位符
- 在查询类型上实现
-
“解码结果时类型不匹配”
- 定义带有类型化抛出错误的
Decodable协议 - 在错误中附加列/单元格上下文
- 定义带有类型化抛出错误的
-
“连接状态损坏”
- 使用带有明确状态转换的分层状态机
- 添加
.modifying哨兵以防止 COW 问题
-
“背压不起作用”
- 实现自适应缓冲策略
- 通过数据源协议传递需求
最佳实践总结
- 将命令/查询作为类型 - 每个操作都是一个带有关联响应类型的结构体
- 使用字符串插值确保安全 - 从设计上防止注入
- 使用协议层次结构进行编码 - 提供不同的保证(抛出错误、静态类型等)
- 使用长度前缀线格式 - 写入占位值、编码,然后回填长度
- 使用分层状态机 - 组合子状态机以实现复杂协议
- 使用自适应背压 - 根据消费者速率动态调整缓冲区目标值
- 使 Actor 与 NIO 对齐 - 使用
unownedExecutor消除上下文切换 - 使用 Cell/Row 包装器 - 提供丰富的元数据,以改进错误消息
- 缓存预处理语句 - 对并发预处理进行去重
- 优雅关闭 - 关闭前排空待处理操作
可供参考的主要库
- valkey-swift (github.com/valkey-io/valkey-swift) - 出色的 RESP3 协议、集群支持和发布/订阅
- postgres-nio (github.com/vapor/postgres-nio) - 出色的查询安全性、类型强制转换和状态机
评论
支持 Markdown