задний план
In database management systems (DBMS), a prepared statement or parameterized statement is a feature used to execute the same or similar database statements repeatedly with high efficiency. Typically used with SQL statements such as queries or updates, the prepared statement takes the form of a template into which certain constant values are substituted during each execution. (En. Wikipedia.org/wiki/prepar…)
В системе базы данных параметризованный оператор, с одной стороны, может обеспечить возможность предварительной компиляции, чтобы достичь цели эффективного выполнения оператора и повышения производительности. С другой стороны, это может предотвратить атаки SQL-инъекций, а безопасность лучше. Вышеуказанные два пункта являются основными причинами, по которым традиционные системы баз данных поддерживают операторы с параметрами.
С точки зрения системы базы данных OpenMLDB поддерживает оператор параметризованного запроса для дальнейшего улучшения возможностей запросов к базе данных. С точки зрения бизнеса это позволяет OpenMLDB поддерживать вычисление функций правил в сценарии механизма правил.
Пример сценария: вычисление функции обработчика правил
SELECT
SUM(trans_amount) as F_TRANS_AMOUNT_SUM,
COUNT(user) as F_TRANS_COUNT,
MAX(trans_amount) as F_TRANS_AMOUNT_MAX,
MIN(trans_amount) as F_TRANS_AMOUNT_MIN,
FROM t1 where user = 'ABC123456789' and trans_time between 1590115420000 and 1592707420000;
В примере мы считаем пользователяABC123456789
от2020-05-22 02:43:40
прибыть2020-06-20 07:43:40
в течение этого периодаобщая транзакция,количество транзакций,максимальная сумма сделки,Минимальная сумма транзакции. Эти функции будут переданы нижестоящим компонентам (механизм правил) для использования.
В реальных сценариях невозможно написать кусок кода SQL-запроса для каждого пользователя. Следовательно, требуется шаблон для регулярного расчета признаков, а пользовательский временной интервал динамически изменяется.
Самый простой способ — написать программу, подобную следующей, где имя пользователя и временной интервал вставлены в оператор SQL в качестве переменных.
String query = "SELECT "+
"SUM(trans_amount) as F_TRANS_AMOUNT_SUM, "+
"COUNT(user) as F_TRANS_COUNT,"+
"MAX(trans_amount) as F_TRANS_AMOUNT_MAX,"+
"MIN(trans_amount) as F_TRANS_AMOUNT_MIN,"+
"FROM t1 where user = '"+ user +"' and trans_time between "
+ System.currentTimestamp()-30*86400000+ " and " + System.currentTimestamp();
executor.execute(query);
Эта реализация проста, но производительность запросов будет низкой, и может возникнуть риск SQL-инъекций. Более рекомендуемый способ - использовать параметризованный запрос
PreparedStatement stmt = conn.prepareStatement("SELECT "+
"SUM(trans_amount) as F_TRANS_AMOUNT_SUM, "+
"COUNT(user) as F_TRANS_COUNT,"+
"MAX(trans_amount) as F_TRANS_AMOUNT_MAX,"+
"MIN(trans_amount) as F_TRANS_AMOUNT_MIN,"+
"FROM t1 where user = ? and trans_time between ? and ? ");
stmt.setString(1, user);
stmt.setTimestamp(2, System.currentTimestamp()-30*86400000);
stmt.setTimestamp(3, System.currentTimestamp())
ResultSet rs = stmt.executeQuery();
rs.next();
детали реализации
В OpenMLDB поддерживается новая функция синтаксиса, которую обычно нужно делать последовательноРазбор синтаксиса, генерация и оптимизация плана, выражение Codegen, выполнение запроса и другие шаги.При необходимости также необходимо рассмотреть возможность добавления или рефакторинга связанных интерфейсов на стороне клиента.Paramteried Query
Поддержка в основном охватывает модификацию и разработку вышеуказанных модулей, поэтому понимание соответствующих деталей реализации поможет вам быстро понять разработку OpenMLDB, особенно разработку OpenMLDB Engine.
На следующем рисунке представлена схема процесса выполнения запроса с параметрами.
- пользователь в приложении
JavaApplication
s использует JDBC (PreparedStatement) для выполнения запросов с параметрами. - Клиент (TabletClient) предоставляет интерфейс
ExecuteSQLParameterized
Обработать запрос с параметрами и вызвать сервер (планшет) через RPCQuery
Служить. - Сервер (планшет) использует модуль Engine для компиляции и выполнения запросов.
- Компиляция оператора запроса должна пройти три основных этапа: анализ синтаксиса SQL, создание и оптимизация плана и Codegen выражений. После успешной компиляции результат компиляции будет сохранен в контексте SQL (SqlContext) текущего сеанса выполнения (jizSeesion). Если текущий оператор запроса был предварительно скомпилирован, в повторной компиляции нет необходимости. Соответствующие продукты компиляции можно получить непосредственно из кэша компиляции и сохранить в SqlContext RunSession.
- Для выполнения оператора запроса необходимо вызвать функцию RunSeesion.
Run
интерфейс. Результатыrun output
Он будет сохранен во вложении к ответу и возвращен в TabletClient. наконец сохранено вResultSet
Вернуться кJavaApplication
1. JDBC PreparedStatement
1.1 Обзор подготовленных операторов JDBC
Sometimes it is more convenient to use a
PreparedStatement
object for sending SQL statements to the database. This special type of statement is derived from the more general class,Statement
, that you already know.
If you want to execute aStatement
object many times, it usually reduces execution time to use aPreparedStatement
object instead.[[2]](Using Prepared Statements)
JDBC предоставляетPreparedStatement
Оператор SQL, который выполняет параметры для пользователя. Пользователи могут использовать PrepareStatment для выполнения запросов, вставок, обновлений и т. д. операций с параметрами. В этом разделе мы поговорим о деталях OpenMLDB PrepareStatement, выполняющих операторы запроса с параметрами.
1.2 Введение в использование OpenMLDB PreapredStatement
public void parameterizedQueryDemo() {
SdkOption option = new SdkOption();
option.setZkPath(TestConfig.ZK_PATH);
option.setZkCluster(TestConfig.ZK_CLUSTER);
option.setSessionTimeout(200000);
try {
SqlExecutor executor = new SqlClusterExecutor(option);
String dbname = "demo_db";
boolean ok = executor.createDB(dbname);
// create table
ok = executor.executeDDL(dbname, "create table t1(user string, trans_amount double, trans_time bigint, index(key=user, ts=trans_time));");
// insert normal (1000, 'hello')
ok = executor.executeInsert(dbname, "insert into t1 values('user1', 1.0, 1592707420000);");
ok = executor.executeInsert(dbname, "insert into t1 values('user1', 2.0, 1592707410000);");
ok = executor.executeInsert(dbname, "insert into t1 values('user1', 3.0, 1592707400000);");
ok = executor.executeInsert(dbname, "insert into t1 values('user2', 4.0, 1592707420000);");
ok = executor.executeInsert(dbname, "insert into t1 values('user2', 5.0, 1592707410000);");
ok = executor.executeInsert(dbname, "insert into t1 values('user2', 6.0, 1592707400000);");
PreparedStatement query_statement
= executor.getPreparedStatement(dbname, "select SUM(trans_amout), COUNT(trans_amout), MAX(trans_amout) from t1 where user=? and trans_time between ? and ?");
query_statement.setString(1, "user1");
query_statement.setLong(2, 1592707410000);
query_statement.setLong(3, 1592707420000);
com._4paradigm.openmldb.jdbc.SQLResultSet rs1
= (com._4paradigm.openmldb.jdbc.SQLResultSet) query_statement.executeQuery();
query_statement.setString(1, "user2");
query_statement.setLong(2, 1592707410000);
query_statement.setLong(3, 1592707420000);
com._4paradigm.openmldb.jdbc.SQLResultSet rs2
= (com._4paradigm.openmldb.jdbc.SQLResultSet) query_statement.executeQuery();
query_statement.setString(1, "user3");
query_statement.setLong(2, 1592707410000);
query_statement.setLong(3, 1592707420000);
com._4paradigm.openmldb.jdbc.SQLResultSet rs3
= (com._4paradigm.openmldb.jdbc.SQLResultSet) query_statement.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
}
- Шаг 1: Создайте исполнителя. Подготовить базу данных, таблицы, табличные данные (при необходимости)
- Шаг 2. Создайте новый экземпляр PreparedStatement, используя оператор запроса с параметрами.
PreparedStatement query_statement
= executor.getPreparedStatement(dbname, "select SUM(trans_amout), COUNT(trans_amout), MAX(trans_amout) from t1 where user=? and trans_time between ? and ?");
- Шаг 3: Установите значение параметра в каждой позиции.
query_statement.setString(1, "user1");
query_statement.setLong(2, 1592707410000);
query_statement.setLong(3, 1592707420000);
- Шаг 4: Выполните запрос. Получить результаты запроса. Обратите внимание, что после выполнения запроса данные параметра в PrepareStatement будут автоматически очищены. Вы можете напрямую настроить новые значения параметров для нового раунда запросов
com._4paradigm.openmldb.jdbc.SQLResultSet rs2
= (com._4paradigm.openmldb.jdbc.SQLResultSet) query_statement.executeQuery();
query_statement.setString(1, "user2");
query_statement.setLong(2, 1592707410000);
query_statement.setLong(23, 1592707420000);
com._4paradigm.openmldb.jdbc.SQLResultSet rs2
= (com._4paradigm.openmldb.jdbc.SQLResultSet) query_statement.executeQuery();
1.3 Детали реализации PreparedStatement
public class PreparedStatement implements java.sql.PreparedStatement {
//...
// 参数行
protected String currentSql;
// 参数数据
protected TreeMap<Integer, Object> currentDatas;
// 参数类型
protected TreeMap<Integer, com._4paradigm.openmldb.DataType> types;
// 上次查询的参数类型
protected TreeMap<Integer, com._4paradigm.openmldb.DataType> orgTypes;
//...
}
PrepareaStatement
Унаследовал стандартный интерфейс JDBCjava.sql.PreparedStatement
. Он поддерживает некоторые основные элементы, необходимые для компиляции и выполнения запроса: оператор запроса (currentSql), данные параметров (currentDatas), типы параметров (types) и т. д.
- Построить
PrepareaStatement
После того, как мы инициализируемPreparedStatement
, и установитеcurrentSql
- После установки значения параметра
currentDatas
,types
будет обновлено. - При выполнении запроса
query_statement.executeQuery()
@Override
public SQLResultSet executeQuery() throws SQLException {
checkClosed();
dataBuild();
Status status = new Status();
com._4paradigm.openmldb.ResultSet resultSet = router.ExecuteSQLParameterized(db, currentSql, currentRow, status);
// ... 此处省略
return rs;
}
- Сначала выполните
dataBuild
: закодировать набор данных параметра в currentRow по типу и положению параметра. Стоит отметить, что если тип параметра не меняется, мы можем повторно использовать исходный экземпляр currentRow.
protected void dataBuild() throws SQLException {
// types has been updated, create new row container for currentRow
if (null == this.currentRow || orgTypes != types) {
// ... 此处省略
this.currentRow = SQLRequestRow.CreateSQLRequestRowFromColumnTypes(columnTypes);
this.currentSchema = this.currentRow.GetSchema();
this.orgTypes = this.types;
}
// ... 此处currentRow初始化相关的代码
for (int i = 0; i < this.currentSchema.GetColumnCnt(); i++) {
DataType dataType = this.currentSchema.GetColumnType(i);
Object data = this.currentDatas.get(i+1);
if (data == null) {
ok = this.currentRow.AppendNULL();
} else {
// 省略编码细节
// if (DataType.kTypeInt64.equals(dataType)) {
// ok = this.currentRow.AppendInt64((long) data);
// }
// ...
}
if (!ok) {
throw new SQLException("append data failed, idx is " + i);
}
}
if (!this.currentRow.Build()) {
throw new SQLException("build request row failed");
}
clearParameters();
}
}
- Затем вызовите интерфейс запроса с параметрами, предоставленными клиентом.
ExecuteSQLParameterized
.
2. TabletClient и Tablet
2.1 Клиент table_client
Клиент предоставляет интерфейсExecuteSQLParameterized
для поддержки запроса с параметрами.
/// Execute batch SQL with parameter row
std::shared_ptr<hybridse::sdk::ResultSet>
ExecuteSQLParameterized(const std::string& db, const std::string& sql,
std::shared_ptr<SQLRequestRow> parameter,
::hybridse::sdk::Status* status) override;
ExecuteSQLParameterized
начнется со строки параметровparameter
Извлеките тип параметра, размер строки параметра и другую информацию изQueryRequest
и загрузите строку данных параметра во вложение roc. Клиент вызывает rpc, запрос компилируется и запускается на сервере.
- Загрузить размер строки параметров, количество срезов, список типов параметров
QueryRequest
request.set_parameter_row_size(parameter_row.size());
request.set_parameter_row_slices(1);
for (auto& type : parameter_types) {
request.add_parameter_types(type);
}
- Строка данных параметра хранится во вложении rpc
cntl->request_attachment()
auto& io_buf = cntl->request_attachment();
if (!codec::EncodeRpcRow(reinterpret_cast<const int8_t*>(parameter_row.data()), parameter_row.size(), &io_buf)) {
LOG(WARNING) << "Encode parameter buffer failed";
return false;
}
- вызов RPC
bool ok = client_.SendRequest(&::openmldb::api::TabletServer_Stub::Query, cntl, &request, response);
2.2 Серверный планшет
Серверный планшетQuery
Функция отвечает за запуск сQueryRequest
Получите информацию о строке параметров из , а затем вызовите интерфейсengine_->Get()
[Скомпилируйте оператор запроса и вызовите интерфейсsession.Run](https://link.zhihu.com/?target=http%3A//xn--%2560session-i30nta9680a88mb6mci9ble5a3t6bkia4g4p.run/)()
Выполните оператор запроса.
::hybridse::vm::BatchRunSession session;
if (request->is_debug()) {
session.EnableDebug();
}
session.SetParameterSchema(parameter_schema);
{
bool ok = engine_->Get(request->sql(), request->db(), session, status);
// ...
}
::hybridse::codec::Row parameter_row;
auto& request_buf = static_cast<brpc::Controller*>(ctrl)->request_attachment();
if (request->parameter_row_size() > 0 &&
!codec::DecodeRpcRow(request_buf, 0, request->parameter_row_size(), request->parameter_row_slices(),
¶meter_row)) {
response->set_code(::openmldb::base::kSQLRunError);
response->set_msg("fail to decode parameter row");
return;
}
std::vector<::hybridse::codec::Row> output_rows;
int32_t run_ret = session.run(parameter_row, output_rows);
Для получения более подробной информации вы можете прочитатьпланшетный клиент и tabletРеализация исходного кода .
3. Компиляция: скомпилируйте оператор запроса.
3.1 Компиляция операторов запроса
- Шаг 1: Для операторов запроса с параметрами во время компиляции требуется дополнительная информация о типе параметра конфигурации по сравнению с обычными запросами.
session.SetParameterSchema(parameter_schema);
- шаг 2: После настройки списка параметров вызовите
engine.Get(...)
Интерфейс для компиляции операторов SQL
Компиляция оператора запроса должна пройти черезАнализ синтаксиса SQL(3.2. Парсер: разбор синтаксиса),составление плана (3.3 Планировщик: создание плана),Кодеген выражений (3.4 Codegen: генерация кода для выражений) три основных этапа. После успешного завершения компиляции результат компиляции будет сохранен в контексте SQL (SqlContext) текущего сеанса выполнения (RunSeesion). В следующих нескольких подразделах будет последовательно представлен процесс компиляции операторов запросов с параметрами.
Если текущий оператор запроса был предварительно скомпилирован, в повторной компиляции нет необходимости. Соответствующие продукты компиляции можно получить непосредственно из кэша компиляции и сохранить в SqlContext RunSession. Нам нужно обратить особое внимание на изменения дизайна кэша компиляции.Для запросов с параметрами для попадания в кеш требуются обасовпадениеСписок типов SQL и параметров.
// check if paramter types matches with target compile result or not.
for (int i = 0; i < batch_sess->GetParameterSchema().size(); i++) {
if (cache_ctx.parameter_types.Get(i).type() != batch_sess->GetParameterSchema().Get(i).type()) {
status = Status(common::kEngineCacheError, "Inconsistent cache parameter type, expect " +
batch_sess->GetParameterSchema().Get(i).DebugString()," but get ", cache_ctx.parameter_types.Get(i).DebugString());
return false;
}
}
3.2 Парсер: разбор синтаксиса
Интерпретатор синтаксиса для OpenMLDB основан наZetaSQL
Разработано интерпретатором SQL: помимо покрытия исходных синтаксических возможностей Zetasql, он также дополнительно поддерживает уникальные синтаксические функции OpenMLDb. Например, введены специальные типы правописания для сценариев ИИ.LastJoin
и тип окнаROWS_RANGE
Ждать. Анализ синтаксиса и новые синтаксические функции OpenMLDB будут объяснены в будущих технических статьях.
Параметризованный оператор SQL использует?
В качестве заполнителей для параметров такие заполнители анализируются интерпретатором ZetaSQL какzetasql::ASTParameterExpr
. так какZetaSQL
Синтаксический анализ оператора параметризованного запроса уже поддерживается, поэтому нам не нужно вносить слишком много дополнительных изменений в модуль синтаксического анализа.Нам нужно только открыть исходное ограничение, идентифицировать это выражение параметра и преобразовать его в OpenMLDB.ParameterExpr
тип узлов выражений и хранится в синтаксическом дереве.
/// Convert zetasql::ASTExpression into ExprNode
base::Status ConvertExprNode(const zetasql::ASTExpression* ast_expression, node::NodeManager* node_manager,
node::ExprNode** output) {
//...
base::Status status;
switch (ast_expression->node_kind()) {
//...
case zetasql::AST_PARAMETER_EXPR: {
const zetasql::ASTParameterExpr* parameter_expr =
ast_expression->GetAsOrNull<zetasql::ASTParameterExpr>();
CHECK_TRUE(nullptr != parameter_expr, common::kSqlAstError, "not an ASTParameterExpr")
// Only support anonymous parameter (e.g, ?) so far.
CHECK_TRUE(nullptr == parameter_expr->name(), common::kSqlAstError,
"Un-support Named Parameter Expression ", parameter_expr->name()->GetAsString());
*output = node_manager->MakeParameterExpr(parameter_expr->position());
return base::Status::OK();
}
//...
}
}
Например, следующий оператор запроса параметра:
SELECT col0 FROM t1 where col1 <= ?;
После синтаксического анализа будет сгенерировано следующее дерево синтаксиса запроса:
+-list[list]:
+-0:
+-node[kQuery]: kQuerySelect
+-distinct_opt: false
+-where_expr:
| +-expr[binary]
| +-<=[list]:
| +-0:
| | +-expr[column ref]
| | +-relation_name: <nil>
| | +-column_name: col1
| +-1:
| +-expr[parameter]
| +-position: 1
+-group_expr_list: null
+-having_expr: null
+-order_expr_list: null
+-limit: null
+-select_list[list]:
| +-0:
| +-node[kResTarget]
| +-val:
| | +-expr[column ref]
| | +-relation_name: <nil>
| | +-column_name: col0
| +-name: <nil>
+-tableref_list[list]:
| +-0:
| +-node[kTableRef]: kTable
| +-table: t1
| +-alias: <nil>
+-window_list: []
Здесь вы можете сосредоточиться на условиях фильтрации, where col1 <= ?
анализируется как:
+-where_expr:
| +-expr[binary]
| +-<=[list]:
| +-0:
| | +-expr[column ref]
| | +-relation_name: <nil>
| | +-column_name: col1
| +-1:
| +-expr[parameter]
| +-position: 1
3.3 Планировщик: Генерация плана
- логический план
На этапе логического планирования нет разницы между запросом с параметрами и обычными параметрами. Поэтому в данной статье не предполагается подробно раскрывать логический план. Следующий оператор запроса параметра:
SELECT col0 FROM t1 where col1 <= ?;
Логический план таков:
: +-[kQueryPlan]
+-[kProjectPlan]
+-table: t1
+-project_list_vec[list]:
+-[kProjectList]
+-projects on table [list]:
| +-[kProjectNode]
| +-[0]col0: col0
+-[kFilterPlan]
+-condition: col1 <= ?1
+-[kTablePlan]
Читатели, интересующиеся деталями логического плана, а также физического плана, могут следить за нашей колонкой. В дальнейшем одна за другой будет запускаться серия статей, знакомящих с техническими подробностями двигателя.
- Программа по физике
На этапе создания физического плана для поддержки запроса с параметрами необходимо сделать две вещи:
Во-первых, в контексте физического планирования, в контексте анализа выражений и в контексте CodeGen.Ведение списка типов параметров.
В операторе запроса с параметрами параметры, используемые в окончательном выполнении, динамически задаются пользователем, поэтому тип параметра также динамически задается извне. С этой целью мы предоставляем соответствующие интерфейсы, чтобы пользователи могли настраиватьСписок типов параметров(если есть параметры). Этот список в конечном итоге будет храниться в контексте физического планирования, контексте анализа выражений и контексте CodeGen.
// 物理计划上下文
class PhysicalPlanContext {
// ...
private:
const codec::Schema* parameter_types_;
}
// 表达式分析上下文
class ExprAnalysisContext {
// ...
private:
const codec::Schema* parameter_types_;
}
// Codegen上下文
class CodeGenContext {
// ...
private:
const codec::Schema* parameter_types_;
}
Во-вторых, это делается в соответствии со списком типов параметров.Вывод типа для выражений параметров.
После того, как параметризованный оператор запроса завершает интерпретацию синтаксиса, это почти синтаксическое дерево, сгенерированное обычным оператором запроса. Единственное отличие состоит в том, что дерево синтаксиса параметризованного запроса имеет узлы выражений параметров (ParamterExpr
). Поскольку тип параметра не связан ни со схемой восходящей таблицы запроса, ни с константой. Поэтому мы не можем напрямуюВывод типа для выражений параметров. Это заставляет нас на этапе генерации плана, особенно в процессе вывода типов выражений, нам необходимоParamterExpr
особый уход. Конкретный подход: выводParamterExpr
При выводе типа нужно начинать с параметра в соответствии с расположением параметраСписок типов параметровНайдите подходящий тип в .
Status ParameterExpr::InferAttr(ExprAnalysisContext *ctx) {
// ignore code including boundary check and nullptr check
// ...
type::Type parameter_type = ctx->parameter_types()->Get(position()-1).type();
node::DataType dtype;
CHECK_TRUE(vm::SchemaType2DataType(parameter_type, &dtype), kTypeError,
"Fail to convert type: ", parameter_type);
SetOutputType(ctx->node_manager()->MakeTypeNode(dtype));
return Status::OK();
}
Тем не менее предыдущий оператор SQL, физический план генерирует следующие результаты:
SIMPLE_PROJECT(sources=(col0))
FILTER_BY(condition=col1 <= ?1, left_keys=, right_keys=, index_keys=)
DATA_PROVIDER(table=auto_t0)
в,FILTER_BY
Условие фильтра в узле содержит выражение параметраcondition=(col1 <= ?1)
3.4 Codegen: Генерация кода для выражений
Модуль Codegen отвечает за анализ списка выражений каждого узла плана, а затем за выполнение обработки генерации кода для ряда выражений и функций. После codegen каждый узел плана, которому необходимо оценить выражения, сгенерирует по крайней мере одну функцию codegen. Эти функции отвечают за оценку вычислений выражений.
- Функция Codegen добавляет параметр
OpenMLDB генерирует промежуточный код (IR) для каждого узла, участвующего в оценке выражения через LLVM. Конкретная реализация заключается в создании аналогичного списка выражений для каждого узла.@__internal_sql_codegen_6
функции (эти функции будут вызываться во время выполнения оператораперечислить(4 Run: выполнение оператора запроса):
; ModuleID = 'sql'
source_filename = "sql"
define i32 @__internal_sql_codegen_6(i64 /*row key id*/,
i8* /*row ptr*/,
i8* /*rows ptr*/,
i8** /*output row ptr ptr*/) {
__fn_entry__:
// 此处省略
}
Параметры этой функции в основном включают некоторыеint_8
указатели, которые указывают на строки данных (row ptr
) или набор данных (rows ptr
) (агрегированные вычисления зависят от набора данных). Тело функции отвечает за вычисление каждого выражения, и кодирует результаты в строки по порядку, и кодирует адрес до последнегоi8**
по выходному параметру.
Когда список выражений содержит выражения параметров, нам дополнительно необходимо получить данные параметров, поэтому необходимо добавить указатель на строку параметра в исходной структуре функции (parameter_row ptr
).
Status RowFnLetIRBuilder::Build(...) { // 此处省略
std::vector<std::string> args;
std::vector<::llvm::Type*> args_llvm_type;
args_llvm_type.push_back(::llvm::Type::getInt64Ty(module->getContext()));
args_llvm_type.push_back(::llvm::Type::getInt8PtrTy(module->getContext()));
args_llvm_type.push_back(::llvm::Type::getInt8PtrTy(module->getContext()));
args_llvm_type.push_back(::llvm::Type::getInt8PtrTy(module->getContext())); // 新增一个int8ptr类型的参数
args_llvm_type.push_back(
::llvm::Type::getInt8PtrTy(module->getContext())->getPointerTo());
// ...
}
тогда,После поддержки выражений параметров, структура функции codegen становится следующей:
; ModuleID = 'sql'
source_filename = "sql"
define i32 @__internal_sql_codegen_6(i64 /*row key id*/,
i8* /*row ptr*/,
i8* /*rows ptr*/,
i8* /*parameter row ptr*/,
i8** /*output row ptr ptr*/) {
__fn_entry__:
// 此处省略
}
- codegen для выражений параметров
Строка параметров такая же, как и обычная строка данных, и соответствует формату кодирования OpenMLDB.0-й элемент строки параметров — это первый параметр в операторе запроса параметров, первый элемент — второй параметр и т. д. следовательно,Вычислить выражения параметровфактическиСчитайте параметр в соответствующей позиции из строки параметров.
// Get paramter item from parameter row
// param parameter
// param output
// return
Status ExprIRBuilder::BuildParameterExpr(const ::hybridse::node::ParameterExpr* parameter, NativeValue* output) {
// ...
VariableIRBuilder variable_ir_builder(ctx_->GetCurrentBlock(), ctx_->GetCurrentScope()->sv());
NativeValue parameter_row;
// 从全局scope中获取参数行parameter_row
CHECK_TRUE(variable_ir_builder.LoadParameter(¶meter_row, status), kCodegenError, status.msg);
// ...
// 从参数行中读取相应位置的参数
CHECK_TRUE(
buf_builder.BuildGetField(parameter->position()-1, slice_ptr, slice_size, output),
kCodegenError, "Fail to get ", parameter->position(), "th parameter value")
return base::Status::OK();
}
Итак, оператор запроса в предыдущем примереFilter
состояние узлаcol1 < ?
Будет сгенерирован следующий код:
; ModuleID = 'sql'
source_filename = "sql"
define i32 @__internal_sql_codegen_6(i64, i8*, i8*, i8*, i8**) {
__fn_entry__:
%is_null_addr1 = alloca i8, align 1
%is_null_addr = alloca i8, align 1
// 获取行指针row = {col0, col1, col2, col3, col4, col5}
%5 = call i8* @hybridse_storage_get_row_slice(i8* %1, i64 0)
%6 = call i64 @hybridse_storage_get_row_slice_size(i8* %1, i64 0)
// Get field row[1] 获取数据col1
%7 = call i32 @hybridse_storage_get_int32_field(i8* %5, i32 1, i32 7, i8* nonnull %is_null_addr)
%8 = load i8, i8* %is_null_addr, align 1
// 获取参数行指针paramter_row = {?1}
%9 = call i8* @hybridse_storage_get_row_slice(i8* %3, i64 0)
%10 = call i64 @hybridse_storage_get_row_slice_size(i8* %3, i64 0)
// Get field of paramter_row[0] 获取第一个参数
%11 = call i32 @hybridse_storage_get_int32_field(i8* %9, i32 0, i32 7, i8* nonnull %is_null_addr1)
%12 = load i8, i8* %is_null_addr1, align 1
%13 = or i8 %12, %8
// 比较 col1 <= ?1
%14 = icmp sle i32 %7, %11
// ... 此处省略多行
// 将比较结果%14编码输出
store i1 %14, i1* %20, align 1
ret i32 0
}
Здесь мы не собираемся расширять специфику кодегена. В будущем технические статьи, связанные с дизайном и оптимизацией Codegen, будут обновляться одна за другой. Если вы заинтересованы, вы можете продолжать уделять внимание технической колонке OpenMLDB.
4. Выполнить: выполнение оператора запроса
- После компиляции оператора запроса скомпилированный продукт сохраняется в текущем выполняющемся сеансе (RunSession).
- RunSession обеспечивает
Run
Интерфейс поддерживает выполнение операторов запроса. Для оператора запроса с параметрами при выполнении запроса по сравнению с обычным запросом необходимо передавать дополнительную информацию о строке параметра.
session.run(parameter_row, outputs)
- строка параметров
paramter_row
будет храниться взапустить контекстRunContext
середина:
RunnerContext ctx(&sql_ctx.cluster_job, parameter_row, is_debug_);
- При запросе с параметрами вычисление выражений может зависеть от динамически передаваемых параметров. Следовательно, нам необходимо получить строку параметров из контекста выполнения и вывести ее в функцию-выражение для расчета при выполнении плана. Возьмем в качестве примера узел TableProject.
- Для обычных запросов реализация TableProject заключается в обходе каждой строки в таблице, а затем выполнении
RowProject
работать. В сценарии запроса с параметрами, поскольку вычисление выражения может зависеть от параметра помимо строки данных. Итак, нам нужно получить строку параметра из контекста строки запуска, а затемproject_gen_.Gen(iter->GetValue(), parameter)
.
std::shared_ptr<DataHandler> TableProjectRunner::Run(
RunnerContext& ctx,
const std::vector<std::shared_ptr<DataHandler>>& inputs) {
// ... 此处省略部分代码
// 从运行上下文中获取参数行(如果没有则获得一个空的行指针
auto& parameter = ctx.GetParameterRow();
iter->SeekToFirst();
int32_t cnt = 0;
while (iter->Valid()) {
if (limit_cnt_ > 0 && cnt++ >= limit_cnt_) {
break;
}
// 遍历表中每一行,计算每一个行的表达式列表
output_table->AddRow(project_gen_.Gen(iter->GetValue(), parameter));
iter->Next();
}
return output_table;
}
const Row ProjectGenerator::Gen(const Row& row, const Row& parameter) {
return CoreAPI::RowProject(fn_, row, parameter, false);
}
-
CoreAPI::RowProject
Строка данных функции и строка параметра для оценки списка выражений. Его самая важная задача — вызвать функцию fn. Функция fn — это функция, сформированная в соответствии со списком выражений Codegen во время компиляции оператора запроса. в подразделегенерация кода для выражений(3.4 Codegen: Генерация кода выражений) Как мы уже говорили, мы добавляем указатель строки параметра в список параметров функции codegen.
// 基于输入数据行和参数行计算表达式列表并输出
hybridse::codec::Row CoreAPI::RowProject(const RawPtrHandle fn,
const hybridse::codec::Row row,
const hybridse::codec::Row parameter,
const bool need_free) {
// 此处省略部分代码
auto udf = reinterpret_cast<int32_t (*)(const int64_t, const int8_t*,
const int8_t* /*paramter row*/,
const int8_t*, int8_t**)>(
const_cast<int8_t*>(fn));
auto row_ptr = reinterpret_cast<const int8_t*>(&row);
auto parameter_ptr = reinterpret_cast<const int8_t*>(¶meter);
int8_t* buf = nullptr;
uint32_t ret = udf(0, row_ptr, nullptr, parameter_ptr, &buf);
// 此处省略部分代码
return Row(base::RefCountedSlice::CreateManaged(
buf, hybridse::codec::RowView::GetSize(buf)));
}
Будущая работа
Предварительная компиляция PreparedStatement выполняется на серверном планшете, и результат компиляции, созданный в результате предварительной компиляции, будет кэшироваться на планшете. В следующем запросе, если оператор SQL и тип параметра успешно совпадают, результат компиляции можно использовать повторно. Но это означает, что каждый раз, когда клиент выполняет запрос, оператор SQL и типы параметров должны передаваться на серверный планшет. Если оператор запроса очень длинный, эту часть накладных расходов можно сэкономить. Поэтому в нашем дизайне еще есть место для оптимизации. Рассмотрите возможность создания уникального предварительно скомпилированного QID запроса на стороне сервера, который будет передан обратно клиенту и сохранен в контексте PrepareStatemetn. Пока тип параметра запроса не меняется, клиент может выполнить запрос по QID и параметру. Таким образом, можно уменьшить нагрузку на передачу оператора запроса.
std::shared_ptr<hybridse::sdk::ResultSet>
ExecuteSQLParameterized(const std::string& db, const std::string& qid,
std::shared_ptr<SQLRequestRow> parameter,
::hybridse::sdk::Status* status) override;
Приглашаем больше разработчиков подписаться и принять участиеOpenMLDBПроект с открытым исходным кодом.