MathJax

MathJax-2

MathJax-3

Google Code Prettify

置頂入手筆記

EnterproseDB Quickstart — 快速入門筆記

由於考慮採用 EnterpriseDB 或是直接用 PostgreSQL 的人,通常需要一些入手的資料。這邊紀錄便提供相關快速上手的簡單筆記 ~ 這篇筆記以 資料庫安裝完畢後的快速使用 為目標,基本紀錄登入使用的範例:

2017年8月7日 星期一

用 Postgres 的 Procedure Language 把指令查詢結果匯出到文字檔裡面,以及 Security Label 的使用

有時候資料或查詢結果需要落檔:在 psql 裡面,大家都可以用 \copy 這個 Meta-Command 把查詢結果匯出(但好像不多人使用 psql 就是了),或是手動用 GUI 界面(pgAdmin 或是 Libreoffice Base 或是其他 . . .),把資料用手拉出來。
那如果要用 SQL + Procedural Language 程式的話呢?

PostgreSQL 有提供 COPY 指令,但是 COPY 只能給 Superuser 執行:COPY 指令涉及資料庫「外部」的操作(寫檔案),Postgres 基於安全考量,只允許資料庫的 Superuser,以資料庫程序的執行者對資料庫外面操作(OS 中的相關檔案操作權限也以這個執行帳戶的能耐來操作)。

要是把 COPY 寫在函數裡面「也只能」用 Superuser 執行這個函數嗎?預設行為的確會禁止一般帳戶使用這樣的函數,執行到這裡就出錯了:
CREATE OR REPLACE FUNCTION enterprisedb.w2file (query text, filepath text)
  RETURNS void
AS $$
BEGIN
 EXECUTE format('COPY ( %s ) TO %L CSV;'
                , query, filepath);
END;
$$ LANGUAGE plpgsql;

GRANT USAGE ON SCHEMA enterprisedb TO some_user;
GRANT EXECUTE ON FUNCTION enterprisedb.w2file(text, text) TO some_user;
edb=> \c
You are now connected to database "edb" as user "some_user".
edb=> select enterprisedb.w2file('select * from dept','/tmp/file1.csv');
ERROR:  must be superuser to COPY to or from a file
HINT:  Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone.
CONTEXT:  SQL statement "COPY ( select * from dept ) TO '/tmp/file1.csv' CSV;"
PL/pgSQL function enterprisedb.w2file(text,text) line 3 at EXECUTE

(注意)

  1. 函數所在的 Schema,要能夠被使用才行
  2. 上面的 format() 的控制字元(%s,%I,%L,%%)的用法,可以參考這裡
  3. 這裡的提示 \copy 是 psql 的 Meta-Command,無法用在 PL/pgSQL 裡面


但是!在 Postgres 的函數,有執行權限修飾字,叫做 Security Label,可以處理這個問題。Security Label 分為 Security definer / Security Invoker 兩種:

  • SECURITY DEFINER 指的是,在這個函數被呼叫之時,該函數執行期間的相關權限,等同於這個函數的擁有者在執行一樣
  • SECURITY INVOKER 則是,在這個函數被呼叫之時,執行期間的相關權限完全以目前使用該函數的帳戶。預設是這個(很重要所以標紅字!)


注意這不要和函數物件本身的 Onwership / Execute 權限搞混:

Ownership / Execute 權限指的是一個函數物件本身,能不能夠被某個帳戶所調整 / 呼叫;沒權限的帳戶呼叫就會發生 Permission Denied,碰都碰不得。

而 Security Definer/Invoker 則是函數在執行期間的事情(這時已經被 Grant 相關使用權限了)。例如這篇的範例,被 Superuser 建立、內含 Copy 指令的自訂函數;就算某個普通帳戶有權限呼叫這個自訂函數,執行到 Copy 指令時,也沒辦法繼續下去,就會中途出錯。


因此,確切的用法是,先使用 superuser 建立包含 copy 的函數,並增加 SECURITY DEFINER 修飾字。然後再 grant execute 給要執行的帳戶
CREATE OR REPLACE FUNCTION enterprisedb.w2file (query text, filepath text)
  RETURNS void
AS $$
BEGIN
 EXECUTE format('COPY ( %s ) TO %L CSV;'
                , query, filepath);
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER;

GRANT USAGE ON SCHEMA enterprisedb TO some_user;
GRANT EXECUTE ON FUNCTION enterprisedb.w2file(text, text) TO some_user;
edb=> \c
You are now connected to database "edb" as user "some_user".
edb=> select enterprisedb.w2file('select * from dept', '/tmp/file2.csv');
 w2file_sql 
------------
 
(1 row)
edb=> \q
[enterprisedb@edbvm ~]$ cat /tmp/file2.csv 
10,ACCOUNTING,NEW YORK
20,RESEARCH,DALLAS
30,SALES,CHICAGO
40,OPERATIONS,BOSTON

這樣就成功了。



