Tải bản đầy đủ (.doc) (11 trang)

BÁO CÁO BỔ SUNG HÀM MỞ RỘNG VIẾT BẰNG C VÀO HỆ QUẢN TRỊ CƠ SỞ DỮ LIỆU POSTGRESQL

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (193.36 KB, 11 trang )

ĐẠI HỌC QUỐC GIA HÀ NỘI
ĐẠI HỌC CÔNG NGHỆ

BÁO CÁO
BỔ SUNG HÀM MỞ RỘNG VIẾT BẰNG C VÀO HỆ QUẢN
TRỊ CƠ SỞ DỮ LIỆU POSTGRESQL

Học viên: Bùi Hoàng Khánh
Lớp K16T3

Hà Nội, 03/2010


MỤC LỤC
MỤC LỤC......................................................................................................................................................2
Tính mở rộng của PostgreSQL.......................................................................................................................3
Hàm viết bằng ngôn ngữ C.............................................................................................................................3
Kết luận........................................................................................................................................................10
TÀI LIỆU THAM KHẢO............................................................................................................................11

2


Tính mở rộng của PostgreSQL
I.1. Tính mở rộng của PosgreSQL
PostgreSQL cũng giống với các hệ cơ sở dữ liệu quan chuẩn hệ khác, lưu trữ các thông tin
về cơ sở dữ liệu, các bảng, cột… trong một cái gọi là danh mục hệ thống (system catalogs) (còn
được gọi là thư viện dữ liệu). Các danh mục này giống như các bảng đối với người dùng. Một
điểm khác biệt giữa PostgreSQL với các hệ cơ sở dữ liệu quan hệ chuẩn là PostgreSQL lưu nhiều
thông tin hơn trong các danh mục: không chỉ có thông tin về bảng và cột, mà còn thông tin về
kiểu dữ liệu, hàm, phương thức truy cập…


Những bảng này người dùng có thể thay đổi được và vì PostgreSQL hoạt động dựa trên
các bản này, nên người dùng có thể mở rộng.
I.2. Hàm người dùng định nghĩa
PostgreSQL cung cấp 4 loại hàm người dùng định nghĩa, gồm:
-

Các hàm ngôn ngữ truy vấn (được viết bằng SQL)
Các hàm ngôn ngữ thủ tục (được viết bằng PL/pgSQL hoặc PL/Tcl)
Các nội hàm
Các hàm ngôn ngữ C

Các loại hàm này có thể sử dụng các kiểu dữ liệu cơ bản, phức hợp hoặc kết hợp hai loại
này làm tham số vào. Các hàm này cũng có thể được định nghĩa để trả về các loại dữ liệu khác
nhau: cơ sơ, phức hợp, và kết hợp. Nhiều loại có thể nhận và trả về các kiểu dữ liệu giả (pseudotypes), như các kiểu đa hình.

Hàm viết bằng ngôn ngữ C
Hàm người dùng định nghĩa có thể viết bằng ngôn ngữ C hoặc một ngôn ngữ tương thích
với C như C++. Những hàm này được biên dịch thành các đối tượng có thể tải động (dinamically
loadable objects) (còn được gọi là các thư viện chia sẻ) và được tải vào bởi máy chủ khi cần. Tính
năng tải động này phân biệt các hàm ngôn ngữ C với các hàm bên trong – thực tế thì quy định lập
trình về cơ bản là giống nhau.
Có 2 cách để gọi hàm viết bằng C:
-

Cách thứ nhất là viết một macro PG_FUNCTION_INFO_V1() cho hàm, gọi là version 1.
Cách thứ hai không sử dụng macro trên, gọi là version 0. Cách này khó linh hoạt và thiếu
một số chức năng. Tuy nhiên, nó vẫn được phát triển vì khả năng tương thích.
Cả hai cách này phải xác định tên ngôn ngữ ở CREATE FUNCTION trong là C.
3



