用C语言编写MySQL程序
- 编译和链接客户端程序
以下假设头文件和客户端开发库的存储位置分别为
/usr/local/include/mysql
/usr/local/lib/mysql.
gcc -c -I/usr/local/include/mysql myclient.c
gcc -o myclient myclient.o -L/usr/local/lib/mysql -lmysqlclient
gcc -o myclient myclient.o -L/usr/local/lib/mysql -lmysqlclient -lz
使用mysql_config工具.
mysql_config --include// 显示需用-I指出的路径信息
mysql_config --libs// 显示需用-L和-l指出的开发库和链接库信息
如要开发一个名为myclient的客户端程序,它由两个源文件[main.c和lib.c]和一个头文件[myclient.h]构成.
CC=gcc
INCLUDES=-I/usr/local/include/mysql
LIBS=-L/usr/local/lib/mysql -lmysqlclient
all : myclient
main.o:main.c myclient.h
$(CC) -c $(INCLUDES) main.c
lib.o:lib.c myclient.h
$(CC) -c $(INCLUDES) lib.c
myclient:main.o lib.o
$(CC) -o myclient main.o lib.o $(LIBS)
clean:
rm -f myclient main.o lib.o
INCLUDES=${shell mysql_config --include}
LIBS=${shell mysql_config --libs}
出错处理和命令行选项处理
1.mysql_error()
会返回一个包含出错消息的字符串
2.mysql_errno()
会返回一个MySQL特有的数字出错代码
3.mysql_sqlstate()
会返回一个SQLSTATE代码,
- 处理SQL语句
第一组例程,
会把每条语句当做一个字符串发给服务器
且会按字符串格式返回所有数据列的结果
第二组例程,
使用的是二进制协议,
能以原始格式发送和返回非字符串数据,
不需来回转换字符串格式.
mysql_read_query()
把语句当做一个计数字符串[字符串+长度值]
mysql_query()
传给mysql_query()的语句,需是一个以null结尾的字符串.
if(mysql_query(conn, stmt_str) != 0)
{
// err
}
else
{
// suc
}
所有语句可粗略划分为两大类
一类用于修改行,
一类会返回一个结果集
结果集,会通过MYSQL_RES数据类型来表示.
- 处理那些修改行的语句
执行后调用mysql_affected_rows()
可查明实际插入,删除或修改了多少行.
if(mysql_query(conn,
"INSERT INTO my_tbl SET name='My Name'") != 0)
{
print_error(conn, "INSERT statement failed");
}
else
{
printf("INSERT statement succeeded;\
number of rows affected:%lu\n",
(unsigned long)mysql_affected_rows(conn));
}
- 处理那些返回结果集的语句
MYSQL里能返回行的语句并非只有SELECT
SHOW,DESCRIBE,EXPLAIN和CHECK TABLE这样的语句
也都返回结果集
对这些语句,调用后,需执行附加的行处理操作.
1.调
mysql_store_result()/mysql_use_result()可生成结果集
这两个函数调用成功时,
会返回一个MYSQL_RES指针
失败时,
会返回NULL
mysql_store_result()会立刻从服务器检索出行,
把它们缓存在客户端的内存里
调mysql_fetch_row()可依次取回结果集里的每个行
此函数调用成功时,
会返回一个MYSQL_ROW值.
如已没有行,
会返回NULL
返回的MYSQL_ROW值其实是一个字符串数组指针,
其中的字符串分别代表着这个行里各个列的值.
处理完结果集后,
需调mysql_free_result()释放它所使用的内存资源.
MYSQL_RES *res_set;
if(mysql_query(conn, "SHOW TABLES FROM sampdb") != 0)
{
print_error(conn, "mysql_query() failed");
}
else
{
res_set = mysql_store_result(conn);
if(res_set == NULL)
{
print_error(conn,
"mysql_store_result() failed");
}
else
{
process_result_set(conn, res_set);
mysql_free_result(res_set);
}
}
MYSQL_ROW row;
while((row=mysql_fetch_row(res_set)) != NULL)
{
// 处理行的内容
}
mysql_fetch_row将返回一个MYSQL_ROW值,
则可使用row[i]语法来访问行中的各个值
i的范围是从0到行里的列数减少1
a.MYSQL_ROW是指针类型
应定义为MYSQL_ROW row;
b.在MYSQL_ROW数组里,
对所有类型的值,甚至包括数字类型的,
都是以字符串方式返回的.
如想把某个值当作数字,
则需自己对字符串进行转换
c.MYSQL_ROW数组里的字符串都是以null结尾的.
如某列可包含二进制数据,则它可能包含null字节.
因此不能把这个值当作是以null结尾的字符串来对待.
可通过考虑数据列长度来表达数据长度
d.在MYSQL_ROW数组里,
SQL的NULL将被表示成C语言的NULL指针.
void process_result_set(
MYSQL* conn,
MYSQL_RES* res_set)
{
MYSQL_ROW row;
unsigned int i;
while((row = mysql_fetch_row(res_set)) != NULL)
{
for(i = 0; i < mysql_num_fields(res_set); i++)
{
if(i > 0)
{
fputc('\t', stdout);
}
printf("%s", row[i] != NULL ? row[i] : "NULL");
}
fputc('\n', stdout);
}
if(mysql_errno(conn) != 0)
{
print_error(conn, "mysql_fetch_row() failed");
}
else
{
printf("Number of rows returned:%lu\n",
(unsigned long)mysql_num_rows(res_set));
}
}
- 通用的语句处理器
1.执行语句,如失败,则结束.
2.如执行成功,
调mysql_store_result()
并创建一个结果集.
3.
调mysql_fetch_row()来处理各个行
一直到它返回NULL为止
然后释放此结果集
4.如mysql_store_result()调用失败,
具体原因可能是:
语句根本不会返回结果集,
也可能是它需要返回结果集,但试图创建结果集时发生了错误.
可利用mysql_field_count()来加以区别,
即把连接处理器传递给mysql_field_count()
然后检查它的返回值
如mysql_field_count()返回0
则表明此语句没返回任何列
故,也就没有结果集.
如mysql_field_count()返回是非0值,
则表明有错误.
因为该语句应返回一个结果集,但却未返回.
void process_statement(
MYSQL* conn,
char* stmt_str)
{
MYSQL_RES *res_set;
if(mysql_query(conn, stmt_str) != 0)
{
print_error(conn, "Could not execute statement");
return;
}
res_set = mysql_store_result(conn);
if(res_set)
{
process_result_set(conn, res_set);
mysql_free_result(res_set);
}
else
{
// 若存在返回结果集,结果集中每一行的列数
// 返回结果集下,列数至少为1
// 不返回结果集下,获取的列数将为0
if(mysql_field_count(conn) == 0)
{
printf("Number of rows affected:%lu\n",
(unsigned long)mysql_affected_rows(conn));
}
else
{
print_error(conn, "Could not retrieve result set");
}
}
}
- 另一种语句处理处理方法
上述的process_statement()有以下3个特点
1.使用了mysql_query()来执行语句
2.使用了mysql_store_result()来检索结果集
3.没获得结果集时,
利用mysql_field_count()来判断:
是出现了错误,还是原本就不会有结果集返回.
如对语句处理的这3个方面加以调整,
就能得到另一种处理方案.
1.执行语句时,
用一个计数字符串和mysql_real_query()来代替那个以null结尾的字符串和mysql_query()
2.通过调mysql_use_result()
而非调mysql_store_result()来创建结果集
3.调mysql_error()或mysql_errno()
而非mysql_field_count()来确定是结果集检索失败,
还是根本就没有供检索的结果集
void process_real_statement(
MYSQL* conn,
char* stmt_str,
unsigned int len)
{
MYSQL_RES* res_set;
if(mysql_real_query(conn, stmt_str, len) != 0)
{
print_error(conn, "Could not execute statement");
return;
}
res_set = mysql_use_result(conn);
if(res_set)
{
process_result_set(conn, res_set);
mysql_free_result(res_set);
}
else
{
if(mysql_errno(conn) == 0)
{
printf("Number of rows affected:%lu\n",
(unsigned long)mysql_affected_rows(conn));
}
else
{
print_error(conn, "Could not retrieve result set");
}
}
}
- mysql_store_result()与mysql_use_result()的对比
两者相似点:
1.都会接受一个连接处理器参数,并返回一个结果集.
差异:
当你调用mysql_store_result()时
它会立刻检索出所有的行
而mysql_use_result()则只会对检索进行初始化,
并不会实际取回任何行.
当从服务器检索结果集时,
mysql_store_result()会取回行
并为它们分配内存
然后把它们在客户端缓存起来.
此后的mysql_fetch_row()调用决不会返回出错消息
[它们只是从已经含结果集的数据结构里提取行]
当mysql_fetch_row()返回NULL值时,肯定表示已经到达结果集的末尾.
mysql_use_result()本身不会检索任何行.
它只是逐行完成对检索的初始化工作
你需自己针对每一个行调mysql_fetch_row()来完成检索操作.
当mysql_fetch_row()返回NULL时,
通常表示"已经到达结果集的末尾"
也有可能表示"与服务器的通信出现了错误"
通过调mysql_error()或mysql_errno()可区分这两种情况.
mysql_store_result()需把完整的结果集保存在客户端
它的内存和处理需求比mysql_use_result()大.
内存分配和数据结构创建所需的开销会更大,
且如客户端检索的是大型结果集,
有内存耗尽的风险.
如要检索包含大量行的结果集,应使用mysql_use_result()
mysql_use_result()每次只会取回一个行进行处理
所以它对内存的要求低
同时,不必为创建结果集而建立各种复杂的数据结构
故,其内存分配速度也更快
另一方面,
mysql_use_result()加重了服务器的负担,
服务器需保存结果集里的行
一直到客户端程序认为适合检索所有这些行那一刻为止
故mysql_use_result()不适合用在以下两类客户端程序里:
1.根据用户请求,
逐个遍历各有关行的交互式客户端程序
2.两次行检索操作间,
需执行大量处理操作的客户端程序
虽然mysql_store_result()会消耗较多的内存
但也有好处
可立即访问整个结果集
结果集的所有行都可用,可随机访问.
mysql_data_seek()
mysql_row_seek()
mysql_row_tell()
可让你按任意顺序访问各个行可让你按任意顺序访问各个行.
如用mysql_use_result(),只能按mysql_fetch_row()取回行时的顺序访问各个行.
使用mysql_store_result()可访问某些使用my_use_result()时访问不到的列信息.
如调mysql_num_rows()可获得结果集里行的数量.
用mysql_use_result()时,
客户端需通过mysql_fetch_row()调用取回结果集里的每个行.
要避免此问题,需在执行第二条语句前,调mysql_free_result()取回和丢弃尚未被取回的行.
mysql_store_result()不会导致数据不同步问题,
它返回时,一次取完相关数据.
- 使用结果集元数据
结果集不仅包含从数据行里检索出来的列值,
还包含与这些数据有关的信息.
这种信息即为结果集"元数据"
1.结果集里的行数和列数
调mysql_num_rows()和mysql_num_fields()即可获得
2.当前行里各列值的长度
调mysql_fetch_lengths()即可获得
3.关于各列的信息
如列的名字,类型,每个列的值最大宽度,包含这个列的表
上述信息存于MYSQL_FIELD结构里.
调mysql_fetch_field()即可获得.
元数据是否可用,取决于所用的结果集处理方法
如想用行计数或各个列的最大长度值,
则在创建结果集时,
需使用mysql_store_result()
不能用mysql_use_result()
结果集元数据能帮我们决定如何处理结果集数据
1.列的名字及宽度有助于生成漂亮格式的输出
2.列计数则表明
依次处理各个行中各个列的循环,
要迭代多少次才结束
3.行和列的计数
有助于为那些依赖于结果集维度的数据结构分配内存
4.列的数据类型
MYSQL_FIELD *field;
unsigned long col_len;
unsigned int i;
// res_set基于mysql_store_result得到
mysql_field_seek(res_set, 0);
for(i=0; i < mysql_num_fields(res_set); i++)
{
field = mysql_fetch_field(res_set);
// field->name为列标题
// field->max_length列中最长数据值长度
col_len = strlen(field->name);
if(col_len < field->max_length)
{
col_len = field->max_length;
}
if(col_len < 4 && !IS_NOT_NULL(field->flags))
{
col_len = 4;
}
field->max_length = col_len;
}
void print_dashes(MYSQL_RES* res_set)
{
MYSQL_FIELD *field;
unsigned int i,j;
// 定位
mysql_field_seek(res_set, 0);
fputc('+', stdout);
// 列的个数
for(i=0; i<mysql_num_fields(res_set); i++)
{
// 依次获取每个列
field = mysql_fetch_field(res_set);
// 持续输出-
for(j=0; j < field->max_length+2; j++)
{
fputc('-', stdout);
}
// 输出一个+
fputc('+', stdout);
}
// 输出\n
fputc('\n', stdout);
}
void process_result_set(
MYSQL* conn,
MYSQL_RES* res_set)
{
MYSQL_ROW row;
MYSQL_FIELD *field;
unsigned long col_len;
unsigned int i;
mysql_field_seek(res_set, 0);
for(i=0; i<mysql_num_fields(res_set); i++)
{
field=mysql_fetch_field(res_set);
col_len=strlen(field->name);
if(col_len < field->max_length)
{
col_len = field->max_length;
}
if(col_len < 4 && !IS_NOT_NULL(field->flags))
{
col_len = 4;
}
field->max_length = col_len;
}
print_dashes(res_set);
fputc('|', stdout);
mysql_field_seek(res_set, 0);
for(i=0; i < mysql_num_fields(res_set); i++)
{
field = mysql_fetch_field(res_set);
printf(" %-*s |", (int)field->max_length, field->name);
}
fputc('\n', stdout);
print_dashes(res_set);
while((row=mysql_fetch_row(res_set)) != NULL)
{
mysql_field_seek(res_set, 0);
fputc('|', stdout);
for(i=0; i<mysql_num_fields(res_set);i++)
{
field=mysql_fetch_field(res_set);
if(row[i]==NULL)
{
printf(" %-*s |", (int)field->max_length, "NULL");
}
else if(IS_NUM(field->type))
{
printf(" %*s |", (int)field->max_length, row[i]);
}
else
{
printf(" %-*s |", (int)field->max_length, row[i]);
}
}
fputc('\n', stdout);
}
print_dashes(res_set);
printf("Number of rows returned:%lu\n",
(unsigned long)mysql_num_rows(res_set));
}
- 对特殊字符和二进制数据进行编码
如在某个用引号引起来的字符串里,包含一个引号字符,
则可采用双写这个引号,或在其前面加一个反斜线的方式来解决
1.处理包含特殊字符的字符串
如在语句里单纯以字面方式插入包含有引号,null字节或反斜线的数据值
则在执行此语句时会引起各种问题.
如,
你想基于以null结尾的字符串的内容
[指向它的是一个名为name_val的变量],构造SELECT语句
char stmt_buf[1024];
sprintf(
stmt_buf,
"SELECT * FROM mytbl\
WHERE name='%s'", name_val);
如name_val含引号,最终构造出的语句将非法
这类问题可用mysql_real_escape_string()来解决
此函数会对特殊字符进行编码
让它们在用引号引起来的字符串里变得可用
mysql_real_escape_string()会把空字节,单引号,双引号,反斜线,换行符,回车符,Ctrl+Z当作特殊字符.
应在什么时候用mysql_real_escape_string()?
最好一直都用.
mysql_real_escape_string()会对有问题的字符进行编码
它会把它们转换成两个以反斜线开头的字符序列
如,
null字节会被编码成"\0"
其中的"0"是可输出的ASCII字符0,不是NULL
反斜线,单引号,双号将分别被编码成"\\", "\'", "\""
to_len=mysql_real_escape_string(
conn,
to_str,
from_str,
from_len);
mysql_real_escape_string()会对from_str进行编码,
把结果写入to_str
还会在结尾添加null
from_str指向的是一个char缓冲区,其中含要被编码的字符串
这个字符串可包含任何内容,包括二进制数据
to_str指向的是一个已经存在的char缓冲区,
其中写入的是编码后的字符串
不要传递未经初始化的指针或NULL指针,
to_str指向的那个缓冲区的长度,必须至少为(from_len*2)+1个字节
from_str允许包含null字节,且不能把它当做是以null结尾的字符串.
to_len是mysql_real_escape_string()的返回值,
是编码结果字符串的实际长度,未把结尾的null计算在内.
mysql_real_escape_string()返回时,
from_str里的null字节都会被编码成可打印的"\0"序列
故to_str里的编码结果可被当作是以null字节结尾的字符串
char stmt_buf[1024], *p;
p=strcpy(stmt_buf,
"SELECT * FROM mytbl WHERE name='");
p+=strlen(p);
p+=mysql_real_escape_string(
conn,
p,
name_val,
strlen(name_val));
*p++='\'';
*p='\0';
// 可等价为
char stmt_buf[1024], buf[1024];
(void)mysql_real_escape_string(
conn,
buf,
name_val,
strlen(name_val));
sprintf(
stmt_buf,
"SELECT * FROM mytbl\
WHERE name='%s'", buf);
- 处理二进制数据
在语句里使用了任意的二进制数据
因为二进制值可包含任何字符,所以把它们直接嵌到语句里并不安全
设需从文件里读出图像
并把它们存入一个名为picture的表
同时还会存入一个唯一标识符
对长度小于16MB的二进制值来说,
MEDIUMBLOB是个好的选择
CREATE TABLE picture
{
pict_id INT NOT NULL PRIMARY KEY,
pict_data MEDIUMBLOB
};
// 把文件里的图像加载到picture表
int load_image(MYSQL* conn, int id, FILE* f)
{
char stmt_buf[1024*1024],buf[1024*10],*p;
unsigned long from_len;
int status;
sprintf(
stmt_buf,
"INSERT INTO picture(pict_id,pict_data)\
VALUES (%d,'",id);
p = stmt_buf + strlen(stmt_buf);
while((from_len=fread(buf,1,sizeof(buf),f))>0)
{
if(p+(2*from_len)+3>stmt_buf+sizeof(stmt_buf))
{
print_error(NULL, "image is too big");
return 1;
}
p += mysql_real_escape_string(conn, p, buf, from_len);
}
*p++ = '\'';
*p++ = ')';
status = mysql_real_query(conn, stmt_buf, (unsigned long)(p-stmt_buf));
return status;
}
从数据库取回图像值[或任意二进制值]时,
没把它存入数据库时那么多问题,
这个数据值以原始格式存放在MYSQL_ROW变量里,
其长度可调mysql_fetch_lengths()获得.
应将此值作为一个计数字符串处理.
- 一次执行多条语句
MySQL客户端开发库支持一次执行多条语句
据此,可把一个由分号隔开的多条语句构成的字符串,
发给服务器,再依次检索各个结果集.
一次执行多条语句,默认是禁用的.
如你想用它时,
需明确告诉服务器
1.在连接服务器时,
把CLIENT_NULTI_STATEMENTS选项添加到mysql_real_connect()的标志参数里.
opt_flags |= CLIENT_MULTI_STATEMENTS;
if(mysql_real_connect(
conn,
opt_host_name,
opt_user_name,
opt_password,
opt_db_name,
opt_port_num,
opt_socket_name,
opt_flags) == NULL)
{
print_error(conn, "mysql_real_connect() failed");
mysql_close(conn);
exit(1);
}
为现有连接启动此功能
if(mysql_set_server_option(
conn,
MYSQL_OPTION_MULTI_STATEMENTS_ON) != 0)
{
print_error(conn, "Could not enable multiple-statement execution");
}
在处理多个结果集时,
可使用下面这两个函数,对结果检索的当前状态进行基本的检查
1.mysql_more_results()
如还有更多的结果,返回非0值.
否则,返回0
2.mysql_next_result()
如还有更多的结果,初始化下一个结果集的检索工作.
如还有更多的结果,则状态值为0
如没有,则返回-1
如发生了错误,则返回一个大于0的值
使用这些函数时,
可把结果检索代码放在某个循环里.
在用常规代码检索出一个结果后,
记得检查下是否还有结果需检索.
如有,再循环一次.
如没有,退出循环.
根据循环语句具体结构的不同,
可能不需调mysql_more_results()
你可根据mysql_next_result()的返回值来判断是否还有更多的结果
void process_multi_statement(
MYSQL*conn,
char* stmt_str)
{
MYSQL_RES* res_set;
int status;
int keep_going = 1;
if(mysql_query(conn, stmt_str) != 0)
{
print_error(
conn,
"Could not execute statement(s)");
return;
}
do
{
res_set = mysql_store_result(conn);
if(res_set)
{
process_result_set(conn, res_set);
mysql_free_result(res_set);
}
else
{
if(mysql_field_count(conn) == 0)
{
printf(
"Number of rows affected:%lu\n",
(unsigned long)mysql_affected_rows(conn));
}
else
{
print_error(conn, "Could not retrieve result set");
keep_going = 0;
}
}
status = mysql_next_result(conn);
if(status != 0)
{
keep_going = 0;
if(status > 0)
{
print_error(
conn,
"Could not execute statement");
}
}
}while(keep_going);
}
- 使用服务器端预处理语句
本章前面几节中,
用于处理SQL的代码是基于MySQL客户端开发库提供的函数编写的.
都是以字符串的形式来发送和检索所有信息.
本节讨论如何使用二进制的客户端/服务器协议
二进制协议支持服务器端预处理语句,
且可按本地格式来传输数据值.
并非所有语句都能被预处理.
预处理语句API适用于这样几种语句
CREATE TABLE
DELETE
DO
INSERT
REPLACE
SELECT
SET
UPDATE
和绝大多数SHOW变体
预处理机制所支持的语句种类还在不断增加.
为使用二进制协议,
需创建一个语句处理器.
有此处理器,
便可把语句发到服务器进行预处理.
服务器会分析语句,记住它,
并把有关它的信息[客户端开发库存储在语句处理器里的]发送回去.
用于预处理的语句可被参数化,即包含问号(?)
以此来表示,当你将来执行该语句时所提供的数据值会出现在该处.
INSERT INTO score(event_id,student_id,score) VALUES(?,?,?)
上述语句含3个"?"字符,
它们的作用是充当参数标记或占位符
将来可提供数据值绑定到这些占位符上.
执行它时,这些数据便会让语句变得完整
预处理语句的这种"参数传递"机制使它们可重复使用
只要把各有关"参数"替换成某些不同的值
便可反复多次执行同一条语句
1.服务器只需对语句分析一次
2.能降低网络开销
因为每次执行时只需发数据值
3.数据值在发送时无需转换成字符串形式,
可降低执行开销
如上面INSERT语句的3个列全是INT列,
每当用mysql_query()或mysql_real_query()执行类似的INSERT语句时,
只需把几个数据值转换为字符串,嵌入语句文本即可.
有了预处理语句接口后,可用二进制格式来发送数据值
4.不必对检索结果转换
在预处理语句的结果集里,
非字符串值都是以二进制格式返回的.
无需转换成字符串形式.
二进制协议缺点:
1.难用
传输和接收数据值时需更多准备工作
2.二进制协议不支持所有的语句
3.对交互式程序,
最好还是非二进制协议.
预处理语句,适合提升反复执行的语句分摊效率.
用预处理语句基本流程
1.调mysql_stmt_init()分配一个语句处理器
此函数会返回一个指向处理器的指针,供后续使用
2.调mysql_stmt_prepare(),把语句发送到服务器,
让其接受预处理,并与语句处理器相关联.
服务器会分析该语句的特征,如语句类型,包含的参数标记,在执行时是否会生成一个结果集等.
3.如该语句含占位符,
则在执行它之前,
需为每个占位符提供相应的数据.
即为每个参数创建一个MYSQL_BIND结构
每个结构表明一个参数的数据类型,值,是否为NULL.
再调mysql_stmt_bind_param()函数,
把结构与该语句绑定在一起
4.调mysql_stmt_execute(),执行该语句
5.如该语句只修改数据,
不生成结果集
则调mysql_stmt_affected_rows()来确定该语句影响的行数
6.如该语句生成结果集,则可调mysql_stmt_result_metadata()来获取关于该结果集的元数据.
为获得这些行,需再次用MYSQL_BIND结构.
这次用于接收从服务器返回的数据.
需为结果集里的每一个列创建一个MYSQL_BIND结构.
它们包含与每一个行的数据值有关的信息,
这些值是你期望从服务器接收到的.
先调mysql_stmt_bind_result()
把这些结构绑定到语句处理器上,
再反复调mysql_stmt_fetch(),
依次取回每一个行
取回每一个行后,便可访问当前行的各个列值.
调mysql_stmt_fetch()前,
可选择调mysql_stmt_store_result()
如调了,
则可从服务器里一次性取回全部结果集的行
并缓存在客户端的内存里
也可通过调mysql_stmt_num_rows()确定结果集里的行数
如结果集为空,则函数返回0
取回结果集后,记得调mysql_stmt_free_result()
7.如再次执行刚才语句,
可返回3,指定新的参数值
8.如想用这个处理器来预处理另一条语句
则返回2
9.用完语句处理器后,
需调mysql_stmt_close()来释放它.
void process_prepared_statements(MYSQL* conn)
{
MYSQL_STMT* stmt;
char *use_stmt = "USE sampdb";
char *drop_stmt = "DROP TABLE IF EXISTS t";
char *create_stmt = "CREATE TABLE t\
(i INT, f FLOAT, c CHAR(24), dt DATETIME)";
if(mysql_query(conn, use_stmt) != 0
|| mysql_query(conn, drop_stmt) != 0
|| mysql_query(conn, create_stmt) != 0)
{
print_error(conn, "Could not set up test table");
return;
}
stmt = mysql_stmt_init(conn);
if(stmt == NULL)
{
print_error(conn, "Could not initialize statement handler");
return;
}
insert_rows(stmt);
select_rows(stmt);
mysql_stmt_close(stmt);
}
static void print_stmt_error(
MYSQL_STMT* stmt,
char* message)
{
fprintf(stderr, "%s\n", message);
if(stmt != NULL)
{
fprintf(stderr, "Error %u (%s):%s\n",
mysql_stmt_errno(stmt),
mysql_stmt_sqlstate(stmt),
mysql_stmt_error(stmt));
}
}
static void insert_rows(MYSQL_STMT* stmt)
{
char* stmt_str = "INSERT INTO t \
(i,f,c,dt)\
VALUES(?,?,?,?)";
MYSQL_BIND param[4];
int my_init;
float my_float;
char my_str[26];
MYSQL_TIME my_datetime;
unsigned long my_str_length;
time_t clock;
struct tm* cur_time;
int i;
printf("Inserting records...\n");
if(mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str)) != 0)
{
print_stmt_error(stmt, "Could not prepare INSERT statement");
return;
}
memset((void*)param, 0, sizeof(param));
param[0].buffer_type = MYSQL_TYPE_LONG;
param[0].buffer = (void*)&my_int;
param[0].is_unsigned = 0;
param[0].is_null = 0;
param[1].buffer_type = MYSQL_TYPE_FLOAT;
param[1].buffer = (void*)&my_float;
param[1].is_null = 0;
param[2].buffer_type = MYSQL_TYPE_STRING;
param[2].buffer = (void*)my_str;
param[2].buffer_length = sizeof(my_str);
param[2].is_null = 0;
param[3].buffer_type = MYSQL_TYPE_DATETIME;
param[3].buffer = (void*)&my_datetime;
param[3].is_null = 0;
if(mysql_stmt_bind_param(stmt, param) != 0)
{
print_stmt_error(stmt, "Could not bind parameters for INSERT");
return;
}
for(i = 1; i <= 5; i++)
{
printf("Inserting record %d ...\n", i);
(void)time(&clock);
my_int = i;
my_float = (float)i;
(void)strcpy(my_str, ctime(&clock));
my_str[24] = '\0';
my_str_length = strlen(my_str);
param[2].length = &my_str_length;
cur_time = localtime(&clock);
my_datetime.year = cur_time->tm_year + 1900;
my_datetime.month = cur_time->tm_mon + 1;
my_datetime.day = cur_time->tm_mday;
my_datetime.hour = cur_time->tm_hour;
my_datetime.minute = cur_time->tm_min;
my_datetime.second = cur_time->tm_sec;
my_datetime.second_part = 0;
my_datetime.neg = 0;
if(mysql_stmt_execute(stmt) != 0)
{
print_stmt_error(stmt, "Could not execute statement");
return;
}
sleep(1);
}
}
占位符通常代表VALUES()里列出的数据值
或WHERE子句里的数据值
有些地方不能用占位符
1.标识符
2.可把占位符放在操作符任何一侧,但不允许同时用在两侧
static void select_rows(MYSQL_STMT* stmt)
{
char *stmt_str = "SELECT i,f,c,dt FROM t";
MYSQL_BIND param[4];
int my_int;
float my_float;
char my_str[24];
unsigned long my_str_length;
MYSQL_TIME my_datetime;
my_bool is_null[4];
printf("Retrieving records...\n");
if(mysql_stmt_prepare(stmt, stmt_str, strlen(stmt_str)) != 0)
{
print_stmt_error(stmt, "Could not prepare SELECT statement");
return;
}
if(mysql_stmt_field_count(stmt) != 4)
{
print_stmt_error(stmt, "Unexpected column count from SELECT");
return;
}
memset((void*)param, 0, sizeof(param));
param[0].buffer_type = MYSQL_TYPE_LONG;
param[0].buffer = (void*)&my_int;
param[0].is_unsigned = 0;
param[0].is_null = &is_null[0];
param[1].buffer_type = MYSQL_TYPE_FLOAT;
param[1].buffer = (void*)&my_float;
param[1].is_null = &is_null[1];
param[2].buffer_type = MYSQL_TYPE_STRING;
param[2].buffer = (void*)my_str;
param[2].buffer_length = sizeof(my_str);
param[2].length = &my_str_length;
param[2].is_null = &is_null[2];
param[3].buffer_type = MYSQL_TYPE_DATETIME;
param[3].buffer = (void*)&my_datetime;
param[3].is_null = &is_null[3];
if(mysql_stmt_bind_result(stmt, param) != 0)
{
print_stmt_error(stmt, "Could not bind parameters for SELECT");
return;
}
if(mysql_stmt_execute(stmt) != 0)
{
print_stmt_error(stmt, "Could not execute SELECT");
return;
}
if(mysql_stmt_store_result(stmt) != 0)
{
print_stmt_error(stmt, "Could not buffer result set");
return;
}
else
{
printf("Number of rows retrieved:%lu\n",
(unsigned long)mysql_stmt_num_rows(stmt));
}
while(mysql_stmt_fetch(stmt) == 0)
{
printf("%d", my_int);
printf("%.2f", my_float);
printf("%*.*s ",
(int)my_str_length,
(int)my_str_length,
my_str);
printf(
"%04d-%02d-%02d %02d:%02d:%02d\n",
my_datetime.year,
my_datetime.month,
my_datetime.day,
my_datetime.hour,
my_datetime.minute,
my_datetime.second);
}
mysql_stmt_free_result(stmt);
}
使用预处理CALL支持
可用预处理CALL语句来调存储过程
可访问OUT和INOUT类型过程参数的返回值
预处理CALL语句不能产生多个结果集,且调用者不能访问返回的参数值
CREATE PROCEDURE grade_event_stats
(IN p_event_id INT, OUT p_min INT, OUT p_max INT)
BEGIN
SELECT student_id, score
FROM score
WHERE event_id = p_event_id
ORDER BY student_id;
SELECT MIN(score), MAX(score)
FROM score
WHERE event_id = p_event_id
INTO p_min, p_max;
END;
>>>SET @p_min = NULL, @p_max = NULL;
>>>CALL grade_event_stats(4, @p_min, @p_max);
>>>SELECT @p_min, @p_max;
if(mysql_get_server_version(conn) < 50503)
{
print_error(NULL, "...");
mysql_close(conn);
exit(1);
}
stmt = mysql_stmt_init(conn);
if(!stmt)
{
print_error(NULL, "Could not initialize statement handler");
}
else
{
if(exec_prepared_call(stmt) == 0)
{
process_call_result(conn, stmt);
}
mysql_stmt_close(stmt);
}
static int exec_prepared_call(MYSQL_STMT*stmt)
{
MYSQL_BIND params[3];
int int_date[3];
int i;
if(mysql_stmt_prepare(stmt, "CALL grade_event_stats(?,?,?)", 31))
{
print_stmt_error(stmt, "Cannot prepare statement");
return 1;
}
memset(params, 0, sizeof(params));
for(i = 0; i < 3; ++i)
{
params[i].buffer_type = MYSQL_TYPE_LONG;
params[i].buffer = (char*)&int_data[i];
params[i].length = 0;
params[i].is_null = 0;
}
if(mysql_stmt_bind_param(stmt, params))
{
print_stmt_error(stmt, "Cannot bind parameters");
return 1;
}
int_data[0] = 4;
int_data[1] = 0;
int_data[2] = 0;
if(mysql_stmt_execute(stmt))
{
print_stmt_error(stmt, "Cannot execute statement");
return 1;
}
return 0;
}
在执行CALL后,下面处理其结果
1.在此存储过程中执行的语句,每一个都会产生一个结果集
如SHOW/SELECT/....
2.如此存储过程有OUT或INOUT参数,
则还会有一个附加的单行结果集--其中含最终的参数值,且排列顺序与它们出现在过程定义中的顺序一致.
3.最终状态包
没有结果集与它关联,你可把它与语句或参数结果集区别开.
1.通过获得下一个结果的列数,可确定它是否含有结果集
或是否为最终状态包.
如列数为0,则它是状态包.无需进一步处理.
2.如列数大于0,
则表示有一个有很多个列的结果集
此结果集,可由某个语句产生,也可能含存储过程返回的那些参数值.
3.通过调mysql_stmt_next_result()
可检查是否还有更多的结果
如还有更多结果,函数返回0.
如无,返回-1.
如有错误,返回一个大于0的值.
static void process_call_result(MYSQL* conn, MYSQL_STMT* stmt)
{
int status;
int num_cols;
do
{
if((num_cols = mysql_stmt_field_count(stmt)) > 0)
{
if(conn->server_status & SERVER_PS_OUT_PARAMS)
{
printf("OUT/INOUT parameter values:\n");
}
else
{
printf("Statement result set values:\n");
}
if(process_result_set(stmt, num_cols))
{
break;
}
}
status = mysql_stmt_next_result(stmt);
if(status > 0)
{
print_stmt_error(stmt, "Error checking for next result");
}
}while(status == 0);
}