另外,其實還可以用 PL/Python(U) 來處理:如果可以賦予建立 PL/Python 函數的權限的話,就可以產生這種危險的(?)函數。實際賦予會出錯(這裡我用 EnterpriseDB 的 PostgreSQL 發行版,安裝 LanguagePack 套件,裡面附上的是 Python3)
edb=# CREATE EXTENSION plpython3u;
edb=# CREATE LANGUAGE plpython3u;
edb=# grant USAGE on LANGUAGE plpython3u TO user1;
ERROR:  language "plpython3u" is not trusted
DETAIL:  GRANT and REVOKE are not allowed on untrusted languages, because only superusers can use untrusted languages.

所以 Untrusted PL/Python 的 Stored Procedure 還是需要 Superuser 把程式佈上去才行~

在處理之前要注意一下:PL/Python 執行完的 SQL 回傳值會放在 List of Dictionaries 裡面,每一筆資料列都以 Python Dictionary 的 Key-Value 方式呈現,並使用 List 把多筆資料裝起來,因此匯出資料需要用迴圈寫入檔案。
-- 使用 Superuser 佈上去
CREATE OR REPLACE FUNCTION w2file_py (query text, filepath text)
  RETURNS TEXT
AS $$
 import os
 if not os.path.exists(filepath):
  outputfile = open(filepath, 'w')
 else:
  return 'FIle exist! No Output Result.'   # End
 rv = plpy.execute(query)
 for item in rv:
  outputfile.write("%s\n" % item)
 return 'Query Result Exported.'           # End
$$ LANGUAGE plpython3u;

GRANT USAGE ON SCHEMA enterprisedb TO some_user;
GRANT EXECUTE ON FUNCTION enterprisedb.w2file_py(text, text) TO some_user;
edb=> \c
You are now connected to database "edb" as user "some_user".
edb=> select w2file_py ('select * from emp limit 2', '/tmp/output.txt'); 
      w2file_py        
------------------------
 Query Result Exported.
(1 row)

edb=> \! cat /tmp/output.txt 
{'deptno': Decimal('20'), 'sal': Decimal('1250.00'), 'empno': Decimal('7369'), 'mgr': Decimal('7902'), 'comm': None, 'job': 'CLERK', 'ename': 'SMITH', 'hiredate': '17-DEC-80 00:00:00'}
{'deptno': Decimal('30'), 'sal': Decimal('2500.00'), 'empno': Decimal('7499'), 'mgr': Decimal('7698'), 'comm': Decimal('300.00'), 'job': 'SALESMAN', 'ename': 'ALLEN', 'hiredate': '20-FEB-81 00:00:00'}

要處理成 CSV 的話要調一下程式:使用 csv 模組的 writerows() 一次處理 List of dictionaries,記得要提供欄位名稱資訊(每個 Dict 的 Key 都長的一樣,只要用第零組的 key 作代表就好,也就是 rv[0].keys()),如果不要欄位名稱,就把 writeheader() 拿掉就好
CREATE OR REPLACE FUNCTION w2file_py (query text, filepath text)
  RETURNS TEXT
AS $$
 import os
 import csv

 if not os.path.exists(filepath):
   rv = plpy.execute(query)
   with open(filepath, 'w') as outputfile:
     csvw = csv.DictWriter(outputfile, rv[0].keys() )
     csvw.writeheader()
     csvw.writerows(rv)
   return 'Query Result Exported.'           # End
 else:
   return 'FIle exist! No Output Result.'      # End
 
$$ LANGUAGE plpython3u;

GRANT USAGE ON SCHEMA enterprisedb TO some_user;
GRANT EXECUTE ON FUNCTION enterprisedb.w2file_py(text, text) TO some_user;
edb=> \c
You are now connected to database "edb" as user "user1".
edb=> select enterprisedb.w2file_py ('select * from emp limit 2', '/tmp/outcsvpy.txt');
       w2file_py        
------------------------
 Query Result Exported.
(1 row)

edb=> \! cat /tmp/outcsvpy.txt
empno,mgr,deptno,sal,job,comm,ename,hiredate
7369,7902,20,1250.00,CLERK,,SMITH,17-DEC-80 00:00:00
7499,7698,30,2500.00,SALESMAN,300.00,ALLEN,20-FEB-81 00:00:00

注意看一下,使用 PL/Python3U 竟然不用設定 Security Definer!我們可以看到,由於 PL/Python 在 Postgres 裡面其實是在資料庫之外分支一個 Python 程序來執行 Script,因此 Postgres 無法檢查 Python 裡面做了什麼,不會有執行期間執行權限的問題。但這也表示 PL/Python 的使用要小心處理安全性的問題才行~


要使用 Security Label 控制函數的安全性,還是使用 PL/Python 這類程式語言外掛的函數,端看撰寫方便與否來決定。最根本的還是避免一般帳戶執行一些行為不夠安全的函數。

函數行為的安全性,便取決於個別函數的實際撰寫內容決定;只能建議盡量不要用太過萬能的函數比較好,最好可以限制匯出格式或是寫死能處理的表格。像這裡的範例在使用安全性上都是不太好的示範~


參考資料

使用 COPY 指令的部份



PL/Python 部份

沒有留言:

張貼留言