I.3. Quá trình tải động
Như đã trình bày, các hàm ngôn ngữ C được tải động bởi server khi cần. Lần đầu tiên một
hàm người dùng định nghĩa trong một file đối tượng có thể tải được gọi trong một phiên, bộ tải
động file đối tượng đó vào bộ nhớ. Như vậy, CREATE FUNCTION phải xác định 2 thông tin của
hàm là: tên của file đối tượng, và tên C của hàm trong file đối tượng. Nếu tên C không xác định
thì lấy tên giống với tên hàm SQL.
Thuật toán để xác định vị trí của file đối tượng dựa vào tên được đưa ra ở CREATE
FUNCTION như sau:
1. Nếu tên là một đường dẫn tuyệt đối thì file được tải
2. Nếu tên bắt đầu bằng $libdir, thì phần đó được thay thế bằng tên đường dẫn thư
viện PostgreSQL, được xác định lúc build.
3. Nếu tên không chứa thành phần thư mục, thì file sẽ được tìm trong đường dẫn được xác
định bởi tham số cấu hình dymamic_library_path.
4. Ngược lại (file không tìm thấy trong đường dẫn, hoặc nó chứa một đường dẫn không
tuyệt đối), bộ tải động sẽ thử để lấy được tên như đã đưa ra.
Nếu các bước này không làm việc thì phần mở rộng của thư viện chia sẽ (thường là .so)
được thêm vào cuối tên file và thực hiện lại các bước trên.
Một chú ý là nên lưu các đối tượng trong thư mục thư viện động hoặc trong $libdir, có
thể xác định bằng lệnh pg_config –-pkglibdir.
Sau lần sử dụng đầu tiên, đối tượng được tải động được lưu trong bộ nhớ. Những lần gọi
tiếp theo trong cùng một phiên đến các hàm trong file này chỉ cần tìm trong bảng các đối tượng.
Nếu cần tải lại đối tượng thì sử dụng lệnh LOAD hoặc bắt đầu một phiên mới.
I.4. Các kiểu dữ liệu cơ bản trong hàm ngôn ngữ C
Để viết các hàm ngôn ngữ C, cần biết cách PostgreSQL biểu diễn các kiểu dữ liệu cơ bản
và cách chúng được truyền vào cũng như được trả về. Các hàm tự định nghĩa cần lưu ý là phải
dựa vào quy tắc định nghĩa trong PostgreSQL, để nó có thể chạy được hàm do tự bạn xây dựng.
Như vậy, PostgreSQL sẽ lưu trữ và lấy dữ liệu từ ổ đĩa ra và sử dụng hàm tự định nghĩa để nhập,
xử lý, đưa dữ liệu ra.
Dữ liệu trong PostgreSQL có thể là một trong ba dạng sau:

-

Dạng 1: Truyền bằng giá trị, có độ dài cố định
Độ dài có thể là 1, 2, 4 bytes (cũng có thể là 8 bytes).Có thể xác định độ dài bằng sizeof().
Ví dụ sử dụng kiểu int trên máy Unix:
Typedef int int4;
4


-

Dạng 2: Truyền bằng tham chiếu, có độ dài cố định
Khi truyền dữ liệu vào và ra khỏi hàm PostgreSQL thì phải sử dụng con trỏ. Để trả lại giá
trị của biến, phải cấp vùng nhớ thông qua palloc, và để một con trỏ trỏ tới nó. Ví dụ:
typedef struct
{
double x, y;
} Point;

-

Dạng 3: Truyền bằng tham chiếu, có độ dài thay đổi
Loại này, tất cả dữ liệu phải bắt đầu một trường có độ dài 4 bytes, và được lưu trữ trên
vùng nhớ cùng kiểu dữ liệu, cùng độ dài. Kích thước dữ liệu bao gồm tổng độ dài của cấu
trúc (bao gồm cả kích thước của chính nó). Ví dụ:
typedef struct
{
int4 length;
char data[1];
} text;

text *destination;

