MySQL 连接器 C/C API - 带特殊字符的查询
MySQL Connector C/C API - Query with special characters
我是一个 C 程序,我有一个接受域名参数的函数:
void db_domains_query(char *name);
使用 mysql_query() 我测试域名是否存在于数据库中。如果不是这样,我插入新域名:
...
char *query[400];
sprintf(query, "SELECT Id, DomainName FROM domains WHERE domainName LIKE '%s'", name);
if (mysql_query(con, query))
finish_with_error(con);
MYSQL_RES *result = mysql_store_result(con);
if (result == NULL)
finish_with_error(con);
MYSQL_ROW row;
if ((row = mysql_fetch_row(result)))
printf("Element exists : %s %s\n", row[0], row[1]);
else
printf("Element %s doesn't found\n", name);
// Then insert the new domain name ...
如果 name 仅包含 "normal characters",则这部分代码可以完美运行。但是,对于包含 "special characters" 的域名,查询似乎不正确,即使这些域名在数据库中,例如:
- 姓名 = undaben.de :
Element exists : 100 undaben.de
- 姓名=®here.com :
Element ®here.com is not found.
姓名 = §travel.us : Element §travel.us is not found.
table 的摘录:
+-----+--------------+
| id | domainname |
+-----+--------------+
| 100 | undaben.de |
| 162 | §travel.us |
| 197 | ®here.com |
+-----+--------------+
字段 域名 的排序规则是 utf8_unicode_ci。
那么我如何才能将所有域名传递给 mysql_query,包括 "special" 的域名?
我建议您避免使用 C API,除非您有充分的理由使用它。 C++ API 更有用。
您正在将参数嵌入到查询字符串中。这有很多问题,包括安全风险。如果您坚持采用这种方法,为了防止参数问题干扰您的查询,您需要确保以下几点:
- 确保您的数据编码与 MySQL 客户端连接的编码匹配(这可能与您的数据库编码不同)。如果您的连接设置为 UTF-8,那么您需要确保特殊字符(例如 ©)在用作
sprintf
函数的输入时也以 UTF-8 编码。
- 您还需要防止其他 SQL 转义字符(例如
'
)。为此,您可以使用 mysql_real_escape_string
函数,如 Efficiently escaping quotes in C before passing to mysql_query. 中所述
但是,您很可能会使用 准备好的语句 来规避这些问题。您仍然需要确保您的输入数据编码与您的客户端连接的编码匹配,但其他一切都将更容易处理。
我粘贴了一个使用 C API 和准备好的语句的参数化查询示例(来自 http://lgallardo.com/2011/06/23/sentencias-preparadas-de-mysql-en-c-ejemplo-completo/ 的示例)。请注意,该示例适用于整数,而不是字符串,您需要适应您的用例。
sql = "select count(*) from addresses where id = ?";
// Open Database
openDB(&conn);
// Allocate statement handler
stmt = mysql_stmt_init(conn);
if (stmt == NULL) {
print_error(conn, "Could not initialize statement handler");
return;
}
// Prepare the statement
if (mysql_stmt_prepare(stmt, sql, strlen(sql)) != 0) {
print_stmt_error(stmt, "Could not prepare statement");
return;
}
// Initialize the result column structures
memset (param, 0, sizeof (param)); /* zero the structures */
memset (result, 0, sizeof (result)); /* zero the structures */
// Init param structure
// Select
param[0].buffer_type = MYSQL_TYPE_LONG;
param[0].buffer = (void *) &myId;
param[0].is_unsigned = 0;
param[0].is_null = 0;
param[0].length = 0;
// Result
result[0].buffer_type = MYSQL_TYPE_LONG;
result[0].buffer = (void *) &myNumAddresses;
result[0].is_unsigned = 0;
result[0].is_null = &is_null[0];
result[0].length = 0;
// Bind param structure to statement
if (mysql_stmt_bind_param(stmt, param) != 0) {
print_stmt_error(stmt, "Could not bind parameters");
return;
}
// Bind result
if (mysql_stmt_bind_result(stmt, result) != 0) {
print_stmt_error(stmt, "Could not bind results");
return;
}
// Set bind parameters
myId = id;
// Execute!!
if (mysql_stmt_execute(stmt) != 0) {
print_stmt_error(stmt, "Could not execute statement");
return;
}
if (mysql_stmt_store_result(stmt) != 0) {
print_stmt_error(stmt, "Could not buffer result set");
return;
}
// Init data
(*numAddresses) = 0;
// Fetch
if(mysql_stmt_fetch (stmt) == 0){
(*numAddresses) = myNumAddresses;
}
// Deallocate result set
mysql_stmt_free_result(stmt); /* deallocate result set */
// Close the statement
mysql_stmt_close(stmt);
// Close Database
closeDB(conn);
同样,如果您可以使用其他一些客户端库(如 C++ 客户端),您的代码将会更短且更易读。
我的错,正如@jjmontes 提到的那样,发送的字符串似乎是在 'latin1'
中编码的。
在查询之前使用函数mysql_set_character_set(conn, "utf8")
解决了这个问题。
现在,我将尝试使用 准备好的语句 而不是查询字符串。
再次感谢!
我是一个 C 程序,我有一个接受域名参数的函数:
void db_domains_query(char *name);
使用 mysql_query() 我测试域名是否存在于数据库中。如果不是这样,我插入新域名:
...
char *query[400];
sprintf(query, "SELECT Id, DomainName FROM domains WHERE domainName LIKE '%s'", name);
if (mysql_query(con, query))
finish_with_error(con);
MYSQL_RES *result = mysql_store_result(con);
if (result == NULL)
finish_with_error(con);
MYSQL_ROW row;
if ((row = mysql_fetch_row(result)))
printf("Element exists : %s %s\n", row[0], row[1]);
else
printf("Element %s doesn't found\n", name);
// Then insert the new domain name ...
如果 name 仅包含 "normal characters",则这部分代码可以完美运行。但是,对于包含 "special characters" 的域名,查询似乎不正确,即使这些域名在数据库中,例如:
- 姓名 = undaben.de :
Element exists : 100 undaben.de
- 姓名=®here.com :
Element ®here.com is not found.
姓名 = §travel.us :
Element §travel.us is not found.
table 的摘录:
+-----+--------------+ | id | domainname | +-----+--------------+ | 100 | undaben.de | | 162 | §travel.us | | 197 | ®here.com | +-----+--------------+
字段 域名 的排序规则是 utf8_unicode_ci。 那么我如何才能将所有域名传递给 mysql_query,包括 "special" 的域名?
我建议您避免使用 C API,除非您有充分的理由使用它。 C++ API 更有用。
您正在将参数嵌入到查询字符串中。这有很多问题,包括安全风险。如果您坚持采用这种方法,为了防止参数问题干扰您的查询,您需要确保以下几点:
- 确保您的数据编码与 MySQL 客户端连接的编码匹配(这可能与您的数据库编码不同)。如果您的连接设置为 UTF-8,那么您需要确保特殊字符(例如 ©)在用作
sprintf
函数的输入时也以 UTF-8 编码。 - 您还需要防止其他 SQL 转义字符(例如
'
)。为此,您可以使用mysql_real_escape_string
函数,如 Efficiently escaping quotes in C before passing to mysql_query. 中所述
但是,您很可能会使用 准备好的语句 来规避这些问题。您仍然需要确保您的输入数据编码与您的客户端连接的编码匹配,但其他一切都将更容易处理。
我粘贴了一个使用 C API 和准备好的语句的参数化查询示例(来自 http://lgallardo.com/2011/06/23/sentencias-preparadas-de-mysql-en-c-ejemplo-completo/ 的示例)。请注意,该示例适用于整数,而不是字符串,您需要适应您的用例。
sql = "select count(*) from addresses where id = ?";
// Open Database
openDB(&conn);
// Allocate statement handler
stmt = mysql_stmt_init(conn);
if (stmt == NULL) {
print_error(conn, "Could not initialize statement handler");
return;
}
// Prepare the statement
if (mysql_stmt_prepare(stmt, sql, strlen(sql)) != 0) {
print_stmt_error(stmt, "Could not prepare statement");
return;
}
// Initialize the result column structures
memset (param, 0, sizeof (param)); /* zero the structures */
memset (result, 0, sizeof (result)); /* zero the structures */
// Init param structure
// Select
param[0].buffer_type = MYSQL_TYPE_LONG;
param[0].buffer = (void *) &myId;
param[0].is_unsigned = 0;
param[0].is_null = 0;
param[0].length = 0;
// Result
result[0].buffer_type = MYSQL_TYPE_LONG;
result[0].buffer = (void *) &myNumAddresses;
result[0].is_unsigned = 0;
result[0].is_null = &is_null[0];
result[0].length = 0;
// Bind param structure to statement
if (mysql_stmt_bind_param(stmt, param) != 0) {
print_stmt_error(stmt, "Could not bind parameters");
return;
}
// Bind result
if (mysql_stmt_bind_result(stmt, result) != 0) {
print_stmt_error(stmt, "Could not bind results");
return;
}
// Set bind parameters
myId = id;
// Execute!!
if (mysql_stmt_execute(stmt) != 0) {
print_stmt_error(stmt, "Could not execute statement");
return;
}
if (mysql_stmt_store_result(stmt) != 0) {
print_stmt_error(stmt, "Could not buffer result set");
return;
}
// Init data
(*numAddresses) = 0;
// Fetch
if(mysql_stmt_fetch (stmt) == 0){
(*numAddresses) = myNumAddresses;
}
// Deallocate result set
mysql_stmt_free_result(stmt); /* deallocate result set */
// Close the statement
mysql_stmt_close(stmt);
// Close Database
closeDB(conn);
同样,如果您可以使用其他一些客户端库(如 C++ 客户端),您的代码将会更短且更易读。
我的错,正如@jjmontes 提到的那样,发送的字符串似乎是在 'latin1'
中编码的。
在查询之前使用函数mysql_set_character_set(conn, "utf8")
解决了这个问题。
现在,我将尝试使用 准备好的语句 而不是查询字符串。
再次感谢!