Có một bận, tôi ôm trong tay một bộ CSDL của website nọ, với nhiều thông tin danh tính người thật. Để tránh cho dữ liệu danh tính bị lộ, hưởng ứng tinh thần của Luật An Ninh Mạng, tôi quyết định phải làm xáo trộn dữ liệu đó để nó không còn phản ánh danh tính thật nữa. Cụ thể là tôi sẽ ghi thêm vài kí tự bừa bãi vào cột email, cho nó thành email "xạo" hết.
Nói tới nhu cầu này thì cách dễ nhất là viết đoạn code cho nó chạy một vòng lặp, lặp qua các dòng của bảng dữ liệu, tại mỗi dòng lấy ra cột email, ghi nội dung mới vào rồi lưu lại. Cách đó dễ, nhưng hơi cơ bắp, không tinh tế, sẽ chậm khi bảng dữ liệu hơi lớn. Tôi quyết định thử phương án tạo hàm tùy thêm cho hệ CSDL đó, để có thể sửa tất cả trong một câu truy vấn (query) duy nhất, ví dụ:
UPDATE web_users SET email = my_func(email)
Dùng qua cơ sở dữ liệu, các bạn chắc cũng biết trong câu truy vấn, thỉnh thoảng ta bắt gặp lời gọi hàm. Ví dụ phổ biến nhất là hàm COUNT
, ví dụ:
SELECT COUNT(*) FROM student WHERE country = 'VN'
Ngoài những hàm có sẵn như thế, các hệ CSDL vẫn cho phép ta định nghĩa thêm hàm tùy ý, và viết theo cú pháp phương ngữ SQL của hệ CSDL đó, ví dụ trong MySQL:
DELIMITER $$
CREATE FUNCTION CustomerLevel(
credit DECIMAL(10,2)
)
RETURNS VARCHAR(20)
DETERMINISTIC
BEGIN
DECLARE customerLevel VARCHAR(20);
IF credit > 50000 THEN
SET customerLevel = 'PLATINUM';
ELSEIF (credit >= 50000 AND
credit <= 10000) THEN
SET customerLevel = 'GOLD';
ELSEIF credit < 10000 THEN
SET customerLevel = 'SILVER';
END IF;
-- return the customer level
RETURN (customerLevel);
END$$
DELIMITER ;
Hơi dài dòng và khó đọc, đúng không, nếu thế thì "tinh tế" qué gì? Thực ra, muốn tinh tế thì nên dùng PostgreSQL, vì PostgreSQL cho phép bạn định nghĩa hàm tùy tạo bằng ngôn ngữ Python luôn (thông qua tính năng PL/Python). Không chỉ là Python ở mặt cú pháp mà còn được import các thư viện Python hẳn hoi! Chúng ta cùng bắt đầu nhé.
Đụng vào tầng database là một việc hơi nguy hiểm, nên ta cần cẩn thận. Trước tiên, hãy cứ viết ra như một đoạn code Python thuần (file *.py), test tiếc cho cẩn thận rồi hãy chuyển vào trong PostgreSQL. Về ý tưởng xáo trộn email thì thuật toán như sau:
- Đầu tiên, tách địa chỉ email làm hai phần, trước và sau dấu "@". Ví dụ "ng.hong.quan@gmail.com" thì tách thành "ng.hong.quan" và "gmail.com".
- Phần tên miền không cần đụng đến. Tôi sẽ thêm kí tự ngẫu nhiên vào phần tên đăng nhập (username). Thêm một kí tự vào đằng trước, một kí tự vào cuối và một kí tự chen vào giữa, vị trí ngẫu nhiên. Ví dụ, thêm "z" vào trước, "x" vào sau, thành "zng.hong.quanz", rồi thêm "y" vào giữa, thành "zng.hyong.quanx".
Bây giờ viết ra thành file tools.py:
# No need to handle the case that "@" is missing, because
# email address was validated before.
, =
# No need to handle the case that email is empty.
# Pick 3 random letters
, , =
# Choose a random middle position
=
# Put together
return f
Trong bộ dữ liệu của tôi, cột email là bắt buộc, và dữ liệu được kiểm tra tính hợp lệ trước khi ghi vào, nên hàm scramble_email
không cần kiểm tra những trường hợp lắt léo như thiếu dấu "@", chuỗi rỗng.
Đôi lời giải thích nếu bạn đọc là người non kinh nghiệm về Python:
-
email.split('@')
sẽ ngắt thành hai chuỗi con, ví dụ'quan'
và'agriconnect.vn'
. Ta gán hai chuỗi con này cho hai biếnhead
vàtail
. -
string.ascii_lowercase + string.digits
cho ra một chuỗi gồm từ'a'
đến'z'
và từ'0'
đến'9'
. -
random.sample(string.ascii_lowercase + string.digits, 3)
là từ chuỗi trên, bốc ra ba kí tự ngẫu nhiên. Sau khi bốc ra, ta gán cho ba biếnc1
,c2
,c3
. -
Khi lấy vị trí giữa ngẫu nhiên, ta chỉ lấy vị trí từ thứ hai (chỉ số 1) đến kế cuối (chỉ số
len(head) - 2
). Ta phải lường tình huống chuỗi chỉ có một kí tự, khi đó sẽ lấy vị trí cuối luôn. Đó là lí do có thêmmax(1, ...)
vào để "thòng". -
Sau khi có vị trí ngăn giữa
i
rồi thì ta dùng cú pháp "slice" để cắthead
ra thành hai khúc:head[:i]
vàhead[i:]
Mở console của Python lên, import và cho chạy thử với vài dữ liệu:
Trông ổn rồi, giờ thì ta mở database lên, cho phép nó sử dụng ngôn ngữ Python:
Lưu ý rằng, mặc dù extension plpython
là extension gốc, được hỗ trợ chính thức từ PostgreSQL nhưng trong các bản phân phối Linux (Ubuntu, Fedora), người bảo trì lại tách nó ra gói riêng. Ta cần cài gói đó trước, ví dụ trong Ubuntu là:
Sau đó cấu hình cho PostgreSQL luôn nạp nó lên, bằng cách sửa file /etc/postgresql/12/main/postgresql.conf, tìm dòng "shared_preload_libraries" và thêm "plpython3" vào, ví dụ:
Nhớ khởi động lại PostgreSQL sau khi sửa cấu hình:
Quay lại bước vừa nãy, sau khi chạy câu "CREATE EXTENSION..." thì ta bắt đầu định nghĩa hàm, chép code Python vừa nãy vào:
CREATE FUNCTION scramble_email(email text)
RETURNS text
AS $$
import string
import random
head, tail = email.split('@')
c1, c2, c3 = random.sample(string.ascii_lowercase + string.digits, 3)
# Choose a random middle position
i = random.randint(1, max(1, len(head) - 2))
return f'{c1}{head[:i]}{c2}{head[i:]}{c3}@{tail}'
$$ LANGUAGE plpython3u;
Áp dụng nó vào câu query UPDATE, thi triển võ công "đấm một phát chết luôn":
Trong trường hợp bạn không muốn xào nấu địa chỉ email của một số người dùng, ví dụ của nhân viên công ty, ta có thể lọc với WHERE
:
UPDATE kaon_user SET email = scramble_email(email) WHERE email NOT LIKE '%agriconnect.vn%';
Vậy là nhiệm vụ đã hoàn thành. Ta thấy PostgreSQL và Python quả là đôi bạn thân, hỗ trợ nhau rất tốt (ngay cả các hệ CSDL "đại gia" như MS SQL, Oracle còn không có tính năng này). Các tấm hình trên còn cho thấy ngay cả code Python trong PostgreSQL (PL/Python) cũng được tô màu theo đúng cú pháp. Thực ra việc tô màu có được là nhờ một công cụ trong hệ sinh thái Python: pgcli. Tuy nhiên, có một lưu ý nhỏ: Khi sử dụng PL/Python, chỉ superuser mới có quyền tạo hàm. Đây là giới hạn PostgreSQL đặt ra vì Python không có cơ chế tự giới hạn quyền, khiến code PL/Python có thể làm những trò quá mạnh bạo, nguy cơ trở thành "phá hoại" nếu rơi vào tay "gà mờ".
-- Cập nhật ---
Theo dõi bước chân xa hơn trong việc cải thiện tốc độ hàm xử lý này: Viết hàm thêm cho PostgreSQL: Chú voi bay.