# Manticore Search中的预处理语句

Manticore Search中的预处理语句通过防止SQL注入来提高安全性，并且由于不需要在许多情况下处理转义，因此使构建查询更加容易。学习如何通过MySQL协议使用它们。

想象一下你正在构建一个强大的搜索应用程序。用户输入关键词，你的后端需要查询Manticore Search数据库以找到匹配的结果。一种常见（且诱人的！）方法是将用户输入直接嵌入到SQL查询中。例如，你可能会通过数字字段（如类别或记录ID）进行过滤。如果用户传递一个正常值如`5`，查询是`SELECT * FROM products WHERE id=5`。但如果他们传递`1 OR 1=1`？查询会变成`SELECT * FROM products WHERE id=1 OR 1=1`——条件始终为真，因此查询会返回所有行而不是一行。这就是SQL注入。

幸运的是，有一种更安全且更高效的方法：**预处理语句**。本质上，预处理语句将你的SQL代码与传递的数据分离开来。你不需要每次构建整个查询字符串，而是定义一次带有占位符的查询结构，然后单独提供搜索项。你可以在[Wikipedia](https://en.wikipedia.org/wiki/Prepared_statement)上了解更多关于这个概念的信息。

Manticore Search通过标准的MySQL协议支持预处理语句，这为你构建安全的搜索应用程序提供了一个强大的工具。通过使用预处理语句，你不仅会显著降低SQL注入的风险，还会提高代码的可读性。

预处理语句不仅仅是一个功能；有时它们是必需的。例如，Rust的`sqlx`库仅使用预处理语句与MySQL端点进行交互。此外，一些启用MS SQL与MySQL服务器通信的OLE DB连接器在内部使用预处理语句。

## 为什么使用预处理语句？

**首要考虑：安全（SQL注入）**：SQL注入是一种网络安全隐患，允许攻击者干扰应用程序对数据库的查询。当用户输入被不正确地合并到SQL查询中时，就会发生这种情况，从而允许执行恶意代码。例如，考虑一个通过直接将用户的搜索词连接到SQL中构建的简单搜索查询：

```php
// Vulnerable code example (DO NOT USE!)
$productId = $_GET['search'];
$query = "SELECT * FROM products WHERE id= " . $productId;
```

如果`$productId`包含类似`0 OR 1=1`的内容，查询会变成`SELECT * FROM products WHERE id= 0 OR 1=1`，这实际上会绕过WHERE子句并返回所有产品。

预处理语句通过将用户输入严格视为*数据*，而不是SQL命令的一部分，来防止这种情况。数据库驱动程序处理转义和引号，确保任何可能有害的字符都被中和。以下是使用预处理语句的相同查询：

```php
// Secure code example using a prepared statement
$productId = $_GET['search'];
$stmt = $mysqli->prepare("SELECT * FROM products WHERE id= ?");
$stmt->bind_param("i", $productId);
$stmt->execute();
```

在这种情况下，即使`$productId`包含恶意代码，它也会被视为字面值，而不是可执行的SQL。

## 它们是如何工作的

预处理语句通过一个简单的三步过程进行操作：

1. **准备**：首先，你将带有占位符（如`?`或`?VEC?`）的SQL语句发送到Manticore Search。Manticore解析该语句并创建查询计划。然后它返回此预处理语句的唯一标识符。
2. **绑定**：接下来，你将实际数据——占位符的值——单独发送到Manticore。这是安全性的关键所在；数据被纯粹视为数据，而不是SQL代码。
3. **执行**：最后，你指示Manticore使用存储的查询计划和绑定参数来执行预处理语句。

把它想象成创建一个模板。你一次构建结构，然后每次需要使用它时用不同的信息填充空白。

## 参数占位符：`?` & `?VEC?`

Manticore Search使用特定的占位符来识别预处理语句中的参数：
- `?`表示单个参数——这可以是整数、浮点数或字符串。使用此占位符时，Manticore会自动处理字符串值的转义和引号，防止SQL注入并确保数据格式正确。
- `?VEC?`专为数字值列表设计。它期望一个包含用逗号和可选空格分隔的数字的字符串（例如，`1, 2.3, 4, 1e-10, INF`）。关键的是，*不会对`?VEC?`中的值进行转义或引号处理*。有效输入仅包含数字、逗号和空格；任何其他字符可能会导致错误。这使其非常适合直接插入数值向量到你的数据中——包括浮点向量和整数MVAs（多值属性）。


## 示例：PHP中的预处理语句

让我们看看预处理语句在PHP中是如何实际工作的。我们将演示一个简单的字符串值插入和一个更复杂的涉及使用`?VEC?`占位符的浮点向量插入。

首先，一个基本的插入：

```php
<?php
// Assuming you have a valid MySQLi connection established ($mysqli)

$stmt = $mysqli->prepare("INSERT INTO products (name, description) VALUES (?, ?)");
$productName = "Awesome Widget";
$productDescription = "A truly amazing widget for all your needs.";
$stmt->bind_param("ss", $productName, $productDescription); // "ss" indicates two strings
$stmt->execute();

echo "Product added successfully!";
?>
```

此代码准备了`INSERT`语句，绑定产品名称和描述的字符串值，然后执行查询。Manticore执行的SQL将是：

```sql
INSERT INTO products (name, description) VALUES ('Awesome Widget', 'A truly amazing widget for all your needs.');
```

现在，让我们处理一个使用浮点向量的示例。**什么是`?VEC?`？** 它是一个占位符（仅在预处理语句中使用），用于表示*向量*——一个数字列表，例如嵌入或类似数据。在Manticore SQL中，向量字面量始终用括号编写：`(0.1, 0.2, 0.3)`。因此，当你使用预处理语句并有一个向量参数时，你在SQL字符串中编写这些括号，并在数字位置使用`?VEC?`。你只绑定逗号分隔的数字（例如，`"0.1,0.2,0.3"`）；你不绑定`(`和`)`——它们保留在查询中。没有预处理语句，你必须自己在查询字符串中构建完整的字面量`(0.1, 0.2, 0.3)`。

在 PHP `mysqli` 中，通常绑定 `?VEC?` 值的方式是作为字符串，因此在此示例中 `iss` 是正常选择。如果您想流式传输较大的向量负载，也可以将参数绑定为 `b`，并通过 `send_long_data()` 发送内容。

```php
<?php
// Assuming you have a valid MySQLi connection established ($mysqli)

$stmt = $mysqli->prepare("INSERT INTO items (item_id, coords, features) VALUES (?, (?VEC?),(?VEC?))");
$itemId = 123;
$coordVector = "20.245,54.354,30.000"; // that is vector of floats
$featureSet = "1,4,20,456,112,3"; // that is set of integer values (MVA)
$stmt->bind_param("iss", $itemId, $coordVector, $featureSet); // "i" for integer (itemId), "s" for string
$stmt->execute();

echo "Item with feature vector added successfully!";

$itemId = 124;
$coordVector = "18.500,42.000,31.125"; // Another float vector
$featureSet = "0,6,34,665,22,3445,221,564,2232,5644,43"; // Example with more feature values

// For larger payloads you can bind the second ?VEC? as a blob and stream it.
$featurePlaceholder = "";
$stmt->bind_param("isb", $itemId, $coordVector, $featurePlaceholder); // "b" is for blob data
// bind_param() must be called before send_long_data().
$stmt->send_long_data(2, $featureSet); // zero-based index: 2 means the third bound parameter
$stmt->execute();

echo "Item with feature vector added successfully!";
?>
```

请注意，在 `prepare()` 调用中，括号是 *SQL 字符串的一部分*。我们仅使用 `?VEC?` 占位符绑定括号内的 *值*。Manticore 执行的 SQL 将是：

```sql
INSERT INTO items (item_id, coords, features) VALUES (123, (20.245,54.354,30.000), (1,4,20,456,112,3));
INSERT INTO items (item_id, coords, features) VALUES (124, (18.500,42.000,31.125), (0,6,34,665,22,3445,221,564,2232,5644,43));
```

在预处理语句中使用 `?VEC?` 与使用 `?` 占位符一样，可以带来相同的优势：向量值作为数据发送，而不是作为 SQL 文本的一部分，因此它们不会被解释为 SQL，也不会导致注入。您还可以避免在应用程序中手动构建或转义向量字面量——Manticore 接收绑定的数字并正确格式化向量，这使查询更安全，数据更一致。


## 重要注意事项与限制

尽管功能强大，但 Manticore 的预处理语句有一些需要注意的限制。

**多查询：** 每个预处理语句只允许一个 SQL 语句。尝试使用多查询（例如 `SELECT ...; SHOW META`）将失败。如果需要执行多个语句，请为每个语句分别准备，并在同一个会话中按顺序执行它们。

**数值类型：** 某些数据库驱动（如 Node.js 的 `mysql2`）可能会默认将数值参数作为 `DOUBLE` 发送。如果您需要严格的整数行为（例如拒绝负数 ID），这可能导致意外行为。在这种情况下，考虑将整数作为字符串发送，或使用特定于驱动的整数类型（例如 `BigInt`）以确保正确的数据处理。

**Rust `sqlx` 用户：** 如果您在 Rust 中使用 `sqlx` crate，请注意在读取结果集行时，**必须**使用列 *索引* 而不是列名。虽然结果集中存在列名，但 `sqlx` 不会使用它们进行映射。例如，使用 `row.try_get(0)?` 而不是 `row.try_get("id")?`。

## 结论

预处理语句在与 Manticore Search 配合使用时，提供了安全、可读性和潜在性能提升的关键组合。通过将 SQL 逻辑与数据分离，您可以大幅降低 SQL 注入攻击的风险，提高代码的可维护性，并可能加快查询执行速度。我们强烈建议您在 Manticore Search 应用程序中采用预处理语句。

如需更深入的信息，请务必查阅以下资源：

* Manticore Search 预处理语句文档：[https://manual.manticoresearch.com/Connecting_to_the_server/MySQL_protocol#Prepared-statements](https://manual.manticoresearch.com/Connecting_to_the_server/MySQL_protocol#Prepared-statements)
* Wikipedia - 预处理语句：[https://en.wikipedia.org/wiki/Prepared_statement](https://en.wikipedia.org/wiki/Prepared_statement)

本指南为在 Manticore Search 项目中有效使用预处理语句提供了坚实的基础，从而实现更安全、高效和可维护的应用程序。