Các kiểu dữ liệu tương ứng giữa SQL và C được xây dựng sẵn trong PostgreSQL có thể
tham khảo trong Table 31-1. Equivalent C Types for Built-In SQL Types trong tài liệu
PostgreSQL 8.0.0 Documentation.
I.5. Gọi hàm viết bằng ngôn ngữ C
I.5.1. Version 0 - Không sử dụng macro PG_FUNCTION_INFO_V1()

Như đã trình bày ở trên, cách này không còn thích hợp, nhưng nó dễ điều khiển trong khởi
tạo. Các tham số vào và kết quả của hàm C được khai báo ở dạng C bình thường, nhưng phải cẩn
thận trong việc sử dụng C để biểu diễn các kiểu dữ liệu SQL.
Mặc dù các này sử dụng đơn giản, nhưng nó không khả chuyển. Có một số vấn đề trong
việc truyền các kiểu dữ liệu có kích thước nhỏ hơn kiểu int ở một số kiến trúc. Và cũng không
đơn giản khi trả về một kết quả null hoặc nhận các tham số null.
Dưới đây là một ví dụ thực hiện thêm một hàm mở rộng C vào PostgreSQL theo cách này:
funcs.c
#include "postgres.h"
#include <string.h>
/* by value */
int add_one(int arg)
{
return arg + 1;

5


}
/* by reference, fixed length */
float8 * add_one_float8(float8 *arg)
{

float8 *result = (float8 *) palloc(sizeof(float8));
*result = *arg + 1.0;
return result;
}
/* by reference, variable length */
text * copytext(text *t)
{
/*
* VARSIZE is the total size of the struct in bytes.
*/
text *new_t = (text *) palloc(VARSIZE(t));
VARATT_SIZEP(new_t) = VARSIZE(t);
memcpy((void *) VARDATA(new_t), (void *) VARDATA(t), VARSIZE(t)-VARHDRSZ);
return new_t;
}

Bảng 1: Chương trình viết bằng C
File Funcs.c được biên dịch thành một đối tượng chia sẽ với tên là funcs.so, sau đó được đưa vào
PostgreSQL như sau:
CREATE FUNCTION add_one(integer) RETURNS integer
AS ’DIRECTORY/funcs’, ’add_one’
LANGUAGE C STRICT;
CREATE FUNCTION add_one(double precision) RETURNS double precision
AS ’DIRECTORY/funcs’, ’add_one_float8’
LANGUAGE C STRICT;
CREATE FUNCTION copytext(text) RETURNS text
AS ’DIRECTORY/funcs’, ’copytext’
LANGUAGE C STRICT;

Bảng 2: Lệnh SQL tạo các extender

Ở đây DIRECTORY là chỉ tên đường dẫn đến thư mục dùng chung. Có thể bỏ phần mở
rộng .so. Chú ý là phải xác định hàm là STRICT, có nghĩa là hệ thống sẽ giả định kết quả null
một cách tự động nếu bất kỳ một giá trị đầu vào nào là null. Bằng cách này, chúng ta không cần
phải kiểm tra các giá trị đầu vào null trong chương trình; ngược lại, chúng ta phải kiểm tra với các
đối số truyền bằng tham chiếu (với các đối số truyền bằng giá trị thì không có cách nào kiểm tra
cả)

6


I.5.2. Version 1 - Sử dụng macro PG_FUNCTION_INFO_V1()

