PostgreSQL教程FG125-ECPG实操:嵌入式SQL编写与编译
本文档风哥主要介绍PostgreSQL的ECPG(Embedded SQL in C)实操,包括ECPG的编写规范、编译流程、测试方法等内容,风哥教程参考PostgreSQL官方文档ECPG – Embedded SQL in C内容,适合开发人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。
Part01-基础概念与理论知识
1.1 ECPG基础
ECPG是PostgreSQL的嵌入式SQL预处理器,允许在C语言程序中直接使用SQL语句。它将包含嵌入式SQL语句的C代码转换为标准C代码,然后通过标准C编译器编译。更多视频教程www.fgedu.net.cn
- 预处理器(ecpg)扫描C代码中的嵌入式SQL语句
- 将嵌入式SQL语句转换为对libecpg库函数的调用
- 生成标准C代码文件
- 使用标准C编译器编译生成的C代码
- 链接libecpg库和其他必要的库
1.2 ECPG语法
ECPG的基本语法:
// 1. 声明部分
EXEC SQL BEGIN DECLARE SECTION;
// 变量声明
char fgedudb[50];
int id;
char name[100];
EXEC SQL END DECLARE SECTION;
// 2. 连接数据库
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
// 3. 执行SQL语句
EXEC SQL SELECT id, name INTO :id, :name FROM fgedu_employees;
// 4. 错误处理
if (sqlca.sqlcode != 0) {
fprintf(stderr, “SQL错误: %s\n”, sqlca.sqlerrm.sqlerrmc);
}
// 5. 断开连接
EXEC SQL DISCONNECT;
1.3 ECPG编译流程
ECPG的编译流程:
from oracle:www.itpux.com
- 预处理:使用ecpg命令将.pgc文件转换为.c文件
- 编译:使用C编译器将.c文件编译为目标文件
- 链接:将目标文件与libecpg库等链接为可执行文件
- 运行:执行生成的可执行文件
Part02-生产环境规划与建议
2.1 ECPG环境搭建
ECPG环境搭建:
# 1. 安装PostgreSQL开发包
$ sudo dnf install postgresql-devel postgresql-server-devel
# 2. 验证ECPG安装
$ ecpg –version
ecpg (PostgreSQL) 15.0
# 3. 检查ECPG头文件
$ find /usr/include -name “ecpglib.h”
/usr/include/postgresql/ecpglib.h
# 4. 检查ECPG库文件
$ find /usr/lib64 -name “libecpg*”
/usr/lib64/libecpg.so.6
/usr/lib64/libecpg.so.6.15
/usr/lib64/libecpg_compat.so.3
/usr/lib64/libecpg_compat.so.3.15
2.2 ECPG配置
ECPG的配置选项:
# 常用选项
– -c:生成C代码
– -o file:指定输出文件
– -I directory:指定头文件搜索路径
– -D name=value:定义宏
– -U name:取消定义宏
– -r:生成可重入代码
– -t:生成调试信息
– -v: verbose模式
# 示例
$ ecpg -o example.c example.pgc
$ ecpg -I /usr/include/postgresql example.pgc
$ ecpg -r -t example.pgc
2.3 ECPG最佳实践
ECPG的使用最佳实践:
- 代码组织:将SQL语句与C代码分离,提高可读性
- 错误处理:检查所有SQL操作的返回值,妥善处理错误
- 资源管理:及时释放数据库连接和游标
- 参数化查询:使用参数化查询,避免SQL注入
- 事务管理:合理使用事务,确保数据一致性
- 性能优化:优化SQL语句,减少网络往返
- 代码风格:保持一致的代码风格,提高可维护性
Part03-生产环境项目实施方案
3.1 ECPG代码编写
3.1.1 基本结构
// 文件:basic_structure.pgc
#include
#include
// 声明部分
EXEC SQL BEGIN DECLARE SECTION;
char fgedudb[50] = “fgedudb”;
char fgedu[50] = “postgres”;
char password[50] = “postgres_password”;
int id;
char name[100];
int age;
EXEC SQL END DECLARE SECTION;
int main() {
// 连接数据库
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “连接失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 1;
}
// 执行SQL语句
// …
// 断开连接
EXEC SQL DISCONNECT;
return 0;
}
3.1.2 数据类型映射
// 文件:data_types.pgc
#include
EXEC SQL BEGIN DECLARE SECTION;
// 整数类型
int i;
long l;
short s;
// 浮点类型
float f;
double d;
// 字符串类型
char str[100];
// 日期时间类型
char date[10];
char time[8];
char timestamp[20];
EXEC SQL END DECLARE SECTION;
int main() {
// 连接数据库
EXEC SQL CONNECT TO fgedudb USER pgsql USING postgres_password;
// 测试数据类型
EXEC SQL SELECT 1, 1000000, 100, 3.14, 3.1415926, ‘hello’, current_date, current_time, current_timestamp
INTO :i, :l, :s, :f, :d, :str, :date, :time, :timestamp;
if (sqlca.sqlcode == 0) {
printf(“整数: %d, %ld, %d\n”, i, l, s);
printf(“浮点数: %f, %lf\n”, f, d);
printf(“字符串: %s\n”, str);
printf(“日期: %s\n”, date);
printf(“时间: %s\n”, time);
printf(“时间戳: %s\n”, timestamp);
}
// 断开连接
EXEC SQL DISCONNECT;
return 0;
}
// 编译步骤
$ ecpg data_types.pgc
$ gcc -o data_types data_types.c -lecpg -lpq -I/usr/include/postgresql
// 运行结果
$ ./data_types
整数: 1, 1000000, 100
浮点数: 3.140000, 3.141593
字符串: hello
日期: 2026-04-02
时间: 10:30:45
时间戳: 2026-04-02 10:30:45.123456
3.2 ECPG编译
3.2.1 基本编译
# 1. 创建ECPG源文件
$ vi hello_ecpg.pgc
#include
EXEC SQL BEGIN DECLARE SECTION;
char fgedudb[50] = “fgedudb”;
char fgedu[50] = “postgres”;
char password[50] = “postgres_password”;
EXEC SQL END DECLARE SECTION;
int main() {
// 连接数据库
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “连接失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 1;
}
printf(“Hello, ECPG!\n”);
printf(“连接数据库成功!\n”);
// 断开连接
EXEC SQL DISCONNECT;
return 0;
}
# 2. 预处理
$ ecpg hello_ecpg.pgc
# 3. 编译
$ gcc -o hello_ecpg hello_ecpg.c -lecpg -lpq -I/usr/include/postgresql
# 4. 运行
$ ./hello_ecpg
Hello, ECPG!
连接数据库成功!
3.2.2 使用Makefile
# 创建Makefile
$ vi Makefile
CC = gcc
CFLAGS = -Wall -Wextra -I/usr/include/postgresql
LIBS = -lecpg -lpq
all: hello_ecpg data_types
hello_ecpg: hello_ecpg.pgc
ecpg hello_ecpg.pgc
$(CC) $(CFLAGS) -o hello_ecpg hello_ecpg.c $(LIBS)
data_types: data_types.pgc
ecpg data_types.pgc
$(CC) $(CFLAGS) -o data_types data_types.c $(LIBS)
clean:
rm -f hello_ecpg data_types hello_ecpg.c data_types.c
# 编译项目
$ make
# 运行程序
$ ./hello_ecpg
Hello, ECPG!
连接数据库成功!
$ ./data_types
整数: 1, 1000000, 100
浮点数: 3.140000, 3.141593
字符串: hello
日期: 2026-04-02
时间: 10:30:45
时间戳: 2026-04-02 10:30:45.123456
# 清理
$ make clean
3.3 ECPG测试
# 1. 创建测试文件
$ vi test_ecpg.pgc
#include
#include
EXEC SQL BEGIN DECLARE SECTION;
char fgedudb[50] = “fgedudb”;
char fgedu[50] = “postgres”;
char password[50] = “postgres_password”;
int id;
char name[100];
int age;
int count;
EXEC SQL END DECLARE SECTION;
// 测试连接
int test_connection() {
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “连接失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf(“连接测试成功!\n”);
return 1;
}
// 测试创建表
int test_create_table() {
EXEC SQL CREATE TABLE fgedu_IF NOT EXISTS fgedu_test (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age INTEGER NOT NULL
);
if (sqlca.sqlcode != 0) {
fprintf(stderr, “创建表失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf(“创建表测试成功!\n”);
return 1;
}
// 测试插入数据
int test_insert_data() {
EXEC SQL INSERT INTO fgedu_test (name, age) VALUES (‘测试用户’, 25);
if (sqlca.sqlcode != 0) {
fprintf(stderr, “插入数据失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf(“插入数据测试成功!\n”);
return 1;
}
// 测试查询数据
int test_query_data() {
EXEC SQL SELECT id, name, age INTO :id, :name, :age FROM fgedu_test WHERE name = ‘测试用户’;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “查询数据失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf(“查询数据测试成功! id=%d, name=%s, age=%d\n”, id, name, age);
return 1;
}
// 测试更新数据
int test_update_data() {
EXEC SQL UPDATE fgedu_test SET age = 26 WHERE name = ‘测试用户’;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “更新数据失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf(“更新数据测试成功!\n”);
// 验证更新
EXEC SQL SELECT age INTO :age FROM fgedu_test WHERE name = ‘测试用户’;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “验证更新失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf(“验证更新成功! age=%d\n”, age);
return 1;
}
// 测试删除数据
int test_delete_data() {
EXEC SQL DELETE FROM fgedu_test WHERE name = ‘测试用户’;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “删除数据失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf(“删除数据测试成功!\n”);
return 1;
}
// 测试事务
int test_transaction() {
// 开始事务
EXEC SQL BEGIN TRANSACTION;
// 插入数据
EXEC SQL INSERT INTO fgedu_test (name, age) VALUES (‘事务测试’, 30);
if (sqlca.sqlcode != 0) {
EXEC SQL ROLLBACK;
fprintf(stderr, “插入数据失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
// 提交事务
EXEC SQL COMMIT;
// 验证
EXEC SQL SELECT COUNT(*) INTO :count FROM fgedu_test WHERE name = ‘事务测试’;
if (sqlca.sqlcode != 0) {
fprintf(stderr, “验证事务失败: %s\n”, sqlca.sqlerrm.sqlerrmc);
return 0;
}
printf(“事务测试成功! 记录数: %d\n”, count);
// 清理
EXEC SQL DELETE FROM fgedu_test WHERE name = ‘事务测试’;
return 1;
}
int main() {
if (!test_connection()) {
return 1;
}
if (!test_create_table()) {
EXEC SQL DISCONNECT;
return 1;
}
if (!test_insert_data()) {
EXEC SQL DISCONNECT;
return 1;
}
if (!test_query_data()) {
EXEC SQL DISCONNECT;
return 1;
}
if (!test_update_data()) {
EXEC SQL DISCONNECT;
return 1;
}
if (!test_delete_data()) {
EXEC SQL DISCONNECT;
return 1;
}
if (!test_transaction()) {
EXEC SQL DISCONNECT;
return 1;
}
// 断开连接
EXEC SQL DISCONNECT;
printf(“所有测试通过!\n”);
return 0;
}
# 2. 编译和运行测试
$ ecpg test_ecpg.pgc
$ gcc -o test_ecpg test_ecpg.c -lecpg -lpq -I/usr/include/postgresql
$ ./test_ecpg
# 运行结果
连接测试成功!
创建表测试成功!
插入数据测试成功!
查询数据测试成功! id=1, name=测试用户, age=25
更新数据测试成功!
验证更新成功! age=26
删除数据测试成功!
事务测试成功! 记录数: 1
所有测试通过!
Part04-生产案例与实战讲解
4.1 ECPG常见问题
在使用ECPG时,可能会遇到以下问题:
4.1.1 编译错误
# 错误信息
$ ecpg example.pgc
example.pgc:5: ERROR: syntax error at or near “EXEC”
# 原因:缺少分号或语法错误
# 解决方案:检查SQL语句的语法,确保每个语句都以分号结尾
# 问题2:C编译器报错
# 错误信息
$ gcc -o example example.c -lecpg -lpq
/tmp/cc12345.o: In function `main’:
example.c:(.text+0x10): undefined reference to `ECPGconnect’
# 原因:缺少libecpg库
# 解决方案:确保链接了libecpg库
$ gcc -o example example.c -lecpg -lpq
4.1.2 运行时错误
# 错误信息
连接失败: FATAL: password authentication failed for fgedu “postgres”
# 原因:密码错误或连接参数不正确
# 解决方案:检查连接参数,确保用户名和密码正确
# 问题2:SQL执行失败
# 错误信息
SQL错误: ERROR: relation “fgedu_employees” does not exist
# 原因:表不存在
# 解决方案:确保表已创建,或在代码中添加创建表的语句
4.2 ECPG问题解决方案
# 1. 编译问题
## 问题:ECPG预处理器找不到头文件
## 解决方案:
$ ecpg -I /usr/include/postgresql example.pgc
## 问题:C编译器找不到头文件
## 解决方案:
$ gcc -o example example.c -lecpg -lpq -I/usr/include/postgresql
## 问题:链接失败,找不到libecpg
## 解决方案:
$ gcc -o example example.c -lecpg -lpq -L/usr/lib64
# 2. 运行时问题
## 问题:连接失败
## 解决方案:
– 检查数据库是否运行
– 检查连接参数是否正确
– 检查用户权限
– 检查网络连接
## 问题:SQL执行失败
## 解决方案:
– 检查SQL语句语法
– 检查表和字段是否存在
– 检查用户权限
– 检查数据类型是否匹配
## 问题:内存泄漏
## 解决方案:
– 确保断开数据库连接
– 确保关闭游标
– 检查内存分配和释放
# 3. 性能问题
## 问题:查询速度慢
## 解决方案:
– 优化SQL语句
– 使用索引
– 减少网络往返
– 使用批处理
## 问题:连接池管理
## 解决方案:
– 实现连接池
– 合理设置连接超时
– 复用连接
4.3 ECPG实战案例
# 1. 项目结构
$ mkdir -p student_ecpg/src
$ cd student_ecpg
# 2. 创建源文件
$ vi src/student.pgc
#include
#include
#include
EXEC SQL BEGIN DECLARE SECTION;
char fgedudb[50] = “fgedudb”;
char fgedu[50] = “postgres”;
char password[50] = “postgres_password”;
int id;
char name[100];
int age;
char gender[10];
char major[100];
int count;
EXEC SQL END DECLARE SECTION;
// 错误处理函数
void handle_error() {
fprintf(stderr, “SQL错误: %s\n”, sqlca.sqlerrm.sqlerrmc);
fprintf(stderr, “SQL代码: %d\n”, sqlca.sqlcode);
}
// 连接数据库
int connect_db() {
EXEC SQL CONNECT TO :fgedudb USER :fgedu USING :password;
if (sqlca.sqlcode != 0) {
handle_error();
return 0;
}
printf(“连接数据库成功!\n”);
return 1;
}
// 创建表
void create_table() {
EXEC SQL CREATE TABLE fgedu_IF NOT EXISTS fgedu_students (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age INTEGER NOT NULL,
gender VARCHAR(10) NOT NULL,
major VARCHAR(100) NOT NULL
);
if (sqlca.sqlcode != 0) {
handle_error();
return;
}
printf(“创建学生表成功!\n”);
}
// 添加学生
void add_student(const char *student_name, int student_age, const char *student_gender, const char *student_major) {
strcpy(name, student_name);
age = student_age;
strcpy(gender, student_gender);
strcpy(major, student_major);
EXEC SQL INSERT INTO fgedu_students (name, age, gender, major)
VALUES (:name, :age, :gender, :major);
if (sqlca.sqlcode != 0) {
handle_error();
return;
}
printf(“添加学生 %s 成功!\n”, name);
}
// 查询所有学生
void query_all_students() {
EXEC SQL DECLARE student_cursor CURSOR FOR
SELECT id, name, age, gender, major FROM fgedu_students;
EXEC SQL OPEN student_cursor;
printf(“\n学生列表:\n”);
printf(“ID\t姓名\t年龄\t性别\t专业\n”);
EXEC SQL WHENEVER NOT FOUND DO break;
while (1) {
EXEC SQL FETCH student_cursor INTO :id, :name, :age, :gender, :major;
printf(“%d\t%s\t%d\t%s\t%s\n”, id, name, age, gender, major);
}
EXEC SQL CLOSE student_cursor;
}
// 根据ID查询学生
void query_student_by_id(int student_id) {
id = student_id;
EXEC SQL SELECT name, age, gender, major INTO :name, :age, :gender, :major
FROM fgedu_students WHERE id = :id;
if (sqlca.sqlcode != 0) {
handle_error();
return;
}
printf(“\n学生信息:\n”);
printf(“ID: %d\n”, id);
printf(“姓名: %s\n”, name);
printf(“年龄: %d\n”, age);
printf(“性别: %s\n”, gender);
printf(“专业: %s\n”, major);
}
// 更新学生信息
void update_student(int student_id, const char *student_name, int student_age, const char *student_gender, const char *student_major) {
id = student_id;
strcpy(name, student_name);
age = student_age;
strcpy(gender, student_gender);
strcpy(major, student_major);
EXEC SQL UPDATE fgedu_students
SET name = :name, age = :age, gender = :gender, major = :major
WHERE id = :id;
if (sqlca.sqlcode != 0) {
handle_error();
return;
}
printf(“更新学生 ID=%d 成功!\n”, id);
}
// 删除学生
void delete_student(int student_id) {
id = student_id;
EXEC SQL DELETE FROM fgedu_students WHERE id = :id;
if (sqlca.sqlcode != 0) {
handle_error();
return;
}
printf(“删除学生 ID=%d 成功!\n”, id);
}
// 统计学生人数
void count_students() {
EXEC SQL SELECT COUNT(*) INTO :count FROM fgedu_students;
if (sqlca.sqlcode != 0) {
handle_error();
return;
}
printf(“\n学生总人数: %d\n”, count);
}
// 断开连接
void disconnect_db() {
EXEC SQL DISCONNECT;
printf(“连接已关闭!\n”);
}
int main() {
// 连接数据库
if (!connect_db()) {
return 1;
}
// 创建表
create_table();
// 添加学生
add_student(“风哥1号”, 20, “男”, “计算机科学与技术”);
add_student(“风哥2号”, 21, “女”, “软件工程”);
add_student(“王五”, 19, “男”, “数据科学”);
add_student(“赵六”, 20, “女”, “人工智能”);
// 查询所有学生
query_all_students();
// 统计学生人数
count_students();
// 根据ID查询学生
query_student_by_id(1);
// 更新学生信息
update_student(1, “风哥1号”, 21, “男”, “计算机科学与技术”);
// 查询所有学生
query_all_students();
// 删除学生
delete_student(4);
// 查询所有学生
query_all_students();
// 统计学生人数
count_students();
// 断开连接
disconnect_db();
return 0;
}
# 3. 创建Makefile
$ vi Makefile
CC = gcc
CFLAGS = -Wall -Wextra -I/usr/include/postgresql
LIBS = -lecpg -lpq
all: student
student: src/student.pgc
ecpg src/student.pgc
$(CC) $(CFLAGS) -o student src/student.c $(LIBS)
clean:
rm -f student src/student.c
# 4. 编译和运行
$ make
$ ./student
# 运行结果
连接数据库成功!
创建学生表成功!
添加学生 风哥1号 成功!
添加学生 风哥2号 成功!
添加学生 王五 成功!
添加学生 赵六 成功!
学生列表:
ID 姓名 年龄 性别 专业
1 风哥1号 20 男 计算机科学与技术
2 风哥2号 21 女 软件工程
3 王五 19 男 数据科学
4 赵六 20 女 人工智能
学生总人数: 4
学生信息:
ID: 1
姓名: 风哥1号
年龄: 20
性别: 男
专业: 计算机科学与技术
更新学生 ID=1 成功!
学生列表:
ID 姓名 年龄 性别 专业
1 风哥1号 21 男 计算机科学与技术
2 风哥2号 21 女 软件工程
3 王五 19 男 数据科学
4 赵六 20 女 人工智能
删除学生 ID=4 成功!
学生列表:
ID 姓名 年龄 性别 专业
1 风哥1号 21 男 计算机科学与技术
2 风哥2号 21 女 软件工程
3 王五 19 男 数据科学
学生总人数: 3
连接已关闭!
Part05-风哥经验总结与分享
5.1 ECPG使用技巧
ECPG使用技巧:
- 代码组织:将SQL语句与C代码分离,提高可读性
- 错误处理:检查所有SQL操作的返回值,妥善处理错误
- 资源管理:及时释放数据库连接和游标
- 参数化查询:使用参数化查询,避免SQL注入
- 事务管理:合理使用事务,确保数据一致性
- 性能优化:优化SQL语句,减少网络往返
- 代码风格:保持一致的代码风格,提高可维护性
- 测试:充分测试应用程序,确保功能正确
- 版本控制:使用版本控制系统管理代码
- 文档:编写清晰的文档,便于维护和使用
5.2 ECPG性能优化
# 1. 连接管理
– 使用连接池:减少连接建立和关闭的开销
– 适当设置连接超时:避免连接等待时间过长
– 使用连接复用:在多个请求之间复用连接
– 批量操作:减少网络往返次数
# 2. 查询优化
– 使用预处理语句:提高查询执行效率
– 批量操作:减少网络往返次数
– 索引使用:确保查询使用适当的索引
– 游标使用:处理大型结果集
– 分页查询:避免一次性加载过多数据
# 3. 内存管理
– 合理分配内存:避免内存泄漏
– 及时释放资源:关闭游标和连接
– 使用适当的数据类型:减少内存使用
– 避免内存碎片:合理管理内存分配
# 4. 编译优化
– 使用-O2或-O3优化级别:提高代码执行效率
– 启用警告:-Wall -Wextra
– 使用合适的C标准:-std=c99或-std=c11
– 链接优化:-Wl,-O1
# 5. 数据库优化
– 调整PostgreSQL参数:shared_buffers, work_mem等
– 合理设计表结构:使用适当的索引
– 定期维护数据库:VACUUM, ANALYZE
– 使用分区表:处理大型表
– 合理使用物化视图:提高查询性能
5.3 ECPG资源与工具
ECPG相关资源与工具:
- PostgreSQL官方文档:https://www.postgresql.org/docs/current/ecpg.html
- ECPG编程指南:https://www.postgresql.org/docs/current/ecpg-programming.html
- PostgreSQL源代码:包含ECPG示例
- PostgreSQL社区:https://www.postgresql.org/community/
- ECPG邮件列表:pgsql-ecpg@lists.postgresql.org
- Stack Overflow:搜索ECPG相关问题
- GitHub:查找ECPG相关项目和示例
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
