SQLDelight 类型投影(Type Projections)与自定义 Mapper 实战指南
后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载SQLDelight 默认会把每条 SELECT 查询的结果集映射为自动生成的数据类但实际业务中我们往往只需要投影中的某几个字段、或者想把原始字段组合成自定义类型。本文以 Android SQLite 场景为切入点系统讲解 SQLDelight 的类型投影机制如何通过类型安全的 Mapper 参数接管结果映射、如何优先用SQL 本身完成投影并结合本仓库的编译器与运行时源码说明这些能力从.sq文件到生成代码再到查询执行的完整链路。读完本文你将掌握自定义投影的两种正交手段并理解它们各自的适用边界。本文对应仓库文档为 docs/android_sqlite/custom_projections.md其正文内容来自所有平台共用的 docs/common/custom_projections.mdAndroid 之外JVM、Native、JS 等的投影行为完全一致。一、什么是类型投影默认数据类与自定义映射SQLDelight 在编译.sq文件时会为每个表生成对应的数据类例如sample工程中的 Player.sq 会生成Player数据类。默认情况下任何 SELECT 查询的每一行都会被映射为该数据类的一个实例-- Player.sq selectAll: SELECT * FROM player JOIN team ON player.team team.id;// 默认返回 ListPlayer val players playerQueries.selectAll().executeAsList()这就是默认投影查询返回的数据类即投影。它覆盖了绝大多数场景但当你只需要投影中的一部分字段、或想对列做即时变换如把名字转大写、拼接字符串、封装成业务类型时默认数据类就显得笨重——你不得不为一行数据分配一个包含全部字段的对象再手动取用。此时就轮到类型安全的 Mapper登场SQLDelight 为每个 SELECT 查询额外生成一个接受mapper参数的函数重载让调用方用一行 lambda 自定义从这一行的列到目标类型的映射规则。二、用 Mapper 参数覆盖默认投影文档给出的核心示例是查询player表的两列球员号码与姓名但不想构造整个数据类只想拿到姓名的大写形式列表val selectAllNames playerQueries.selectAll( mapper { player_number, full_name - full_name.toUppercase() } ) println(selectAllNames.executeAsList()) // Prints [RYAN GETZLAF, COREY PERRY]关键点拆解如下lambda 参数即投影的每一列mapper的入参顺序、类型与查询返回的列一一对应例如上述查询返回player_number与full_name两列lambda 就接收这两个参数参数名来自.sq中的列名由编译器保证类型安全写错列名或类型会直接编译失败。返回值任意mapper的返回值类型T就是整个查询的结果行类型executeAsList()返回ListT。你可以返回String、Int、自定义业务类甚至Pair、data class。只影响该次调用传入mapper只改变这次查询的映射结果不会改动表的数据类定义也不会影响其他查询。2.1 带参数的查询同样适用如果查询声明了 SQL 参数如WHERE id :team_id生成的带 mapper 函数会先接收这些参数、再接 mapper 尾参val result playerQueries.forTeam( teamId, mapper { first_name, last_name, teamName - $first_name $last_name ($teamName) } )这一签名的源码形态可以在编译器生成逻辑中看到见下文第三节。2.2 单列查询的简化当查询只投影一列时编译器不会生成带 mapper 的重载而是直接返回该列的 Kotlin 类型如QueryString因为你已经可以通过executeAsList()拿到最简类型无需再包一层 lambda。这是编译器在 SelectQueryGenerator.kt#L120-L124 中的显式分支needsWrapper()为 false 时no custom type possible, just returns the single column。三、编译器如何生成带 Mapper 的查询方法源码视角要理解自定义投影为什么类型安全最好的方式看代码生成器。SQLDelight 编译器中的 SelectQueryGenerator.kt 负责生成所有 SELECT 查询的接口方法其中customResultTypeFunctionInterface()L86-L127精确描述了带 mapper 的函数签名// 生成的函数签名示意源码注释中的原话 fun T selectForId(id, mapper: (column1: String) - T): QueryT其生成步骤为先按顺序把查询的 SQL 参数逐个加入参数列表query.parameters.forEach { function.addParameter(it.name, it.argumentType()) }若查询需要包装needsWrapper()即返回多列则声明类型变量T追加mapper尾参类型为按列构造的 lambda 类型(col1: Type1, col2: Type2, ...) - T每个参数的类型来自resultColumns各列的javaType返回类型变为QueryT。而真正执行映射的 lambda 在customResultTypeFunction()L134-L237中组装。生成的运行时代码大致等价于{ cursor - check(cursor is SqlCursorSubclass) // 游标类型校验 mapper( cursor.getLong(0), // 按列索引反序列化 adapter.decode(cursor.getString(1)) ) }这里可以看到两个值得注意的实现事实列的读取顺序与索引生成代码用resultColumns.mapIndexed { index, column - column.cursorGetter(index) }把第 index 列的反序列化表达式按顺序拼进mapper(...)调用因此 lambda 的形参顺序恰好等于 SELECT 列表的顺序类型适配器自动衔接如果某列声明了自定义列类型AS SomeType生成代码会调用表名Adapter.列名Adapter.decode(...)先把游标原始值解码成 Kotlin 类型再传给mapper。这意味着自定义投影与自定义列类型可以无缝组合。四、运行时如何消费 Mapper源码视角生成的带 mapper 查询最终构造一个QueryT实例runtime 的 Query.kt其中Query/ExecutableQuery构造函数接收一个mapper: (SqlCursor) - RowType执行时由驱动把游标交给你自定义的 lambda。核心执行入口executeAsList()Query.kt#L174-L178的实现非常直白fun executeAsList(): ListRowType execute { cursor - val result mutableListOfRowType() while (cursor.next().value) result.add(mapper(cursor)) QueryResult.Value(result) }.value即逐行推进游标每行调用一次 mapper收集成 List。默认数据类投影本质上也是同一个机制——编译器为默认查询生成的函数内部以数据类构造器引用::Data作为 mapper 传入见defaultResultTypeFunction()的ctorCallSelectQueryGenerator.kt#L68-L72。所以自定义 mapper 并不是什么特殊通道它和默认投影走的是同一条执行管线只是映射逻辑由你接管。此外ExecutableQuery还提供了另外两个消费方式Query.kt#L187-L203executeAsOne()要求结果集恰好一行无行抛NullPointerException多行抛IllegalStateExceptionexecuteAsOneOrNull()返回唯一一行或null多行同样抛IllegalStateException。查询真正下发给数据库时走的是SqlDriver.executeQuery(identifier, sql, mapper, parameters, binders)接口SqlDriver.kt#L40-L46。需要特别留意的是接口 KDoc 中的约束游标不能逃出 mapper 块的作用域——在自定义 mapper 中不要试图保存游标引用只能在 lambda 内同步消费列值。五、优先用 SQL 完成投影文档在给出 Mapper 用法后立刻强调了一条重要的工程原则In general you should be leveraging SQL to do custom projections whenever possible.一般而言只要可能应优先借助 SQL 本身来完成自定义投影。原因很务实把投影逻辑如大小写转换、拼接、聚合、去重、类型转换下推到数据库层意味着——结果集在网络/进程间传输前就已经裁剪完毕减少客户端无谓的数据搬运与对象分配投影逻辑声明在.sq文件里随数据库 schema 一起可审计、可测试、可复用数据库引擎的优化器可以对投影列做覆盖索引等优化。文档随后给出同款需求的 SQL 写法selectNames: SELECT upper(full_name) FROM hockeyPlayer;val selectAllNames playerQueries.selectNames() println(selectAllNames.executeAsList()) // Prints [RYAN GETZLAF, COREY PERRY]因为selectNames只返回单列编译器直接生成QueryString见 2.2 节连mapper都不需要写代码更简洁、意图更清晰。sample工程中的 Player.sq 也展示了同类实践——forTeam查询用CAST (number AS TEXT)与team.name AS teamName在 SQL 层完成列转换与重命名正是SQL 优先投影的落地样板。六、两套方案的取舍何时用 SQL、何时用 Mapper综合文档与源码可以整理出清晰的决策依据场景推荐手段理由大小写、拼接、CAST、聚合、DISTINCT、CASE 等标准 SQL 能力SQL 投影下推给数据库代码最简.sq内可读可测把多列封装成业务领域类型如Address、MoneyMapperSQL 无法表达 Kotlin 类型mapper类型安全且无需额外数据类依赖自定义列类型适配器AS SomeType的列两者皆可编译器生成的 mapper lambda 会自动先decode适配器列见第三节仅查询单列无需 mapper编译器直接返回该列类型需要澄清的一点是Mapper 只是结果行的映射钩子它不做列级别的类型转换。若某列需要从数据库类型转为 Kotlin 类型例如INTEGER存时间戳、文本存枚举应使用列类型与适配器机制参见 custom_column_types.md 与 types.md否则该列在 mapper lambda 里只会以原始 Kotlin 类型出现。七、行为验证驱动测试中的执行语义自定义投影的正确性依赖运行时对 mapper 的调用语义仓库的驱动测试直接验证了这一点。QueryTest.kt 中所有驱动共享的跨平台测试覆盖了executeAsOne()恰好一行时返回该行L65-L69零行抛 NPEL81-L84、多行抛IllegalStateExceptionL89-L95executeAsOneOrNull()零行返回nullL108-L110、多行抛异常L112-L117executeAsList()assertEquals(listOf(data1, data2), testDataQuery().executeAsList())L123-L130验证多行按序逐行映射。这些测试位于 drivers/driver-test 模块被各平台驱动Android、JVM、Native、Web复用说明 mapper 的执行语义在所有平台一致——你在 Android SQLite 上写出的自定义投影在桌面端、iOS、JS 上行为相同。八、小结SQLDelight 的类型投影提供两条互补的路径Mapper 参数为每个多列 SELECT 查询生成(col1, col2, ...) - T尾参重载用类型安全的 lambda 在 Kotlin 侧接管行到目标类型的映射适合封装领域类型与内存变换SQL 投影在.sq文件中用SELECT upper(...)、CAST(...) AS ...等把裁剪与变换下推给数据库单列结果自动简化为该列的 Kotlin 类型。两者的底层实现是同一套机制编译器生成逐列反序列化后调用 mapper的 lambda运行时由Query/ExecutableQuery在游标推进过程中逐行执行并由跨平台驱动测试保证语义一致。实战中遵循文档的指引——凡是 SQL 能表达的投影交给 SQLSQL 表达不了的 Kotlin 类型映射交给 Mapper即可写出既精简又类型安全的查询层。赞分享后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载相关推荐SQLDelight 自定义投影Type Projections实战指南从类型安全 Mapper 到纯 SQL 投影SQLDelight 自定义投影Type Projections实战指南从类型安全 Mapper 到纯 SQL 投影 SQLDelight 会根据 .sq后端ORMSQLDelight 自定义投影Type Projections用类型安全 Mapper 定制查询返回类型SQLDelight 自定义投影Type Projections用类型安全 Mapper 定制查询返回类型 SQLDelight 默认会为每条查询自动生成后端ORMSQLDelight 自定义投影Type Projections用 mapper 与 SQL 掌控查询返回类型SQLDelight 自定义投影Type Projections用 mapper 与 SQL 掌控查询返回类型 本文基于 SQLDelight 官方文档中后端ORM上一篇Meteor 2.11 版本升级指南MongoDB 6.x 支持、驱动升级与破坏性变更全解析下一篇Fleet 4.89.0 发布深度解析Windows 部署体验增强、Android 变量全面落地与策略自动化历史回溯创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

