在我的項目中,連接oracle數據庫並執行各種增刪改查操作,主要是通過oracle的存儲過程,這比直接執行SQL語句要簡單並靈活多變。因為項目需要,要遷移到PostgreSQL下,因為考慮到各個平台的兼容性,采用libpq庫來達到目的,在開發的過程中碰到了一些問題,在這里記錄一下。
業務需求:pg中有個照片表,需要將照片信息及數據插入到該表中。並可能伴隨增刪改查動作。本節只處理插入操作。
照片表創建:
1 CREATE TABLE ist_image 2 ( 3 isc_imgid bigint NOT NULL, 4 isc_imgname character varying(64), 5 isc_updatetime timestamp without time zone, 6 isc_id bigint, -- 插入順序編號(由序列生成) 7 isc_imgdata bytea, 8 CONSTRAINT ist_image_pkey PRIMARY KEY (isc_imgid) 9 )
自定義函數創建:
1 create or replace function func_insertimage(in nImgID bigint, in szImgName varchar, in pImgData bytea) 2 returns integer --必須有返回值 3 as 4 $$ 5 begin 6 insert into ist_image(isc_imgid, isc_imgname, isc_updatetime, isc_id, isc_imgdata) 7 values(nImgID, szImgName, now()::timestamp(0), nextval('iss_seq_isc_id'), pImgData); 8 return 0; 9 end; 10 $$ 11 language plpgsql;
照片數據結構:
1 class ImageInfo { 2 public: 3 __int64 nImgNumber; 4 char szImgName[64]; 5 int nImgDataLen; 6 unsigned char* pImgData; 7 }
插入函數:
1 //注意該函數是有BUG的 2 unsigned long long htonll(unsigned long long val) { 3 return (((unsigned long long )htonl((int)((val << 32) >> 32))) << 32) | (unsigned int)htonl((int)(val >> 32)); 4 } 5 6 int GP_TestInsert(deque<ImageInfo>& deqImages, char* szConnStr) { 7 int nParamNum = 3; //參數個數 8 int paramLens[3] = {0}; //參數長度 9 int paramFormats[3] = {1, 1, 1}; //參數是二進制格式(1表示二進制格式 0表示文本格式) 10 int nReturnForm = 0; //返回值是文本格式 11 unsigned long long ullHID = 0, ullNID = 0; 12 13 //函數調用語句 14 TCHAR szSQL[1024] = {0}; 15 _stprintf_s(szSQL, 1024, _T("select func_insertimage($1::bigint, $2::character varying, $3::bytea)")); 16 17 //連接GP數據庫 18 PGConn* pConn = PQconnectdb(szConnStr); 19 if (CONNECTION_OK != PQstatus(pConn)) { 20 printf("GP_TestInsert Connect failed. ErrMsg: %s", PQerrorMessage(pConn)); 21 return -1; 22 } 23 24 //開始批量插入事務(顯式BEGIN會開始一個事務) 25 PGresult* pRes = PQexec(pConn, "BEGIN"); 26 if (PQresultStatus(pRes) != PGRES_COMMAND_OK) { 27 printf("GP_TestInsert Exec BEGIN command failed. ErrMsg: %s", PQerrorMessage(pConn)); 28 PQclear(pRes); 29 return -1; 30 } 31 PQclear(pRes); //任何時候不再需要 PGresult 時,應該PQclear它來避免內存泄露 32 33 //循環插入數據 34 for (deque<ImageInfo>::iterator it = deqImages.begin(); it != deqImages.end(); ++it) 35 { 36 ullHID = (unsigned long long)(it->nImgNumber); 37 ullNID = htonll(ullHID); 38 39 //value 40 const char* const pszParamValue[3] = { (char*)&ullNID, 41 it->szImgName, 42 (char*)it->pImgData }; 43 44 //Length 45 paramLens[0] = sizeof(__int64); 46 paramLens[1] = (int)strlen(it->szImgName); //字符串類型長度要注意 47 paramLens[2] = it->nImgDataLen; 48 49 //execse 50 pRes = PQexecParams(pConn, szSQL, 3, NULL, (const char**)pszParamValue, paramLens, paramFormats, nReturnForm); 51 ExecStatusType resState = PQresultStatus(pRes); 52 if (PGRES_TUPLES_OK != resState || strcmp(PQgetvalue(pRes, 0, 0), "0") != 0) { 53 printf("GP_TestInsert Exec function failed. ErrCode: %d ErrInfo: %s ErrDesc: %s"), 54 resState, PQresStatus(resState), PQresultErrorMessage(pRes)); 55 return -1; 56 } 57 58 PQclear(pRes); 59 } 60 61 //結束事務(做完此步才會進行commit) 62 pRes = PQexec(pConn, "END"); 63 PQclear(pRes); 64 65 return 0; 66 }
這里面有幾個要注意的地方:
1.每次連接、執行SQL語句要注意檢查狀態,判斷是否執行成功;
2.對於整形數據的插入,要進行字節序轉換,並根據整形結構的長度區別使用不同的字節序轉換函數;
3.PostgreSQL數據庫好像是默認開啟AutoCommit的,若不想其自動commit,可以采用以下兩種辦法:
1)關閉數據庫的自動commit屬性,不過這種方式會導致管理時,出現一些煩人的提示;
2)每次顯式調用BEGIN命令,一個事務(很多次條語句執行過后)結束后,執行END命令來commit,這種方式更靈活,個人更喜歡這種方式;
4.任何時候不再需要PGresult時,應該調用PQclear來清理它,避免內存泄露,具體請參見libpq說明文檔;
當然,使用insert方式插入數據,對於PostgreSQL來說,並不是高效的方式,這里只是用這個例子來簡單說明使用libpq庫調用自定義函數的方式,后續再補充使用更高效方式來插入數據的例子,對於PostgreSQL來說,我也僅僅是初學者,若文中有錯誤煩請指正。