Một lời cảnh báo :kiểu này với SQL động trong SECURITY DEFINER
chức năng có thể được trang nhã và thuận tiện. Nhưng đừng lạm dụng nó. Không lồng nhiều cấp chức năng theo cách này:
- Kiểu này dễ bị lỗi hơn nhiều so với SQL thuần túy.
- Chuyển đổi ngữ cảnh với
SECURITY DEFINER
có một thẻ giá. - SQL động với
EXECUTE
không thể lưu và sử dụng lại các kế hoạch truy vấn. - Không có "hàm nội tuyến".
- Và tôi không muốn sử dụng nó cho các truy vấn lớn trên các bảng lớn. Sự tinh vi được thêm vào có thể là một rào cản về hiệu suất. Như:tính năng song song bị tắt đối với các kế hoạch truy vấn theo cách này.
Điều đó nói rằng, chức năng của bạn có vẻ tốt, tôi không thấy cách nào để chèn SQL. định dạng () được chứng minh là tốt để nối và trích dẫn các giá trị và số nhận dạng cho SQL động. Ngược lại, bạn có thể loại bỏ một số phần thừa để làm cho nó rẻ hơn.
Tham số hàm offset__i
và limit__i
là integer
. Việc chèn SQL là không thể thông qua các số nguyên, thực sự không cần phải trích dẫn chúng (mặc dù SQL cho phép các hằng số chuỗi được trích dẫn cho LIMIT
và OFFSET
). Vì vậy, chỉ:
format(' OFFSET %s LIMIT %s', offset__i, limit__i)
Ngoài ra, sau khi xác minh rằng mỗi key__v
nằm trong số các tên cột hợp pháp của bạn - và mặc dù đó là tất cả các tên cột hợp pháp, không được trích dẫn - không cần phải chạy nó qua %I
. Chỉ có thể là %s
Tôi muốn sử dụng text
thay vì varchar
. Không phải là vấn đề lớn, nhưng text
là loại chuỗi "ưa thích".
Có liên quan:
- Định dạng chỉ định cho các biến số nguyên ở định dạng () cho EXECUTE?
- Hàm trả về tập hợp cột động cho bảng nhất định
COST 1
có vẻ quá thấp. Hướng dẫn sử dụng:
Trừ khi bạn biết rõ hơn, hãy để lại COST
ở 100
mặc định .
Hoạt động dựa trên một tập hợp duy nhất thay vì tất cả các vòng lặp
Toàn bộ vòng lặp có thể được thay thế bằng một SELECT
duy nhất bản tường trình. Sẽ nhanh hơn đáng kể. Các nhiệm vụ tương đối đắt trong PL / pgSQL. Như thế này:
CREATE OR REPLACE FUNCTION goods__list_json (_options json, _limit int = NULL, _offset int = NULL, OUT _result jsonb)
RETURNS jsonb
LANGUAGE plpgsql SECURITY DEFINER AS
$func$
DECLARE
_tbl CONSTANT text := 'public.goods_full';
_cols CONSTANT text[] := '{id, id__category, category, name, barcode, price, stock, sale, purchase}';
_oper CONSTANT text[] := '{<, >, <=, >=, =, <>, LIKE, "NOT LIKE", ILIKE, "NOT ILIKE", BETWEEN, "NOT BETWEEN"}';
_sql text;
BEGIN
SELECT concat('SELECT jsonb_agg(t) FROM ('
, 'SELECT ' || string_agg(t.col, ', ' ORDER BY ord) FILTER (WHERE t.arr->>0 = 'true')
-- ORDER BY to preserve order of objects in input
, ' FROM ' || _tbl
, ' WHERE ' || string_agg (
CASE WHEN (t.arr->>1)::int BETWEEN 1 AND 10 THEN
format('%s %s %L' , t.col, _oper[(arr->>1)::int], t.arr->>2)
WHEN (t.arr->>1)::int BETWEEN 11 AND 12 THEN
format('%s %s %L AND %L', t.col, _oper[(arr->>1)::int], t.arr->>2, t.arr->>3)
-- ELSE NULL -- = default - or raise exception for illegal operator index?
END
, ' AND ' ORDER BY ord) -- ORDER BY only cosmetic
, ' OFFSET ' || _offset -- SQLi-safe, no quotes required
, ' LIMIT ' || _limit -- SQLi-safe, no quotes required
, ') t'
)
FROM json_each(_options) WITH ORDINALITY t(col, arr, ord)
WHERE t.col = ANY(_cols) -- only allowed column names - or raise exception for illegal column?
INTO _sql;
IF _sql IS NULL THEN
RAISE EXCEPTION 'Invalid input resulted in empty SQL string! Input: %', _options;
END IF;
RAISE NOTICE 'SQL: %', _sql;
EXECUTE _sql INTO _result;
END
$func$;
db <> fiddle tại đây
Ngắn hơn, nhanh hơn và vẫn an toàn trước SQLi.
Dấu ngoặc kép chỉ được thêm vào khi cần thiết cho cú pháp hoặc để bảo vệ khỏi việc đưa vào SQL. Ghi xuống chỉ để lọc các giá trị. Tên cột và toán tử được xác minh dựa trên danh sách cố định các tùy chọn được phép.
Đầu vào là json
thay vì jsonb
. Thứ tự của các đối tượng được giữ nguyên trong json
, vì vậy bạn có thể xác định trình tự của các cột trong SELECT
danh sách (có ý nghĩa) và WHERE
điều kiện (hoàn toàn là mỹ phẩm). Chức năng quan sát cả hai ngay bây giờ.
Đầu ra _result
vẫn là jsonb
. Sử dụng OUT
tham số thay vì biến. Đó là hoàn toàn tùy chọn, chỉ để thuận tiện. (Không có RETURN
rõ ràng yêu cầu tuyên bố.)
Lưu ý việc sử dụng chiến lược concat()
để im lặng bỏ qua NULL và toán tử nối ||
để NULL làm cho chuỗi nối là NULL. Bằng cách này, FROM
, WHERE
, LIMIT
và OFFSET
chỉ được chèn khi cần thiết. A SELECT
câu lệnh hoạt động mà không có một trong hai điều đó. SELECT
trống danh sách (cũng hợp pháp, nhưng tôi cho là không mong muốn) dẫn đến lỗi cú pháp. Tất cả đều có mục đích.
Sử dụng format()
chỉ dành cho WHERE
bộ lọc, để thuận tiện và báo giá trị. Xem:
Hàm không phải là STRICT
nữa không. _limit
và _offset
có giá trị mặc định NULL
, vì vậy chỉ tham số đầu tiên _options
bắt buộc. _limit
và _offset
có thể là NULL hoặc bị bỏ qua, sau đó mỗi thứ sẽ bị loại bỏ khỏi câu lệnh.
Sử dụng text
thay vì varchar
.
Thực sự tạo các biến không đổi CONSTANT
(chủ yếu để làm tài liệu).
Ngoài ra, chức năng thực hiện những gì bản gốc của bạn làm.