MCP for Beginners 课程变更史:从 changelog.md 看 MCP 2026-07-28 规范演进与课程实战化路径

MCP for Beginners 课程变更史:从 changelog.md 看 MCP 2026-07-28 规范演进与课程实战化路径

教程文档人工智能 【免费下载链接】mcp-for-beginners This open-source curriculum introduces the fundamentals of Model Context Protocol (MCP) through real-world, cross-language examples in .NET, Java, TypeScript, JavaScript, Rust and Python. Designed for deve…

2026/10/9 14:48:15 阅读更多 →
Nuxt UI InputDate 组件实战指南:日期输入、范围选择与日期选择器的完整解析

Nuxt UI InputDate 组件实战指南:日期输入、范围选择与日期选择器的完整解析

前端UI组件 【免费下载链接】ui The Intuitive Vue UI Library powered by Reka UI & Tailwind CSS. 项目地址: https://gitcode.com/gh_mirrors/ui4/ui 点击查看 免费下载 UInputDate 是 Nuxt UI(本仓库 ui 项目)内置的日期输入组件&am…

2026/10/11 2:11:11 阅读更多 →
PostgreSQL 标识符 63 字节上限:截断机制、_fkey 后缀保护与 NAMEDATALEN 源码解析

PostgreSQL 标识符 63 字节上限:截断机制、_fkey 后缀保护与 NAMEDATALEN 源码解析

