Postgresql

為什麼 colname 作為函式的輸入參數 = 不是好主意?

  • May 19, 2017

有人告訴我,使用文本輸入作為列名並對其進行格式化,就像我在下面所做的那樣,很少是一個好主意。然而,當我問為什麼時,並沒有給出答案。那是在 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;

他還說用executewithusing代替,那麼這有什麼區別:

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,但我的印像是,由於我在後端和數據庫之間來回做了很多事情(發送一些東西,等待響應,計算一些東西,再次發送..)我應該使用函式讓所有事情一下子發生。

這裡有兩個問題,

  1. EXECUTE .. USING和有什麼區別EXECUTE FORMAT()
  2. 為什麼在過程函式中包裝或生成簡單的 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 積極確保不選擇通用計劃會很有幫助。

所以你在這裡有一些論據。

  1. 你可以同時使用EXECUTE FORMAT() ... USING
  2. USING允許記憶體計劃。
  3. USING允許符號保留符號並阻止它們必須轉換為文本並重新轉義。
  4. USING不能與標識符一起使用,只能與文字一起使用。

在過程函式中包裝和生成簡單的 SQL 語句是一個壞主意。

至於問題的另一部分,

所以我想知道為什麼不建議這樣做?(在 plpgsql 中包裝簡單的 SQL 語句。)

有很多原因,

  • 這樣的功能模糊了計劃者的成本(複數),並要求使用者明確

    1. 設置執行成本(或使用相當愚蠢的估計)
    2. 9.6+ 確定它是否是並行安全安全/受限/不安全
    3. 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 。

引用自:https://dba.stackexchange.com/questions/173862