跳轉至

Provisa pgwire 伺服器

Provisa 公開一個 PostgreSQL 網絡協定(pgwire)端點。任何支援 PostgreSQL 客戶端協定的工具——psycopg2、asyncpg、DBeaver、Tableau、JDBC——都可以連接並透過與 HTTP API 相同的治理管線查詢 Provisa 數據。(REQ-266

查詢會經過完整的治理堆疊:行級安全執行、遮罩規則、關係防護、域訪問檢查。(REQ-001REQ-002REQ-263)pgwire 介面並非繞過機制。(REQ-002REQ-266


連接詳情

PROVISA_PGWIRE_PORT 設定為非零整數時,伺服器便會啟動。預設為停用狀態。(REQ-527)[tool-verified: app.py:1739]

Host: 0.0.0.0  (all interfaces)
Port: $PROVISA_PGWIRE_PORT

TLS。PROVISA_PGWIRE_CERTPROVISA_PGWIRE_KEY 設定為 PEM 證書及金鑰的路徑。兩者皆存在時,伺服器會將傳入連接以 TLS 包裝。兩者皆缺席時,TLS 為關閉狀態,伺服器會對 SSL 協商請求回覆 N。(REQ-530)[tool-verified: server.py:1746-1750]

回報的伺服器版本。 客戶端看到的版本為 14.0.provisa。根據版本號啟用功能的工具,其行為可能如同連接至 PostgreSQL 14。(REQ-579)[tool-verified: server.py:208]


身份驗證

啟動封包會攜帶一個用戶名和一個密文欄位,但沒有任何方案說明該密文是什麼。Provisa 會根據密文本身判斷,因此客戶端除了 userpassword 之外無需任何額外設定:

密文是 辨識依據 解析為
個人存取權杖 provisa_pat_ 前綴 該權杖的擁有者及其角色(REQ-1263
OIDC/提供者簽發的 bearer 權杖 所設定的提供者是權杖提供者 該權杖所聲明的身分(REQ-890
密碼 其他任何情況 所設定提供者(basicsimple)中的帳戶

判斷只做一次。被選定驗證器拒絕的憑證不會再交給另一個驗證器重試,因此一次拒絕不會變成第二次猜測。

trust 模式(provider: none,或驗證中介軟件未啟用)是例外:用戶名會直接用作 role_id,密文會被忽略。請勿在未加密的連接上使用它。

SCRAM-SHA-256。provider: basicauth.scram: true 時,伺服器會公告帶 SCRAM-SHA-256 的 SASL(驗證代碼 10),密碼以證明方式驗證而非傳送。(REQ-1394)不提供 SCRAM-SHA-256-PLUS。對於尚未寫入驗證子的用戶——驗證子無法從 bcrypt 雜湊推導——伺服器會以模擬交換作答,使連線上看不出誰已遷移;該用戶會透過 TLS 上的明文密碼進行驗證,直到下次輸入密碼時寫入驗證子為止。關閉 auth.scram 時,伺服器使用 PG 驗證類型 3(明文密碼)。兩種情況下都不支援 MD5。

客戶端憑證。 設定 PROVISA_MTLS_CLIENT_CA 後,伺服器會在交握期間、檢查任何憑證之前驗證客戶端憑證。(REQ-1228)啟用 PROVISA_MTLS_BIND_PRINCIPAL 時,憑證的 common name 必須與該連接隨後用於驗證的 user 相同。參見設定

失敗嘗試會被計數。 五分鐘內五次失敗會將帳戶鎖定十五分鐘,且該計數器與 HTTP 和 Bolt 共用——在任一介面上招致的鎖定,在所有介面上都生效。(REQ-1393

選擇組織。 在多組織部署中,連接至 <org>.<您的網域>,pgwire 會從 TLS ClientHello 的主機名稱中讀取組織,方式與 HTTP 從 Host 標頭中讀取相同。(REQ-1234)主機名稱是在請求某個組織,而不是授予它;在該組織中沒有成員資格的主體會被拒絕。透過 IP 位址連接則不請求任何組織。


支援的功能

SELECT

所有 SELECT 陳述式都會經過治理管線(_pipeline.py)。(REQ-001REQ-262REQ-266)此管線會:

  1. 將語意 SQL 重寫為實體 SQL(rewrite_semantic_to_physical
  2. 套用治理(行級安全、遮罩、域訪問)(REQ-263
  3. 對照已註冊的結構描述 (Schema) 進行驗證(REQ-011
  4. 路由至 Trino 或直接路由至來源池(REQ-027REQ-028

支援多陳述式的簡單查詢。以分號分隔的陳述式會被拆分並按順序執行。(REQ-580)[tool-verified: server.py:318-381]

在簡單查詢模式及擴展查詢(Bind/Execute)模式下均支援參數化查詢($1$2……)。參數會在執行前以字面值替換。(REQ-581)[tool-verified: server.py:78-85]

SELECT * FROM fn(args)SELECT fn(args)——其中 fn 指定一個已註冊且受追蹤的函式——會在治理管線之前被攔截,並透過唯一受治理的執行器(invoke_tracked_function)路由。結果為一個類型化的行集,與該指令在其他任何介面所返回的結果一致。writable_by 及治理規則會在執行器內強制執行。(REQ-1156)[tool-verified: provisa/pgwire/function_call.py:74-88]

DDL

DDL 陳述式由 server.py 中的正則表達式偵測,並派送至 DdlHandler。角色必須具備 "ddl" 權限。(REQ-042)若無此權限,該陳述式會以 SQLSTATE 42501 被拒絕。[tool-verified: ddl_handler.py:82-83]

可識別的 DDL 形式為:

CREATE TABLE / VIEW / INDEX / UNIQUE INDEX / SEQUENCE / SCHEMA
ALTER TABLE / INDEX / SEQUENCE / VIEW
DROP TABLE / VIEW / INDEX / SEQUENCE / SCHEMA

[tool-verified: server.py:56-61]

根據 ddl_catalog 存在兩條執行路徑:(REQ-582

Trino 路徑——當 ddl_catalog 為 Iceberg、Hive 或其他未註冊的 Trino 目錄(例如 iceberghiveotelresults)時使用。此路徑僅支援 CREATE TABLECREATE VIEW。嘗試執行 ALTERDROPCREATE INDEX 會引發錯誤。資料表名稱會完整限定為 catalog.schema.table。[tool-verified: ddl_handler.py:92-100]

直接路徑——當 ddl_catalog 對應至已註冊的來源 ID 時使用。支援完整 DDL:CREATE、ALTER、DROP、索引、序列。CREATE TABLECREATE VIEW 會以結構描述限定為 schema.table。其餘 DDL(ALTER、DROP、CREATE INDEX)在設定結構描述上下文後會原樣傳遞。對於 PostgreSQL 及 SQLite 來源,上下文以 SET search_path TO schema 設定。對於 MySQL 及 MariaDB,則以 USE schema 設定。[tool-verified: ddl_handler.py:139-170, ddl_handler.py:207-213]

在任一路徑執行 DDL 後,新資料表會註冊至該角色的編譯上下文中,以便立即可供查詢。(REQ-583)[tool-verified: ddl_handler.py:216-250]

寫入目標的解析。 DDL 目錄及結構描述來自該域的 ddl_catalogddl_schema 欄位。若未設定 ddl_catalog,系統預設使用 Iceberg 目錄。若未設定 ddl_schema,則預設使用域 ID。域會透過角色的 domain_access 清單解析。(REQ-584)[tool-verified: app.py:804-811, ddl_handler.py:104-115]

COPY

COPY ... TO STDOUTCOPY ... FROM STDIN 均獲支援。(REQ-585)[tool-verified: copy_handler.py:231-257]

COPY TO STDOUT——以 PG COPY 網絡格式匯出查詢結果。有兩種形式可用:

-- Table reference
COPY my_table TO STDOUT WITH (FORMAT csv)

-- Arbitrary query
COPY (SELECT col1, col2 FROM my_table WHERE ...) TO STDOUT WITH (FORMAT text)

支援的格式:text(以定位字符分隔,預設)及 csv。COPY 輸出不支援二進制格式。[tool-verified: copy_handler.py:36-52]

COPY FROM STDIN——將行插入目標資料表。僅限類型為 postgresqlmysqlsqlitemariadb 的來源使用。(REQ-586)嘗試對僅限 Trino 的來源(例如 Iceberg)執行 COPY FROM 會引發權限錯誤。[tool-verified: copy_handler.py:65, copy_handler.py:351-356]

COPY my_table (col1, col2) FROM STDIN WITH (FORMAT text)

若未提供欄位清單,欄位會從已註冊的結構描述推斷得出。[tool-verified: copy_handler.py:357]

交易與工作階段命令

SET、BEGIN、COMMIT、ROLLBACK、SAVEPOINT、RELEASE、DISCARD、RESET 及 DEALLOCATE 會被攔截並返回空的成功回應。(REQ-587)伺服器就交易而言屬於無狀態——不存在交易隔離或回滾支援。(REQ-587)[tool-verified: catalog.py:27-31, catalog.py:1129-1132]


目錄攔截

information_schemapg_catalog 的查詢會在本地解答,無需往返 Trino。(REQ-532)攔截層會按每個請求建構一個記憶體內的 DuckDB 數據庫,並以該角色的編譯上下文填充數據。(REQ-532)[tool-verified: catalog.py:210-213]

被攔截的資料表:

information_schema: schematatablescolumnsviewstable_constraintskey_column_usagereferential_constraints

pg_catalog: pg_namespacepg_classpg_attributepg_typepg_attrdefpg_descriptionpg_indexpg_constraintpg_procpg_rolespg_auth_memberspg_databasepg_settingspg_tablespg_stat_user_tablespg_statio_user_tablespg_ampg_extensionpg_enumpg_stat_activity

[tool-verified: catalog.py:39-67]

pg_constraint 會以從域模型的 pk_columnsjoins 欄位衍生的真實主索引鍵及外部索引鍵數據填充。(REQ-392REQ-399)檢查外部索引鍵關係的 BI 工具(Tableau、DBeaver 等)將看到 Provisa 所知的連接圖。[tool-verified: catalog.py:551-632] 同一來源/目標配對之間的單欄位連接,若其目標欄位共同組成該目標的複合主索引鍵,會被合併為單一 FK 行,並以多元素的 conkey/confkey 陣列表示。(REQ-1094)[tool-verified: catalog_constraints.py]

pg_index 會就每個主索引鍵及 UNIQUE 約束填充一行(indrelid = 資料表 oid,indkey = 已排序的鍵值 attnum,indisprimary/indisunique 已設定)。透過 pg_index.indkey 而非 pg_constraint 解析鍵值欄位的客戶端——例如 DataGrip——會透過標準的 pg_indexpg_attribute 連接找出正確的欄位。(REQ-1095)[tool-verified: catalog_constraints.py:340-384]

以下純量表達式亦會被攔截:(REQ-588

  • current_usersession_user → 已驗證的 role_id
  • current_database()"provisa"
  • current_schema()"public"
  • version()"PostgreSQL 14.0 on Provisa"
  • pg_backend_pid()0
  • current_setting(...) → 從固定的設定表返回數值
  • SHOW <setting> → 從同一設定表返回數值

[tool-verified: catalog.py:168-207, catalog.py:1076-1120]


二進制參數編碼

擴展查詢協定(Bind/Execute)支援以二進制編碼的參數。(REQ-589)以下類型 OID 會從二進制解碼:[tool-verified: postgres.py:69-97]

OID PG 類型 Python 類型
16 bool bool
17 bytea bytes
20 int8 int
21 int2 int
23 int4 int
25 text str
700 float4 float
701 float8 float
1043 varchar str
1082 date datetime.date
1114 timestamp datetime.datetime
1184 timestamptz datetime.datetime (UTC)
1700 numeric decimal.Decimal
2950 uuid str

不在此表中的任何 OID 都會引發 "Unsupported binary parameter type: <oid>"。(REQ-589)[tool-verified: postgres.py:579]

當客戶端要求時,結果欄位亦會以二進制傳送,適用於相同類型集加上 ARRAY、JSON、INTERVAL 及 BIGINT。(REQ-589)[tool-verified: postgres.py:191-244]


驅動程式建議

原生 Python 驅動程式(psycopg2、asyncpg)。 這些驅動程式預設會協商擴展查詢協定,並對大多數類型使用二進制編碼。類型保真度在此最高——NUMERIC 欄位以 Decimal 形式送達,TIMESTAMP 則以 datetime 形式送達,如此類推。適用於以 Python 為基礎的 ETL、腳本或直接整合。

JDBC(PostgreSQL JDBC 驅動程式)。 適用於 Java 生態系統工具:DBeaver、Tableau、Power BI、Metabase、Airflow 的 JDBC 運算子。JDBC 預設使用簡單查詢協定,可避免二進制編碼帶來的複雜情況。連接字串:

jdbc:postgresql://<host>:<PROVISA_PGWIRE_PORT>/provisa?user=<role_id>&password=<password>

部分基於 JDBC 的 BI 工具在連接時,會傳送一連串針對 information_schemapg_catalog 的查詢,以填充其結構描述瀏覽器。這些查詢全部由目錄攔截層解答——在結構描述檢查期間不會產生任何 Trino 流量。(REQ-532

應何時選用哪一種。 若客戶端為 Python,請使用 psycopg2 或 asyncpg 以獲得更佳的類型處理。若客戶端為 BI 工具或任何 JVM 應用程式,請使用 JDBC。若觀察到類型轉換方面的異常情況,應避免在同一連接中混用二進制及文字協定的預期行為——JDBC 的文字模式行為更易於推斷。


注意事項與限制

僅限 SQL;不支援 DML 變更操作。 pgwire 監聽器僅解析並執行 SQL——不接受 GraphQL 及 Cypher 字串。(REQ-614)純粹的 INSERTUPDATEDELETE 不會路由至寫入路徑。(REQ-615)請透過 COPY FROM STDIN(可寫入的來源)或 CREATE TABLE AS 寫入數據;行級變更則應改為透過 GraphQL、Cypher 或 Trino 的寫入路徑處理。

COPY 及 DDL 需要 ddl 權限。 COPY(不論方向)及 DDL 均受角色的 ddl 權限所限制;未具備此權限的角色會收到 SQLSTATE 42501。(REQ-616

不支援真正的交易功能。 BEGIN/COMMIT/ROLLBACK 會被接受並靜默忽略。每個陳述式均獨立執行。(REQ-587)[tool-verified: server.py:146-158——in_transaction() 一律返回 False]

DDL 逾時 60 秒,查詢逾時 120 秒。 這些數值於處理程序執行緒中硬編碼。(REQ-590)針對遠端來源的長時間執行 DDL(大型資料表的結構描述變更)可能會逾時。[tool-verified: ddl_handler.py:136, server.py:186]

COPY FROM 僅適用於可寫入的來源。 Iceberg、Hive、僅限 Trino 的來源,以及唯讀的來源類型均不接受 COPY FROM。錯誤代碼為 SQLSTATE 42501。(REQ-586)[tool-verified: copy_handler.py:65]

COPY 輸出格式為 text 或 csv。 尚未實作 PG 二進制 COPY 格式(FORMAT binary)。[inferred:_rows_to_copy_text / _rows_to_copy_csv 中僅存在 textcsv 分支]

Trino 路徑上的 DDL 僅限 CREATE。 不支援針對 Iceberg 或 Hive 目錄執行 ALTER、DROP 及 CREATE INDEX。如需完整 DDL,請以已註冊的 SQL 來源作為 ddl_catalog。(REQ-582)[tool-verified: ddl_handler.py:92-100]

參數替換屬字面值形式。 $1$2……等參數會在執行前以 SQL 字面值替換,而非以綁定參數形式傳送至底層引擎。這意味著底層引擎永遠不會看到已準備的陳述式。對 Trino 而言此舉沒有實際影響;對於直接連接池的來源,則會繞過已準備陳述式的快取機制。(REQ-581)[tool-verified: server.py:78-85]

pg_stat_activitypg_stat_user_tablespg_extensionpg_enumpg_attrdefpg_proc 這些資料表存在於目錄層中,但屬於空的存根 (stub)。查詢它們的監控工具將收到零行結果,而非錯誤。(REQ-532)[tool-verified: catalog.py:519-535, catalog.py:639-934](pg_index 已有數據填充——請參閱「目錄攔截」一節。)