PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Seberapa amankah format() untuk kueri dinamis di dalam suatu fungsi?

Peringatan :gaya ini dengan SQL dinamis di SECURITY DEFINER fungsi bisa elegan dan nyaman. Tapi jangan berlebihan menggunakannya. Jangan menumpuk beberapa level fungsi dengan cara ini:

  • Gayanya jauh lebih rawan kesalahan daripada SQL biasa.
  • Pengalih konteks dengan SECURITY DEFINER memiliki label harga.
  • SQL Dinamis dengan EXECUTE tidak dapat menyimpan dan menggunakan kembali paket kueri.
  • Tidak ada "fungsi inlining".
  • Dan saya lebih suka tidak menggunakannya untuk kueri besar di tabel besar sama sekali. Kecanggihan tambahan dapat menjadi penghalang kinerja. Seperti:paralelisme dinonaktifkan untuk rencana kueri dengan cara ini.

Yang mengatakan, fungsi Anda terlihat bagus, saya tidak melihat cara untuk injeksi SQL. format() terbukti baik untuk menggabungkan dan mengutip nilai dan pengidentifikasi untuk SQL dinamis. Sebaliknya, Anda mungkin menghapus beberapa redundansi untuk membuatnya lebih murah.

Parameter fungsi offset__i dan limit__i adalah integer . Injeksi SQL tidak mungkin dilakukan melalui bilangan bulat, sebenarnya tidak perlu mengutipnya (walaupun SQL mengizinkan konstanta string yang dikutip untuk LIMIT dan OFFSET ). Jadi cukup:

format(' OFFSET %s LIMIT %s', offset__i, limit__i)

Juga, setelah memverifikasi bahwa setiap key__v ada di antara nama kolom resmi Anda - dan meskipun itu semua adalah nama kolom resmi yang tidak dikutip - tidak perlu menjalankannya melalui %I . Bisa saja %s

Saya lebih suka menggunakan text bukannya varchar . Bukan masalah besar, tapi text adalah tipe string "pilihan".

Terkait:

COST 1 tampaknya terlalu rendah. Manual:

Kecuali Anda tahu lebih baik, tinggalkan COST di default 100 .

Operasi berbasis set tunggal alih-alih semua perulangan

Seluruh perulangan dapat diganti dengan satu SELECT penyataan. Harus terasa lebih cepat. Tugas relatif mahal di PL/pgSQL. Seperti ini:

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 di sini

Lebih pendek, lebih cepat, dan tetap aman terhadap SQLi.

Kutipan hanya ditambahkan jika diperlukan untuk sintaks atau untuk mempertahankan injeksi SQL. Membakar ke nilai filter saja. Nama kolom dan operator diverifikasi berdasarkan daftar bawaan opsi yang diizinkan.

Masukannya adalah json bukannya jsonb . Urutan objek dipertahankan dalam json , sehingga Anda dapat menentukan urutan kolom di SELECT list (yang bermakna) dan WHERE kondisi (yang murni kosmetik). Fungsi mengamati keduanya sekarang.

Keluaran _result masih jsonb . Menggunakan OUT parameter, bukan variabel. Itu benar-benar opsional, hanya untuk kenyamanan. (Tidak ada RETURN explicit eksplisit pernyataan yang diperlukan.)

Perhatikan penggunaan strategis concat() untuk secara diam-diam mengabaikan NULL dan operator gabungan || sehingga NULL membuat string yang digabungkan menjadi NULL. Dengan cara ini, FROM , WHERE , LIMIT , dan OFFSET hanya dimasukkan jika diperlukan. Sebuah SELECT pernyataan berfungsi tanpa salah satu dari itu. SELECT empty kosong list (juga legal, tapi saya kira tidak diinginkan) menghasilkan kesalahan sintaks. Semua dimaksudkan.
Menggunakan format() hanya untuk WHERE filter, untuk kenyamanan dan untuk mengutip nilai. Lihat:

Fungsinya bukan STRICT lagi. _limit dan _offset memiliki nilai default NULL , jadi hanya parameter pertama _options Dibutuhkan. _limit dan _offset dapat NULL atau dihilangkan, maka masing-masing dilucuti dari pernyataan.

Menggunakan text bukannya varchar .

Membuat variabel konstan sebenarnya CONSTANT (kebanyakan untuk dokumentasi).

Selain itu, fungsinya melakukan apa yang asli Anda lakukan.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara menambahkan Kolom baru di tabel setelah kolom ke-2 atau ke-3 di Tabel menggunakan postgres?

  2. Permintaan garis bujur PostgreSQL

  3. Contoh pernyataan INSERT yang disiapkan menggunakan permata ruby ​​pg

  4. Bagaimana Fungsi Ln() Bekerja di PostgreSQL

  5. Perl - DBI dan .pgpass