為什麼 colname 作為函式的輸入參數 = 不是好主意?
有人告訴我,使用文本輸入作為列名並對其進行格式化,就像我在下面所做的那樣,很少是一個好主意。然而,當我問為什麼時,並沒有給出答案。那是在 postgresql IRC 上,那些傢伙似乎知道他們的東西。所以我想知道為什麼不建議這樣做?我主要想知道它是否為 sql 注入打開了大門。
create or replace function getItemsOrderBy(order_by_p text) RETURNS TABLE (id int) AS $$ BEGIN return query EXECUTE format(' SELECT id FROM items ORDER BY %s', order_by_p) ; END;
他還說用
execute
withusing
代替,那麼這有什麼區別:return query EXECUTE format(' SELECT id FROM items ORDER BY %s', order_by_p) ;
和這個 :
return query EXECUTE ' SELECT id FROM items ORDER BY $1' USING order_by_p ;
我的功能比上面的更複雜——格式部分只是其中的一部分。我可以選擇創建一個可以處理多種情況(用於訂購)的函式,或者創建一堆來處理每個訂購。我覺得只做一個更實用。根本沒有功能不是一種選擇。
我實際上正在使用
pg-promise
,但我的印像是,由於我在後端和數據庫之間來回做了很多事情(發送一些東西,等待響應,計算一些東西,再次發送..)我應該使用函式讓所有事情一下子發生。
這裡有兩個問題,
EXECUTE .. USING
和有什麼區別EXECUTE FORMAT()
- 為什麼在過程函式中包裝或生成簡單的 SQL 語句是個壞主意?
EXECUTE .. USING
和之間的區別EXECUTE FORMAT()
從文件中,
命令字元串可以使用參數值,在命令中引用為 $ 1, $ 2 等。這些符號是指在 USING 子句中提供的值。這種方法通常比將數據值作為文本插入到命令字元串中更可取:**它避免了將值轉換為文本並返回的執行時成本,並且由於不需要引用或逃脫。**一個例子是:
EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2' INTO c USING checked_user, checked_date;
**請注意,參數符號只能用於數據值——如果要使用動態確定的表名或列名,則必須以文本形式將它們插入命令字元串。**例如,如果需要對動態選擇的表執行上述查詢,您可以這樣做:
EXECUTE 'SELECT count(*) FROM ' || quote_ident(tabname) || ' WHERE inserted_by = $1 AND inserted <= $2' INTO c USING checked_user, checked_date;
更簡潔的方法是對錶或列名使用 format() 的 %I 規範(由換行符分隔的字元串連接起來):
EXECUTE format('SELECT count(*) FROM %I ' 'WHERE inserted_by = $1 AND inserted <= $2', tabname) INTO c USING checked_user, checked_date;
一個帶有簡單常量命令字元串和一些 USING 參數的 EXECUTE,如上面的第一個範例,在功能上等同於直接在 PL/pgSQL 中編寫命令並允許自動替換 PL/pgSQL 變數。重要的區別是 EXECUTE 將在每次執行時重新規劃命令,生成特定於目前參數值的計劃;而**PL/pgSQL 可能會創建一個通用計劃並將其記憶體以供重用。**在最佳計劃強烈依賴於參數值的情況下,使用 EXECUTE 積極確保不選擇通用計劃會很有幫助。
所以你在這裡有一些論據。
- 你可以同時使用
EXECUTE FORMAT() ... USING
USING
允許記憶體計劃。USING
允許符號保留符號並阻止它們必須轉換為文本並重新轉義。USING
不能與標識符一起使用,只能與文字一起使用。在過程函式中包裝和生成簡單的 SQL 語句是一個壞主意。
至於問題的另一部分,
所以我想知道為什麼不建議這樣做?(在 plpgsql 中包裝簡單的 SQL 語句。)
有很多原因,
這樣的功能模糊了計劃者的成本(複數),並要求使用者明確
- 設置執行成本(或使用相當愚蠢的估計)
- 9.6+ 確定它是否是並行安全安全/受限/不安全
- 9.6+ 確定它是否有副作用 strict/immutable/volatile
他們模糊了使用者的內部結構。
它們防止謂詞下推。
它們使權限複雜化(現在您也需要訪問該功能)。
他們提高了維護的障礙,現在您必須定義函式返回的結果集
TABLE (id int)
他們提出了 ORM 的各種問題。
而且,它不是 SQL。您正在 DBMS 之上建構一種新語言。為什麼?
至於動態組件,還有其他方法可以解決這個問題。以提供的確切陳述為例,最壞的情況是你看到的地方
SELECT * FROM getItemsOrderBy($col);
您必須明確寫出 order-by。儘管它很糟糕,但我認為這是一個更好的解決方案。
SELECT id FROM items ORDER BY col1 SELECT id FROM items ORDER BY col2 SELECT id FROM items ORDER BY col3
更進一步的步驟是使用為生成提供某種幫助的庫,例如
pg-promise
let args = { orderBy: "col1" }; if ( args.orderBy !== 'col1' ) { throw new Error "invalid orderBy Column"; } db.manyOrNone( 'SELECT id FROM items ORDER BY ${orderBy~}', args );
或者,DBIx::Abstract或像DBIx::Class這樣的 ORM 。