文档教程知识库 【免费下载链接】til :memo: Today I Learned 项目地址: https://gitcode.com/gh_mirrors/ti/til 点击查看 免费下载 PostgreSQL 对表名、列名、约束名等所有标识符施加了 63 字节的硬性长度上限,超长标识符并不会报错,而是被…

2026/10/11 21:47:01 阅读更多 →

最新新闻

风电水电联合调度优化算法与工程实践

风电水电联合调度优化算法与工程实践

1. 项目背景与核心价值去年参与某省级电网清洁能源消纳项目时,我第一次接触到风-水联合调度这个课题。当时面对风电出力的强随机性和水电调节的滞后性,团队在制定优化运行方案时遇到了不少挑战。这个经历让我深刻认识到:在双碳目标下&#xf…

2026/10/14 2:21:24 阅读更多 →
从PowerDesigner迁移到PDMan:开源数据库建模与Git版本管理实践

从PowerDesigner迁移到PDMan:开源数据库建模与Git版本管理实践

1. 为什么我最终把数据库建模从商业软件迁到了PDMan做后端开发时间长了,数据库建模这件事几乎绕不开。早些年团队里清一色用PowerDesigner,画物理模型、生成DDL、导出字段文档,一套流程走下来确实顺手。但问题也很明显:正版授权费…

