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 ti
Trang 1Đ I H C QU C GIA HÀ N I ẠI HỌC QUỐC GIA HÀ NỘI ỌC QUỐC GIA HÀ NỘI ỐC GIA HÀ NỘI ỘI
Đ I H C CÔNG NGH ẠI HỌC QUỐC GIA HÀ NỘI ỌC QUỐC GIA HÀ NỘI Ệ
BÁO CÁO
B SUNG HÀM M R NG VI T B NG C VÀO H QU N Ổ SUNG HÀM MỞ RỘNG VIẾT BẰNG C VÀO HỆ QUẢN Ở RỘNG VIẾT BẰNG C VÀO HỆ QUẢN ỘI ẾT BẰNG C VÀO HỆ QUẢN ẰNG C VÀO HỆ QUẢN Ệ ẢN
TR C S D LI U POSTGRESQL Ị CƠ SỞ DỮ LIỆU POSTGRESQL Ơ SỞ DỮ LIỆU POSTGRESQL Ở RỘNG VIẾT BẰNG C VÀO HỆ QUẢN Ữ LIỆU POSTGRESQL Ệ
Học viên: Bùi Hoàng Khánh
Lớp K16T3
Hà Nội, 03/2010
Trang 2M C L C ỤC LỤC ỤC LỤC
MỤC LỤC 2
I Tính mở rộng của PostgreSQL 3
I.1 Tính mở rộng của PosgreSQL 3
I.2 Hàm người dùng định nghĩa 3
II Hàm viết bằng ngôn ngữ C 3
II.1 Quá trình tải động 4
II.2 Các kiểu dữ liệu cơ bản trong hàm ngôn ngữ C 4
II.3 Gọi hàm viết bằng ngôn ngữ C 5
II.3.1 Version 0 - Không sử dụng macro PG_FUNCTION_INFO_V1() 5
II.3.2 Version 1 - Sử dụng macro PG_FUNCTION_INFO_V1() 7
II.4 Quy tắc lập trình và biên dịch hàm viết bằng ngôn ngữ C 9
II.4.1 Quy tắc lập trình 9
II.4.2 Biên dịch 10
III Kết luận 10
TÀI LIỆU THAM KHẢO 12
Trang 3I Tính m r ng c a PostgreSQL ở rộng của PostgreSQL ộng của PostgreSQL ủ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ả (pseudo-types), như các kiểu đa hình
II 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
Trang 4Cả hai cách này phải xác định tên ngôn ngữ ở CREATE FUNCTION trong là C.
II.1 Quá trình t i đ ng ải động ộng của PostgreSQL
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
II.2 Các ki u d li u c b n trong hàm ngôn ng C ểu dữ liệu cơ bản trong hàm ngôn ngữ C ữ liệu cơ bản trong hàm ngôn ngữ C ệu cơ bản trong hàm ngôn ngữ C ơ bản trong hàm ngôn ngữ C ải động ữ 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
Trang 5Độ 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;
- 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
II.3 G i hàm vi t b ng ngôn ng C ọi hàm viết bằng ngôn ngữ C ết bằng ngôn ngữ C ằng ngôn ngữ C ữ liệu cơ bản trong hàm ngôn ngữ C
II.3.1 Version 0 - Không s d ng macro ử dụng macro ụ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"
Trang 6#include <string.h>
/* by value */
int add_one(int arg)
{
return arg + 1;
}
/* 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
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
Trang 7phả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ả)
II.3.2 Version 1 - S d ng macro ử dụng macro ụ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
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);
Trang 8PG_RETURN_FLOAT8(arg + 1.0);
}
/* by reference, variable length */
PG_FUNCTION_INFO_V1(copytext);
Datum copytext(PG_FUNCTION_ARGS)
{
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)
II.4 Quy t c l p trình và biên d ch hàm vi t b ng ngôn ng C ắc lập trình và biên dịch hàm viết bằng ngôn ngữ C ập trình và biên dịch hàm viết bằng ngôn ngữ C ịch hàm viết bằng ngôn ngữ C ết bằng ngôn ngữ C ằng ngôn ngữ C ữ liệu cơ bản trong hàm ngôn ngữ C
II.4.1 Quy t c l p trình ắc lập trình và biên dịch hàm viết bằng ngôn ngữ C ập trình và biên dịch hàm viết bằng ngôn ngữ C
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:
Trang 9- 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.
- 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…
II.4.2 Biên d ch ịch hàm viết bằng ngôn ngữ C
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
Trang 10gcc -fpic -c foo.c
ld -shared -o foo.so foo.o
- FreeBSD
gcc -fpic -c foo.c
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
III K t lu n ết bằng ngôn ngữ C ập trình và biên dịch hàm viết bằng ngôn ngữ C
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.