MySQL X Plugin 文档操作(以 Find/查询为例)流程整理
文件位置 x\src\find_statement_builder.cc
点击查看代码
/*
* Copyright (c) 2015, 2025, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "plugin/x/src/find_statement_builder.h"
#include <algorithm>
#include "plugin/x/src/ngs/protocol/protocol_protobuf.h"
#include "plugin/x/src/xpl_error.h"
namespace xpl {
void Find_statement_builder::build(const Find &msg) const {
if (!is_table_data_model(msg) && msg.grouping_size() > 0)
add_document_statement_with_grouping(msg);
else
add_statement_common(msg);
}
void Find_statement_builder::add_statement_common(const Find &msg) const {
m_builder.put("SELECT ");
if (is_table_data_model(msg))
add_table_projection(msg.projection());
else
add_document_projection(msg.projection());
m_builder.put(" FROM ");
add_collection(msg.collection());
add_filter(msg.criteria());
add_grouping(msg.grouping());
add_grouping_criteria(msg.grouping_criteria());
add_order(msg.order());
add_limit(msg, false);
add_row_locking(msg);
}
namespace {
const char *const DERIVED_TABLE_NAME = "`_DERIVED_TABLE_`";
} // namespace
void Find_statement_builder::add_document_statement_with_grouping(
const Find &msg) const {
if (msg.projection_size() == 0)
throw ngs::Error_code(ER_X_BAD_PROJECTION,
"Invalid empty projection list for grouping");
m_builder.put("SELECT ");
add_document_object(
msg.projection(),
&Find_statement_builder::add_document_primary_projection_item);
m_builder.put(" FROM (");
m_builder.put("SELECT ");
add_table_projection(msg.projection());
m_builder.put(" FROM ");
add_collection(msg.collection());
add_filter(msg.criteria());
add_grouping(msg.grouping());
add_grouping_criteria(msg.grouping_criteria());
add_order(msg.order());
add_limit(msg, false);
m_builder.put(") AS ").put(DERIVED_TABLE_NAME);
add_row_locking(msg);
}
void Find_statement_builder::add_table_projection(
const Projection_list &projection) const {
if (projection.size() == 0) {
m_builder.put("*");
return;
}
m_builder.put_list(
projection, std::bind(&Find_statement_builder::add_table_projection_item,
this, std::placeholders::_1));
}
void Find_statement_builder::add_table_projection_item(
const Projection &item) const {
m_builder.put_expr(item.source());
add_alias(item);
}
void Find_statement_builder::add_document_projection(
const Projection_list &projection) const {
if (projection.size() == 0) {
m_builder.put("doc");
return;
}
if (projection.size() == 1 && !projection.Get(0).has_alias() &&
projection.Get(0).source().type() == Mysqlx::Expr::Expr::OBJECT) {
m_builder.put_expr(projection.Get(0).source()).put(" AS doc");
return;
}
add_document_object(projection,
&Find_statement_builder::add_document_projection_item);
}
void Find_statement_builder::add_document_object(
const Projection_list &projection, const Object_item_adder &adder) const {
m_builder.put("JSON_OBJECT(")
.put_list(projection, std::bind(adder, this, std::placeholders::_1))
.put(") AS doc");
}
void Find_statement_builder::add_document_projection_item(
const Projection &item) const {
if (!item.has_alias())
throw ngs::Error(ER_X_PROJ_BAD_KEY_NAME, "Invalid projection target name");
m_builder.put_quote(item.alias()).put(", ").put_expr(item.source());
}
void Find_statement_builder::add_document_primary_projection_item(
const Projection &item) const {
if (!item.has_alias())
throw ngs::Error(ER_X_PROJ_BAD_KEY_NAME, "Invalid projection target name");
m_builder.put_quote(item.alias())
.put(", ")
.put(DERIVED_TABLE_NAME)
.dot()
.put_identifier(item.alias());
}
void Find_statement_builder::add_grouping(const Grouping_list &group) const {
if (group.size() > 0)
m_builder.put(" GROUP BY ").put_list(group, &Generator::put_expr);
}
void Find_statement_builder::add_grouping_criteria(
const Grouping_criteria &criteria) const {
if (criteria.IsInitialized()) m_builder.put(" HAVING ").put_expr(criteria);
}
void Find_statement_builder::add_row_locking(const Find &msg) const {
if (!msg.has_locking()) {
if (msg.has_locking_options())
throw ngs::Error(ER_X_BAD_LOCKING,
"Invalid \"find\" message, \"locking\" "
"field is required when \"locking_options\" is set.");
return;
}
const auto lock_type = msg.locking();
if (lock_type == Find::SHARED_LOCK)
m_builder.put(" FOR SHARE");
else if (lock_type == Find::EXCLUSIVE_LOCK)
m_builder.put(" FOR UPDATE");
if (!msg.has_locking_options()) return;
const auto lock_options = msg.locking_options();
if (lock_options == Find::NOWAIT)
m_builder.put(" NOWAIT");
else if (lock_options == Find::SKIP_LOCKED)
m_builder.put(" SKIP LOCKED");
}
} // namespace xpl
流程整理
-
- 客户端请求阶段
DevAPI调用
例如:coll.find('$.age > 18').fields("name", "age").execute();
X Protocol消息
客户端将 find 操作以 X Protocol 的 Find 消息格式发送到 MySQL 服务器的 X Plugin(通常监听33060端口)。
- 客户端请求阶段
-
- X Plugin 解析与调度
命令分发
X Plugin 收到消息后,识别命令类型为 Find,调度到Prepared_statement_builder::build(const Find &msg)。
模型判断
内部调用 is_table_data_model(msg) 判断是表模型还是文档模型(collection),文档模型则走文档分支。
- X Plugin 解析与调度
-
- SQL 生成阶段(源码主流程)
3.1 入口分支
- SQL 生成阶段(源码主流程)
void Find_statement_builder::build(const Find &msg) const {
if (!is_table_data_model(msg) && msg.grouping_size() > 0)
add_document_statement_with_grouping(msg); // 文档+分组,特殊处理
else
add_statement_common(msg); // 其他情况,普通处理
}
带分组的文档查询:调用 add_document_statement_with_grouping
普通文档查询:调用 add_statement_common
3.2 普通文档查询 SQL 构造
void Find_statement_builder::add_statement_common(const Find &msg) const {
m_builder.put("SELECT ");
// 投影字段(文档模型走 add_document_projection)
add_document_projection(msg.projection());
m_builder.put(" FROM ");
add_collection(msg.collection());
add_filter(msg.criteria());
add_grouping(msg.grouping());
add_grouping_criteria(msg.grouping_criteria());
add_order(msg.order());
add_limit(msg, false);
add_row_locking(msg);
}
细节
add_document_projection:决定 SELECT 的内容(doc、JSON_OBJECT、expr AS doc 等)
add_collection:FROM 子句,指定集合名
add_filter:WHERE 子句,支持 JSON 路径表达式(如 JSON_EXTRACT(doc, '$.age') > 18)
add_grouping、add_grouping_criteria:GROUP BY/HAVING
add_order/add_limit/add_row_locking:ORDER BY/LIMIT/并发控制
3.3 文档+分组的特殊SQL生成
void Find_statement_builder::add_document_statement_with_grouping(const Find &msg) const {
m_builder.put("SELECT ");
add_document_object(msg.projection(), &Find_statement_builder::add_document_primary_projection_item);
m_builder.put(" FROM (");
m_builder.put("SELECT ");
add_table_projection(msg.projection());
m_builder.put(" FROM ");
add_collection(msg.collection());
add_filter(msg.criteria());
add_grouping(msg.grouping());
add_grouping_criteria(msg.grouping_criteria());
add_order(msg.order());
add_limit(msg, false);
m_builder.put(") AS ").put(DERIVED_TABLE_NAME);
add_row_locking(msg);
}
细节
外层 SELECT 用 JSON_OBJECT 聚合分组结果,保证返回仍是文档风格。
内层 SELECT 负责真正的分组聚合,FROM/WHERE/GROUP BY/HAVING/ORDER BY/LIMIT 都在内层拼接。
3.4 投影细节(add_document_projection)
void Find_statement_builder::add_document_projection(
const Projection_list &projection) const {
if (projection.size() == 0) {
m_builder.put("doc");
return;
}
if (projection.size() == 1 && !projection.Get(0).has_alias() &&
projection.Get(0).source().type() == Mysqlx::Expr::Expr::OBJECT) {
m_builder.put_expr(projection.Get(0).source()).put(" AS doc");
return;
}
add_document_object(projection,
&Find_statement_builder::add_document_projection_item);
}
无投影:查整个文档 SELECT doc
单对象无别名:SELECT
多字段或有别名:SELECT JSON_OBJECT(...) AS doc,每个字段用 add_document_projection_item 拼接
3.5 JSON_OBJECT拼接(add_document_object/add_document_projection_item)
void Find_statement_builder::add_document_object(
const Projection_list &projection, const Object_item_adder &adder) const {
m_builder.put("JSON_OBJECT(")
.put_list(projection, std::bind(adder, this, std::placeholders::_1))
.put(") AS doc");
}
void Find_statement_builder::add_document_projection_item(
const Projection &item) const {
if (!item.has_alias())
throw ngs::Error(ER_X_PROJ_BAD_KEY_NAME, "Invalid projection target name");
m_builder.put_quote(item.alias()).put(", ").put_expr(item.source());
}
结果形如:SELECT JSON_OBJECT("name", JSON_EXTRACT(doc, '$.name'), ...) AS doc
-
- SQL 执行与结果返回
由 X Plugin 调用 SQL 层执行生成的 SQL。
查询结果按 JSON 文档格式(doc 列)返回,X Plugin 再封装为 X Protocol 消息,发回客户端。
客户端收到后解包为 DevAPI 的文档对象。
- SQL 执行与结果返回
-
- 关键特性与优势
自动区分表/文档模型,自动生成合适SQL
支持复杂 JSON 路径/条件、分组聚合、字段投影
所有文档操作都通过 SQL 层处理,兼容 MySQL 权限/事务/并发机制
返回结构总是符合 DevAPI 文档模型要求(JSON 对象)
- 关键特性与优势
-
- 流程图(文字版)
DevAPI find()
↓
X Protocol Find msg
↓
X Plugin Prepared_statement_builder::build
↓
Find_statement_builder::build
↓
├─(文档+分组)─> add_document_statement_with_grouping
└─(普通)─────> add_statement_common
↓
add_document_projection → add_document_object/JSON_OBJECT
↓
其他子句拼接
↓
生成 SQL
↓
SQL 层执行
↓
结果(doc/JSON_OBJECT)封装回 X Protocol
↓
客户端收到文档对象
- 总结
X Plugin 对文档操作的流程本质是:
用 DevAPI/X Protocol 收到结构化命令,根据模型类型和操作细节,动态生成 SQL(大量用到 JSON 函数和 JSON_OBJECT),交由 SQL 层执行,最终保证返回的结果始终是“文档风格(JSON)”。
源码正是这一自动 SQL 生成机制的核心实现,涵盖了所有 find 和投影、分组、聚合、锁定、过滤等关键逻辑。

浙公网安备 33010602011771号