Cách này sử dụng các macro để hạn chế sự phức tạp của việc truyền các đối số và trả về
kết quả. Chương trình C ở cách này cấu trúc file mã nguồn như sau:
PG_FUNCTION_INFO_V1(func_name);
Datum func_name(PG_FUNCTION_ARGS)
{
}
Hai lệnh này phải xuất trong cùng 1 file mã và nguồn và PG_FUNCTION_INFO_V1()
quy ước được viết trước hàm của nó. Macro này không cần thiết cho hàm của các hàm bên trong,
vì PostgreSQL thừa nhận rằng tất cả các hàm đều sử dụng quy ước của phiên bản 1. Tuy nhiên,
nó cần cho các hàm nạp được tải động (dynamically-loaded functions).
Trong phiên bản này, đối số được truyền vào bằng cách sử dụng macro
PG_GETARG_xxx(), kết quả trả về bằng cách sử dụng macro PG_RETURN_xxx().
PG_GETARG_xxx() có đối số là chỉ số của đối số hàm truyền vào, bắt đầu từ 0; ví dụ
PG_RETURN_INT32(0) trả về đối số đầu tiên với kiểu là int32. PG_RETURN_xxx() có đối
số là giá trị thực sự để trả về; ví dụ PG_RETURN_FLOAT4(0.5)trả về một số thực có giá trị là
0.5.
Dưới đây là một ví dụ thực hiện thêm một hàm mở rộng C vào PostgreSQL theo cách dùng
macro:

Functs.c
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
/* by value */
PG_FUNCTION_INFO_V1(add_one);
Datum add_one(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
PG_RETURN_INT32(arg + 1);
}
/* b reference, fixed length */
PG_FUNCTION_INFO_V1(add_one_float8);
Datum add_one_float8(PG_FUNCTION_ARGS)
{
float8 arg = PG_GETARG_FLOAT8(0);
PG_RETURN_FLOAT8(arg + 1.0);
}
/* by reference, variable length */
PG_FUNCTION_INFO_V1(copytext);
Datum copytext(PG_FUNCTION_ARGS)

7


{
text *t = PG_GETARG_TEXT_P(0);
text *new_t = (text *) palloc(VARSIZE(t));
VARATT_SIZEP(new_t) = VARSIZE(t);
memcpy((void *) VARDATA(new_t), void *) VARDATA(t), VARSIZE(t)-VARHDRSZ);

PG_RETURN_TEXT_P(new_t);
}

Lệnh CREATE FUNCTION tương tự như version 0.
Cách này có một số cải tiến so với version 0, như:
-

Với ví dụ ở trên, có thể thấy mã nguồn sử dụng các macro để lấy đối số, trả dữ liệu trả
về… Việc sử dụng các macro có lợi là các macro có thể ẩn đi các chi tiết không cần thiết.
Ví dụ như, trong hàm add_one_float8 ở trên, ta không cần quan tâm float8 là một loại
truyền bằng tham chiếu.

-

Kiểm soát được các giá trị vào và ra là null. Macro PG_ARGISNULL(n) cho phép kiểm
tra giá trị vào là null hay không. Để trả về giá trị null, ta dùng PG_RETURN_NULL();
macro này có thể làm việc trong cả chế độ strick và không strick.

-

Có hai giao diện khác nhau của macro PG_GETARG_xxx(): Đầu tiên là
PG_GETARG_xxx_COPY(), macro này đảm bảo để trả về một bản sao của đối số mà bị
hạn chế việc ghi đè dữ liệu. Các macro bình thường đôi khi trả về một con trỏ tham chiếu
đến một giá trị được lưu ở trong một bảng, mà giá trị đó không được ghi đè. Thứ hai là
macro PG_GETARG_xxx_SLICE() với 3 đối số đầu vào, chỉ số của đối số hàm (giống với
PG_GETARG_xxx()), offset (được tính từ 0) và độ dài của đoạn được trả về.

Với những cải tiến này, cách dùng này cho phép tính di động cao hơn, vì nó không phá vỡ
những giới hạn trong giao thức gọi hàm của C chuẩn; cho phép trả về các kiểu dữ liệu phức tạp,
như tập hợp; thực thi các trigger và điều khiển gọi ngôn ngữ thủ tục (procedural-language call

handler).
I.6. Quy tắc lập trình và biên dịch hàm viết bằng ngôn ngữ C
I.6.1. Quy tắc lập trình

Dưới đây là một số quy tắc lập trình cho việc viết và biên dich hàm C:
-

Sử dụng pg_config --includedir-server để tìm thư mục chứa các file header (.h) của
PostgreSQL đã được cài vào máy.

-

Sử dung palloc và pfree cho việc cấp phát và giải phóng bộ nhớ thay vì sử dụng các hàm
tương ứng trong thư viện C là malloc và free.
8


-

Luôn xóa trắng các struct bằng cách sử dụng memset.

-

Phải include ít nhất 2 file là postgres.h (chứa hầu hết các kiểu dữ liệu) và fmgr.h (chứa các
giao diện quản lý hàm). Tốt nhất đặt postgres.h trước các file header khác.

-

Tên symbol được định nghĩa trong file đối tượng không được trùng với các symbol khác
cũng như các symbol được định nghĩa trong PostgreSQL.


-

Việc biên dịch và liên kết mã nguồn để nó có thể được tải động vào trong PostgreSQL
luôn cần các cờ đặc biệt, như -shared, -fpic...

-

Để chắc chắn không tải các file được tải động vào một server không tương thích, cần phải
thêm một ‘magic block’, cần thêm đoạn mã sau vào file mã nguồn:
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
Cái này giúp server phát hiện các lý do không tương thích, ví dụ như, mã được biên dịch
cho phiên bản PostgreSQL khác với phiên bản của server…

I.6.2. Biên dịch

Trước khi đưa các hàm mở rộng viết bằng C vào trong PostgreSQL, cần phải biên dịch và
liên kết để tạo ra file có thể được tải động bởi server. Chính xác là một thư viên được chia sẻ cần
được tạo ra: đầu tiên, các file mã nguồn được biên dịch thành các file đối tượng, sau đó, các file
đối tượng này được liên kết với nhau. Các file đối tượng cần được tạo với mã độc lập vị trí
(position-independent code - PIC), có nghĩa là chúng có thể được đặt ở một ví trý tùy ý ở trong bộ
nhớ khi chúng được tải vào.
Để biên dịch các hàm C, ta cần một bộ biên dịch C, gcc hoặc cc. Các cờ biên dịch và phần
mở rộng của đối tượng được chia sẽ có thể khác nhau, phụ thuộc vào hệ điều hành đang dùng; ví
dụ .so với các hệ điều hành Linux, .dll với hệ điều hành Window...
Dưới đây là một số bước biên dịch ở một số hệ điều hành, giả thiết file mã nguồn là foo.c,
file đối tượng được tạo ra là foo.o:
- BSD/OS

gcc -fpic -c foo.c
ld -shared -o foo.so foo.o
- FreeBSD
gcc -fpic -c foo.c
9


gcc -shared -o foo.so foo.o
- Linux
cc -fpic -c foo.c
cc -shared -o foo.so foo.o
- Solaris
gcc -fpic -c foo.c
gcc -G -o foo.so foo.o
- Window với bộ biên dịch C là CygWin
gcc –c foo.c
gcc –shared foo.dll foo.o

Kết luận
Với tính mở rộng của PostgreSQL, người dùng có thể đưa vào các hàm người dùng định
nghĩa bằng nhiều cách khác nhau. Báo cáo này đi sâu vào trình bày việc sử dụng hàm ngôn ngữ
C, trong đó trinh bày các cách, các quy tắc cơ bản để thực thi và mở rộng các hàm này vào hệ
quản trị CSDL PostgreSQL.
Từ các đặc điểm của hai cách đưa hàm ngôn mở rộng bằng ngôn ngữ C, có thể thấy, cách
dùng thứ 2 (dùng macro) cung cấp nhiều cải tiến cho người dùng. Cách này cũng có tính linh
động cao, mà không phá vỡ các quy tắc của ngôn ngữ C chuẩn; cho phép trả về các kiểu dữ liệu
phức tạp; thực thi các trigger.
Báo cáo này không trình bày chi tiết về việc truyển vào các đối số và trả các dữ liệu phức
tạp, như phức hợp, tập hợp, đa hình… Chi tiết về các phần này có thể tham khảo trong tài liệu
PostgreSQL 8.0.0 Documentation.


10


TÀI LIỆU THAM KHẢO
1. PostgreSQL 8.0.0 Documentation
2. />3. />
11



×