2026/10/14 2:21:24 阅读更多 →
微信小程序仿智行火车票开发实战:云开发+数据离线化方案

微信小程序仿智行火车票开发实战:云开发+数据离线化方案

简介:本资源是一套完整的微信小程序仿智行火车票12306系统实战项目,面向Java后端与小程序前端初学者及全栈学习者,聚焦铁路订票核心业务场景,涵盖用户查询、车次管理、订单生成等全流程功能实现。项目采用SSM(SpringSp…

2026/10/14 2:21:23 阅读更多 →
RTMP Flash播放插件替代方案:flv.js迁移实战与参数调优

RTMP Flash播放插件替代方案:flv.js迁移实战与参数调优

简介:这份资源是面向需要在网页中播放RTMP流媒体的开发者与运维人员的一套Flash播放插件实现方案,核心解决旧系统或特定场景下通过Flash技术播放RTMP直播、点播流的问题。压缩包共5个文件,约1.15MB,包含2个swf播放器与安装检测组件…

2026/10/14 2:21:23 阅读更多 →
AI编程助手代码质量救星:TypeScript静态类型约束实战

AI编程助手代码质量救星:TypeScript静态类型约束实战

AI 编程助手越帮越乱,这个话题我最近真的是感同身受。我们团队接入 AI 编程助手之后,合并请求里的代码量倒是上去了,可编译期错误非但没减少,反而冒出来一批"语法完全合法、类型对不上、一跑就崩"的代码。后来大家坐下来…

2026/10/14 2:21:23 阅读更多 →
LaTeX 2025环境配置:texlive + TeXstudio

LaTeX 2025环境配置:texlive + TeXstudio

文章目录Latex2025环境配置一、LaTeX 简介1.1 什么是 LaTeX?1.2 为什么使用 LaTeX?1.3 如何开始使用 LaTeX?二、texlive下载安装2.1 texlive下载2.2 texlive安装2.3 常见问题2.3.1 用管理员权限打开install-tl-windows.bat 报错2.3.2 Latex安…

2026/10/14 2:20:23 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →