Rust可以通过外来函数接口(FFI)访问C、C++编写的函数,因此我们可以通过PostgreSQL的libpq库访问PostgreSQL,本文旨在讲述方法及原理,因此例子代码没做任何封装,也没做错误处理。
libpq相关的常量、结构、函数声明均在libpq-fe.h中定义,我们需要先转换成rust的语法。这里先转换一些本例需要用到的。事实上我们是不需要手工转换的,Rust有提供bindgen工具帮助我们转换,只需要执行cargo install bindgen-cli安装,然后运行bindgen即可。
libpq相关声明 - lib_pq.rs
pubtypeOid= ::std::os::raw::c_uint; pubtypepg_int64= ::std::os::raw::c_long; pubconstCONNECTION_OK: ConnStatusType=0; pubconstCONNECTION_BAD: ConnStatusType=1; pubconstCONNECTION_STARTED: ConnStatusType=2; pubconstCONNECTION_MADE: ConnStatusType=3; pubconstCONNECTION_AWAITING_RESPONSE: ConnStatusType=4; pubconstCONNECTION_AUTH_OK: ConnStatusType=5; pubconstCONNECTION_SETENV: ConnStatusType=6; pubconstCONNECTION_SSL_STARTUP: ConnStatusType=7; pubconstCONNECTION_NEEDED: ConnStatusType=8; pubconstCONNECTION_CHECK_WRITABLE: ConnStatusType=9; pubconstCONNECTION_CONSUME: ConnStatusType=10; pubconstCONNECTION_GSS_STARTUP: ConnStatusType=11; pubconstCONNECTION_CHECK_TARGET: ConnStatusType=12; pubconstCONNECTION_CHECK_STANDBY: ConnStatusType=13; pubtypeConnStatusType= ::std::os::raw::c_uint; pubconstPGRES_POLLING_FAILED: PostgresPollingStatusType=0; pubconstPGRES_POLLING_READING: PostgresPollingStatusType=1; pubconstPGRES_POLLING_WRITING: PostgresPollingStatusType=2; pubconstPGRES_POLLING_OK: PostgresPollingStatusType=3; pubconstPGRES_POLLING_ACTIVE: PostgresPollingStatusType=4; pubtypePostgresPollingStatusType= ::std::os::raw::c_uint; pubconstPGRES_EMPTY_QUERY: ExecStatusType=0; pubconstPGRES_COMMAND_OK: ExecStatusType=1; pubconstPGRES_TUPLES_OK: ExecStatusType=2; pubconstPGRES_COPY_OUT: ExecStatusType=3; pubconstPGRES_COPY_IN: ExecStatusType=4; pubconstPGRES_BAD_RESPONSE: ExecStatusType=5; pubconstPGRES_NONFATAL_ERROR: ExecStatusType=6; pubconstPGRES_FATAL_ERROR: ExecStatusType=7; pubconstPGRES_COPY_BOTH: ExecStatusType=8; pubconstPGRES_SINGLE_TUPLE: ExecStatusType=9; pubconstPGRES_PIPELINE_SYNC: ExecStatusType=10; pubconstPGRES_PIPELINE_ABORTED: ExecStatusType=11; pubtypeExecStatusType= ::std::os::raw::c_uint; pubconstPQTRANS_IDLE: PGTransactionStatusType=0; pubconstPQTRANS_ACTIVE: PGTransactionStatusType=1; pubconstPQTRANS_INTRANS: PGTransactionStatusType=2; pubconstPQTRANS_INERROR: PGTransactionStatusType=3; pubconstPQTRANS_UNKNOWN: PGTransactionStatusType=4; pubtypePGTransactionStatusType= ::std::os::raw::c_uint; pubconstPQERRORS_TERSE: PGVerbosity=0; pubconstPQERRORS_DEFAULT: PGVerbosity=1; pubconstPQERRORS_VERBOSE: PGVerbosity=2; pubconstPQERRORS_SQLSTATE: PGVerbosity=3; pubtypePGVerbosity= ::std::os::raw::c_uint; pubstructpg_conn { _unused: [u8; 0], } pubtypePGconn=pg_conn; pubstructpg_result { _unused: [u8; 0], } pubtypePGresult=pg_result; pubstructpg_cancel { _unused: [u8; 0], } pubtypePGcancel=pg_cancel; //本例我们使用了以下函数//告诉Rust需要连接libpqextern"C" { pubfnPQconnectdb(conninfo: *const ::std::os::raw::c_char) ->*mutPGconn; pubfnPQstatus(conn: *constPGconn) ->ConnStatusType; pubfnPQresultStatus(res: *constPGresult) ->ExecStatusType; pubfnPQexec(conn: *mutPGconn, query: *const ::std::os::raw::c_char) ->*mutPGresult; pubfnPQnfields(res: *constPGresult) -> ::std::os::raw::c_int; pubfnPQntuples(res: *constPGresult) -> ::std::os::raw::c_int; pubfnPQclear(res: *mutPGresult); pubfnPQfinish(conn: *mutPGconn); pubfnPQerrorMessage(conn: *constPGconn) ->*mut ::std::os::raw::c_char; pubfnPQfname( res: *constPGresult, field_num: ::std::os::raw::c_int, ) ->*mut ::std::os::raw::c_char; pubfnPQgetvalue( res: *constPGresult, tup_num: ::std::os::raw::c_int, field_num: ::std::os::raw::c_int, ) ->*mut ::std::os::raw::c_char; }
main.rs -- 访问pg
usestd::ffi::{CString}; usestd::ffi::CStr; usestd::process::{exit}; usecrate::lib_pq::{ CONNECTION_OK, PGRES_TUPLES_OK, PGconn, PQclear, PQconnectdb, PQerrorMessage, PQexec, PQfinish, PQfname, PQgetvalue, PQnfields, PQntuples, PQresultStatus, PQstatus}; //声明lib_pq模块modlib_pq; fnexit_nicely(conn: *mutPGconn) { unsafe {PQfinish(conn)}; exit(1); } fnmain() { //数据库连接字符串lets=CString::new("dbname=postgres user=postgres password=xxxxxx host=localhost").unwrap(); //建立连接,调用外部函数需要用unsafe包装letconn=unsafe { PQconnectdb(s.as_ptr()) }; //检查状态ifunsafe{ PQstatus(conn) } !=CONNECTION_OK { exit_nicely(conn); } //执行sqlletsql=CString::new("select * from test1").unwrap(); letres=unsafe{PQexec(conn, sql.as_ptr())}; //检查执行结果ifunsafe{PQresultStatus(res)} !=PGRES_TUPLES_OK { println!("Exec sql failed: {:?}", unsafe{PQerrorMessage(conn)}); unsafe{PQclear(res)}; exit_nicely(conn); } //获取字段名letn_fields=unsafe{PQnfields(res)}; foriin0..n_fields { unsafe { letn_field= &*PQfname(res, i); letv=CStr::from_ptr(n_field).to_string_lossy(); print!("{:<15}", v); } } println!("{}", ""); //打印查询结果letn_tuples=unsafe{PQntuples(res)}; foriin0..n_tuples { forjin0..n_fields { unsafe { letn_tuple= &*PQgetvalue(res, i, j); letv=CStr::from_ptr(n_tuple).to_string_lossy(); print!("{:<15}", v); } } println!("{}", ""); } //清理,释放资源unsafe {PQfinish(conn)}; }
整个例子非常简单,如果执行cargo build报错,提示找不到外部符号PQconnectdb之类,是因为rust找不到libpq的位置,这时需要生成一个构建文件build.rs,此文件必须与Cargo.toml位于同级目录,同时还需要在Cargo.toml的package区段里加上build = "build.rs"
构建文件 build.rs
fnmain() { println!("cargo:rustc-link-search=/usr/local/pgsql15/lib"); println!("cargo:rustc-link-lib=pq"); }
构建文件就是告诉rust编译器去哪里找libpq。构建文件有很多选项,具体可参考Build Scripts 。
Cargo.toml
[package] name = "pg_test" version = "0.1.0" edition = "2021" build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]