mcpField
D
database-driver-design

database-driver-design

github.com/wendylabsinc开发6273 次安装5 周前更新
代码仓库

关于 database-driver-design

面向 Swift 开发者的数据库客户端库设计技能,指导线协议、类型安全 API、连接管理与 Swift Concurrency,并强调查询安全、状态机、背压和连接池。

主要功能

  1. 01类型安全的命令与查询设计
  2. 02参数化查询安全性
  3. 03线协议编码与解码
  4. 04连接状态机与连接池
  5. 05支持背压的结果流
  6. 06Swift Concurrency 与 NIO 协同

适用场景

  1. 01设计数据库驱动 API
  2. 02防止 SQL 查询注入
  3. 03实现数据库线协议
  4. 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 行为准则(遵循以下规则)

  1. 优先使用参数化查询 - 切勿将用户输入拼接到 SQL/命令字符串中
  2. 使用字符串插值确保安全 - 实现 ExpressibleByStringInterpolation,将值转换为绑定参数
  3. 将命令设计为类型 - 每个命令/查询都应是带有关联响应类型的结构体
  4. 为协议实现状态机 - 复杂的连接生命周期需要明确的状态转换
  5. 支持背压 - 行/结果流必须遵循消费者需求
  6. 对齐 actor 执行器 - 使用 unownedExecutor 与 NIO 事件循环对齐
  7. 妥善管理连接池 - 实现保活、空闲超时和优雅关闭

核心模式

命令即类型模式

将命令定义为带有关联响应类型的类型,以确保编译时安全:

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
    }
}

快速决策树

  1. 正在实现线协议编码/解码?

    • 使用带占位符和回填的长度前缀消息
    • 将协议令牌定义为带关联值的枚举
    • 为嵌套结构设置深度限制
  2. 正在设计类型安全的查询 API?

    • 使用 ExpressibleByStringInterpolation 防止注入
    • 创建 Encodable/Decodable 协议层级以进行类型转换
    • 使用可变参数泛型解码多列行
  3. 正在管理连接?

    • 使用与 NIO 执行器对齐的 actor
    • 实现分层状态机
    • 通过请求 ID 支持取消
  4. 正在实现连接池?

    • 遵循 PooledConnection 协议
    • 实现保活行为
    • 通过可观测性代理跟踪指标

优先分类处理手册

  • “SQL 注入漏洞”

    • 在查询类型上实现 ExpressibleByStringInterpolation
    • 将插值的值转换为 $1, $2... 参数占位符
  • “解码结果时类型不匹配”

    • 定义带有类型化抛出错误的 Decodable 协议
    • 在错误中附加列/单元格上下文
  • “连接状态损坏”

    • 使用带有明确状态转换的分层状态机
    • 添加 .modifying 哨兵以防止 COW 问题
  • “背压不起作用”

    • 实现自适应缓冲策略
    • 通过数据源协议传递需求

最佳实践总结

  1. 将命令/查询作为类型 - 每个操作都是一个带有关联响应类型的结构体
  2. 使用字符串插值确保安全 - 从设计上防止注入
  3. 使用协议层次结构进行编码 - 提供不同的保证(抛出错误、静态类型等)
  4. 使用长度前缀线格式 - 写入占位值、编码,然后回填长度
  5. 使用分层状态机 - 组合子状态机以实现复杂协议
  6. 使用自适应背压 - 根据消费者速率动态调整缓冲区目标值
  7. 使 Actor 与 NIO 对齐 - 使用 unownedExecutor 消除上下文切换
  8. 使用 Cell/Row 包装器 - 提供丰富的元数据,以改进错误消息
  9. 缓存预处理语句 - 对并发预处理进行去重
  10. 优雅关闭 - 关闭前排空待处理操作

可供参考的主要库

  • valkey-swift (github.com/valkey-io/valkey-swift) - 出色的 RESP3 协议、集群支持和发布/订阅
  • postgres-nio (github.com/vapor/postgres-nio) - 出色的查询安全性、类型强制转换和状态机

评论

支持 Markdown