الوصف
pg_clickhouse هو امتداد لـ PostgreSQL يتيح تنفيذ الاستعلامات عن بُعد على قواعد بيانات ClickHouse، بما في ذلك [مغلف البيانات الخارجية]. وهو يدعم PostgreSQL 13 والإصدارات الأحدث وClickHouse 23.3 والإصدارات الأحدث.
البدء
أبسط طريقة لتجربة pg_clickhouse هي استخدام [صورة Docker]، والتي تتضمن صورة Docker القياسية لـ PostgreSQL، إلى جانب امتدادي pg_clickhouse وre2:
docker run --name pg_clickhouse -e POSTGRES_PASSWORD=my_pass \
-d ghcr.io/clickhouse/pg_clickhouse:18
docker exec -it pg_clickhouse psql -U postgresراجع الدليل التعليمي لبدء استيراد جداول ClickHouse ودفع تنفيذ الاستعلامات إلى المصدر.
الاستخدام
CREATE EXTENSION pg_clickhouse;
CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');
CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
OPTIONS (user 'default');
CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA taxi FROM SERVER taxi_srv INTO taxi;سياسة الإصدارات
يلتزم pg_clickhouse بـ[الإصدار الدلالي] في إصداراته العامة.
- يُزاد الرقم الرئيسي للإصدار عند إجراء تغييرات على API
- يُزاد الرقم الثانوي للإصدار عند إجراء تغييرات SQL المتوافقة مع الإصدارات السابقة
- يُزاد رقم التصحيح عند إجراء تغييرات تقتصر على الملف التنفيذي
بعد التثبيت، يتتبّع PostgreSQL شكلين من أرقام الإصدار:
- إصدار المكتبة (المعرّف بواسطة
PG_MODULE_MAGICفي PostgreSQL 18 والإصدارات الأحدث) يتضمن الإصدار الدلالي الكامل، ويظهر في مخرجات الدالةpgch_version()أو دالة Postgrespg_get_loaded_modules(). - إصدار الامتداد (المعرّف في ملف التحكم) يتضمن فقط الرقمين الرئيسي
والثانوي، ويظهر في الجدول
pg_catalog.pg_extension، وفي مخرجات الدالةpg_available_extension_versions()، وفي\dx pg_clickhouse.
عمليًا، يعني ذلك أن الإصدار الذي يزيد رقم التصحيح، على سبيل المثال
من v0.1.0 إلى v0.1.1، يفيد جميع قواعد البيانات التي حمّلت v0.1، ولا
تحتاج إلى تشغيل ALTER EXTENSION للاستفادة من الترقية.
أما الإصدار الذي يزيد الرقم الثانوي أو الرئيسي، فسيكون
مصحوبًا بسكربتات ترقية SQL، ويجب على جميع قواعد البيانات الحالية التي تحتوي على
الامتداد تشغيل ALTER EXTENSION pg_clickhouse UPDATE للاستفادة من
الترقية.
مرجع DDL في SQL
تستخدم تعبيرات DDL التالية في SQL الامتداد pg_clickhouse.
CREATE EXTENSION
استخدم CREATE EXTENSION لإضافة pg_clickhouse إلى إحدى قواعد البيانات:
CREATE EXTENSION pg_clickhouse;استخدم WITH SCHEMA لتثبيته في مخطط محدد (يوصى به):
CREATE SCHEMA ch;
CREATE EXTENSION pg_clickhouse WITH SCHEMA ch;ALTER EXTENSION
استخدم ALTER EXTENSION لتعديل pg_clickhouse. أمثلة:
-
بعد تثبيت إصدار جديد من pg_clickhouse، استخدم العبارة
UPDATE:ALTER EXTENSION pg_clickhouse UPDATE; -
استخدم
SET SCHEMAلنقل الامتداد إلى مخطط جديد:CREATE SCHEMA ch; ALTER EXTENSION pg_clickhouse SET SCHEMA ch;
DROP EXTENSION
استخدم DROP EXTENSION لحذف pg_clickhouse من قاعدة بيانات:
DROP EXTENSION pg_clickhouse;يفشل هذا الأمر إذا كانت هناك أي كائنات تعتمد على pg_clickhouse. استخدم
عبارة CASCADE لحذفها أيضًا:
DROP EXTENSION pg_clickhouse CASCADE;CREATE SERVER
استخدم CREATE SERVER لإنشاء خادم خارجي يتصل بخادم ClickHouse. مثال:
CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');الخيارات المدعومة هي:
driver: مشغّل اتصال ClickHouse المراد استخدامه، إما "binary" أو "http". مطلوب.compression: ضغط البروتوكول الأصلي لمشغّل "binary"، ويكون إحدى القيم "none" أو "lz4" أو "zstd". القيمة الافتراضية هي "lz4". ويتجاهله مشغّل "http".dbname: قاعدة بيانات ClickHouse التي ستُستخدم عند الاتصال. القيمة الافتراضية هي "default".host: اسم مضيف خادم ClickHouse. القيمة الافتراضية هي "localhost";port: المنفذ المطلوب الاتصال به على خادم ClickHouse. تكون القيم الافتراضية كما يلي:- 9440 إذا كان
driverهو "binary" وكانhostمضيف ClickHouse Cloud - 9004 إذا كان
driverهو "binary" ولم يكنhostمضيف ClickHouse Cloud - 8443 إذا كان
driverهو "http" وكانhostمضيف ClickHouse Cloud - 8123 إذا كان
driverهو "http" ولم يكنhostمضيف ClickHouse Cloud
- 9440 إذا كان
min_tls_version: الحد الأدنى لإصدار بروتوكول TLS الذي يجب التفاوض عليه في الاتصالات التي تستخدم TLS. إحدى القيمTLSv1أوTLSv1.1أوTLSv1.2أوTLSv1.3. القيمة الافتراضية هي الحد الأدنى الذي تعتمده مكتبة TLS نفسها. وينطبق ذلك على كلا المشغّلين.secure: يحدّد استخدام TLS للاتصال. إحدى القيم التالية:auto(الافتراضي): استخدم TLS عندما يكونhostمضيف ClickHouse Cloud أو يكونportمنفذًا آمنًا؛ وإلا فاستخدم اتصالًا غير مشفّر.on(أوtrue/yes/1): استخدم TLS دائمًا. وتكون القيمة الافتراضية لـportهي 8443 ("http") أو 9440 ("binary").off(أوfalse/no/0): لا تستخدم TLS مطلقًا. وتكون القيمة الافتراضية لـportهي 8123 ("http") أو 9000 ("binary").
ALTER SERVER
استخدم ALTER SERVER لتغيير خادم خارجي. مثال:
ALTER SERVER taxi_srv OPTIONS (SET driver 'http');الخيارات هي نفسها المذكورة في CREATE SERVER.
DROP SERVER
استخدم DROP SERVER لإزالة خادم خارجي:
DROP SERVER taxi_srv;يفشل هذا الأمر إذا كانت هناك كائنات أخرى تعتمد على الخادم. استخدم CASCADE لكي
تُسقِط هذه التبعيات أيضًا:
DROP SERVER taxi_srv CASCADE;CREATE USER MAPPING
استخدم CREATE USER MAPPING لربط مستخدم في PostgreSQL بمستخدم في ClickHouse. على سبيل
المثال، لربط مستخدم PostgreSQL الحالي بمستخدم ClickHouse البعيد عند
الاتصال بـ خادم خارجي taxi_srv:
CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
OPTIONS (user 'demo');الخيارات المدعومة هي:
user: اسم مستخدم ClickHouse. والقيمة الافتراضية هي "default".password: كلمة مرور مستخدم ClickHouse.
ALTER USER MAPPING
استخدم ALTER USER MAPPING لتغيير تعريف تعيين المستخدم:
ALTER USER MAPPING FOR CURRENT_USER SERVER taxi_srv
OPTIONS (SET user 'default');الخيارات مماثلة لتلك الواردة في CREATE USER MAPPING.
DROP USER MAPPING
استخدم DROP USER MAPPING لحذف تعيين مستخدم:
DROP USER MAPPING FOR CURRENT_USER SERVER taxi_srv;IMPORT FOREIGN SCHEMA
استخدم IMPORT FOREIGN SCHEMA لاستيراد جميع الجداول المعرَّفة في قاعدة بيانات ClickHouse كجداول خارجية إلى مخطط PostgreSQL:
CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA demo FROM SERVER taxi_srv INTO taxi;استخدم LIMIT TO لحصر الاستيراد في جداول محددة:
IMPORT FOREIGN SCHEMA demo LIMIT TO (trips) FROM SERVER taxi_srv INTO taxi;استخدم EXCEPT لاستثناء الجداول:
IMPORT FOREIGN SCHEMA demo EXCEPT (users) FROM SERVER taxi_srv INTO taxi;سيقوم pg_clickhouse بجلب قائمة بجميع الجداول في قاعدة بيانات ClickHouse المحددة ('demo' في الأمثلة أعلاه)، ثم يجلب تعريفات الأعمدة لكل جدول، وينفّذ أوامر CREATE FOREIGN TABLE لإنشاء الجداول الخارجية. ستُعرَّف الأعمدة باستخدام أنواع البيانات المدعومة، وكذلك الخيارات التي يدعمها CREATE FOREIGN TABLE متى أمكن اكتشافها.
CREATE FOREIGN TABLE
استخدم CREATE FOREIGN TABLE لإنشاء جدول خارجي يتيح الاستعلام عن البيانات من قاعدة بيانات ClickHouse:
CREATE FOREIGN TABLE acts (
user_id bigint NOT NULL,
page_views int,
duration smallint,
sign smallint
) SERVER taxi_srv OPTIONS(
table_name 'acts'
engine 'CollapsingMergeTree(sign)'
);خيارات الجدول المدعومة هي:
database: اسم قاعدة البيانات البعيدة. تكون القيمة الافتراضية هي قاعدة البيانات المعرّفة للخادم الخارجي.table_name: اسم الجدول البعيد. تكون القيمة الافتراضية هي الاسم المحدد للجدول الخارجي.engine: [محرك الجدول] المستخدم في جدول ClickHouse. بالنسبة إلىCollapsingMergeTree()وAggregatingMergeTree()، يطبّق pg_clickhouse المعلمات تلقائيًا على تعبيرات الدوال التي تُنفَّذ على الجدول.
استخدم نوع البيانات المناسب لنوع بيانات ClickHouse البعيد لكل عمود. خيارات الأعمدة المدعومة هي:
-
column_name: اسم العمود على جهة ClickHouse، ويُستخدم بدلًا من اسم السمة في PostgreSQL عند إعادة توليد الاستعلامات و عمليات الإدراج. وهو مفيد لربط أسماء أعمدة PostgreSQL المكتوبة بأحرف صغيرة وغير الموضوعة بين علامتَي اقتباس بأعمدة ClickHouse الحساسة لحالة الأحرف، على سبيل المثال:CREATE FOREIGN TABLE hits ( watchid bigint OPTIONS(column_name 'WatchID'), javaenable smallint OPTIONS(column_name 'JavaEnable'), title text OPTIONS(column_name 'Title') ) SERVER taxi_srv OPTIONS(table_name 'hits'); -
AggregateFunction: اسم الدالة التجميعية المطبَّقة على عمود من [نوع AggregateFunction]. اربط نوع البيانات بنوع ClickHouse المُمرَّر إلى الدالة، وحدد اسم الدالة التجميعية عبر خيار العمود المناسب، وسيضيف pg_clickhouse تلقائيًاMergeإلى الدالة التجميعية التي تقيّم العمود.CREATE FOREIGN TABLE test ( column1 bigint OPTIONS(AggregateFunction 'uniq'), column2 integer OPTIONS(AggregateFunction 'anyIf'), column3 bigint OPTIONS(AggregateFunction 'quantiles(0.5, 0.9)') ) SERVER clickhouse_srv; -
SimpleAggregateFunction: اسم الدالة التجميعية المطبَّقة على عمود من [نوع SimpleAggregateFunction]. اربط نوع البيانات بنوع ClickHouse المُمرَّر إلى الدالة، وحدد اسم الدالة التجميعية عبر خيار العمود المناسب.
ALTER FOREIGN TABLE
استخدم ALTER FOREIGN TABLE لتغيير تعريف جدول خارجي:
ALTER TABLE table ALTER COLUMN b OPTIONS (SET AggregateFunction 'count');خيارات الجدول والعمود المدعومة هي نفسها الواردة في CREATE FOREIGN TABLE.
DROP FOREIGN TABLE
استخدم DROP FOREIGN TABLE لإزالة جدول خارجي:
DROP FOREIGN TABLE acts;يفشل هذا الأمر إذا وُجدت أي كائنات تعتمد على الجدول الخارجي.
استخدم عبارة CASCADE لحذفها أيضًا:
DROP FOREIGN TABLE acts CASCADE;مرجع SQL لـ DML
قد تستخدم تعبيرات SQL DML الواردة أدناه pg_clickhouse. وتعتمد الأمثلة على جداول ClickHouse التالية:
CREATE TABLE logs (
req_id Int64 NOT NULL,
start_at DateTime64(6, 'UTC') NOT NULL,
duration Int32 NOT NULL,
resource Text NOT NULL,
method Enum8('GET' = 1, 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH', 'QUERY') NOT NULL,
node_id Int64 NOT NULL,
response Int32 NOT NULL
) ENGINE = MergeTree
ORDER BY start_at;
CREATE TABLE nodes (
node_id Int64 NOT NULL,
name Text NOT NULL,
region Text NOT NULL,
arch Text NOT NULL,
os Text NOT NULL
) ENGINE = MergeTree
PRIMARY KEY node_id;EXPLAIN
يعمل الأمر EXPLAIN كما هو متوقع، لكن الخيار VERBOSE يؤدي إلى
إظهار استعلام ClickHouse "Remote SQL":
try=# EXPLAIN (VERBOSE)
SELECT resource, avg(duration) AS average_duration
FROM logs
GROUP BY resource;
QUERY PLAN
------------------------------------------------------------------------------------
Foreign Scan (cost=1.00..5.10 rows=1000 width=64)
Output: resource, (avg(duration))
Relations: Aggregate on (logs)
Remote SQL: SELECT resource, avg(duration) FROM "default".logs GROUP BY resource
(4 rows)يُنفَّذ هذا الاستعلام على ClickHouse مباشرةً عبر عقدة خطة باسم "Foreign Scan"، باستخدام SQL البعيد.
SELECT
استخدم عبارة SELECT لتنفيذ الاستعلامات على جداول pg_clickhouse، تمامًا كما تفعل مع أي جداول أخرى:
try=# SELECT start_at, duration, resource FROM logs WHERE req_id = 4117909262;
start_at | duration | resource
----------------------------+----------+----------------
2025-12-05 15:07:32.944188 | 175 | /widgets/totem
(1 row)يعمل pg_clickhouse على ترحيل تنفيذ الاستعلام إلى ClickHouse قدر الإمكان، بما في ذلك الدوال التجميعية. استخدم EXPLAIN لتحديد مدى هذا الترحيل. فعلى سبيل المثال، في الاستعلام أعلاه، يُرحَّل التنفيذ بالكامل إلى ClickHouse
try=# EXPLAIN (VERBOSE, COSTS OFF)
SELECT start_at, duration, resource FROM logs WHERE req_id = 4117909262;
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Foreign Scan on public.logs
Output: start_at, duration, resource
Remote SQL: SELECT start_at, duration, resource FROM "default".logs WHERE ((req_id = 4117909262))
(3 rows)يقوم pg_clickhouse أيضًا بتنفيذ عمليات JOIN على الجداول الموجودة على الخادم البعيد نفسه:
try=# EXPLAIN (ANALYZE, VERBOSE)
SELECT name, count(*), round(avg(duration))
FROM logs
LEFT JOIN nodes on logs.node_id = nodes.node_id
GROUP BY name;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan (cost=1.00..5.10 rows=1000 width=72) (actual time=3.201..3.221 rows=8.00 loops=1)
Output: nodes.name, (count(*)), (round(avg(logs.duration), 0))
Relations: Aggregate on ((logs) LEFT JOIN (nodes))
Remote SQL: SELECT r2.name, count(*), round(avg(r1.duration), 0) FROM "default".logs r1 ALL LEFT JOIN "default".nodes r2 ON (((r1.node_id = r2.node_id))) GROUP BY r2.name
FDW Time: 0.086 ms
Planning Time: 0.335 ms
Execution Time: 3.261 ms
(7 rows)سيؤدي الربط بجدول محلي إلى إنشاء استعلامات أقل كفاءة ما لم
يُجرَ ضبطه بعناية. في هذا المثال، ننشئ نسخة محلية من
الجدول nodes ونربط بها بدلًا من الجدول البعيد:
try=# CREATE TABLE local_nodes AS SELECT * FROM nodes;
SELECT 8
try=# EXPLAIN (ANALYZE, VERBOSE)
SELECT name, count(*), round(avg(duration))
FROM logs
LEFT JOIN local_nodes on logs.node_id = local_nodes.node_id
GROUP BY name;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=147.65..150.65 rows=200 width=72) (actual time=6.215..6.235 rows=8.00 loops=1)
Output: local_nodes.name, count(*), round(avg(logs.duration), 0)
Group Key: local_nodes.name
Batches: 1 Memory Usage: 32kB
Buffers: shared hit=1
-> Hash Left Join (cost=31.02..129.28 rows=2450 width=36) (actual time=2.202..5.125 rows=1000.00 loops=1)
Output: local_nodes.name, logs.duration
Hash Cond: (logs.node_id = local_nodes.node_id)
Buffers: shared hit=1
-> Foreign Scan on public.logs (cost=10.00..20.00 rows=1000 width=12) (actual time=2.089..3.779 rows=1000.00 loops=1)
Output: logs.req_id, logs.start_at, logs.duration, logs.resource, logs.method, logs.node_id, logs.response
Remote SQL: SELECT duration, node_id FROM "default".logs
FDW Time: 1.447 ms
-> Hash (cost=14.90..14.90 rows=490 width=40) (actual time=0.090..0.091 rows=8.00 loops=1)
Output: local_nodes.name, local_nodes.node_id
Buckets: 1024 Batches: 1 Memory Usage: 9kB
Buffers: shared hit=1
-> Seq Scan on public.local_nodes (cost=0.00..14.90 rows=490 width=40) (actual time=0.069..0.073 rows=8.00 loops=1)
Output: local_nodes.name, local_nodes.node_id
Buffers: shared hit=1
Planning:
Buffers: shared hit=14
Planning Time: 0.551 ms
Execution Time: 6.589 msفي هذه الحالة، يمكننا دفع مزيد من عمليات التجميع إلى ClickHouse عبر
التجميع حسب node_id بدلًا من العمود المحلي، ثم الربط
بجدول البحث لاحقًا:
try=# EXPLAIN (ANALYZE, VERBOSE)
WITH remote AS (
SELECT node_id, count(*), round(avg(duration))
FROM logs
GROUP BY node_id
)
SELECT name, remote.count, remote.round
FROM remote
JOIN local_nodes
ON remote.node_id = local_nodes.node_id
ORDER BY name;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
Sort (cost=65.68..66.91 rows=490 width=72) (actual time=4.480..4.484 rows=8.00 loops=1)
Output: local_nodes.name, remote.count, remote.round
Sort Key: local_nodes.name
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=4
-> Hash Join (cost=27.60..43.79 rows=490 width=72) (actual time=4.406..4.422 rows=8.00 loops=1)
Output: local_nodes.name, remote.count, remote.round
Inner Unique: true
Hash Cond: (local_nodes.node_id = remote.node_id)
Buffers: shared hit=1
-> Seq Scan on public.local_nodes (cost=0.00..14.90 rows=490 width=40) (actual time=0.010..0.016 rows=8.00 loops=1)
Output: local_nodes.node_id, local_nodes.name, local_nodes.region, local_nodes.arch, local_nodes.os
Buffers: shared hit=1
-> Hash (cost=15.10..15.10 rows=1000 width=48) (actual time=4.379..4.381 rows=8.00 loops=1)
Output: remote.count, remote.round, remote.node_id
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Subquery Scan on remote (cost=1.00..15.10 rows=1000 width=48) (actual time=4.337..4.360 rows=8.00 loops=1)
Output: remote.count, remote.round, remote.node_id
-> Foreign Scan (cost=1.00..5.10 rows=1000 width=48) (actual time=4.330..4.349 rows=8.00 loops=1)
Output: logs.node_id, (count(*)), (round(avg(logs.duration), 0))
Relations: Aggregate on (logs)
Remote SQL: SELECT node_id, count(*), round(avg(duration), 0) FROM "default".logs GROUP BY node_id
FDW Time: 0.055 ms
Planning:
Buffers: shared hit=5
Planning Time: 0.319 ms
Execution Time: 4.562 msتقوم عقدة "Foreign Scan" الآن بتمرير التجميع حسب node_id إلى النظام البعيد، مما يقلّل
عدد الصفوف التي يجب سحبها مرة أخرى إلى Postgres من 1000 (كلّها)
إلى 8 فقط، صف واحد لكل عقدة.
الجداول المُقسَّمة
يمكن أن يضم [جدول مُقسَّم] في PostgreSQL أقسامًا محلية وأخرى خارجية تستند إلى ClickHouse. ومن الأنماط الشائعة نقل البيانات الأقدم إلى ClickHouse، مع إبقاء البيانات الأحدث في PostgreSQL:
CREATE TABLE events (id int, ts date, val int, amt float8)
PARTITION BY RANGE (ts);
-- 2023 data lives on ClickHouse
CREATE FOREIGN TABLE events_2023 PARTITION OF events
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01')
SERVER ch_svr OPTIONS (table_name 'events');
-- 2024 data stays local
CREATE TABLE events_2024 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');للاطلاع على مثال يوضح كيفية نقل البيانات من التقسيمات المحلية إلى التقسيمات الخارجية، راجع offload-partition.sql.
تتطلب عمليات التجميع عبر التقسيمات المحلية والخارجية [التجميع حسب التقسيم]، وهو معطّل افتراضيًا في PostgreSQL:
SET enable_partitionwise_aggregate = on;عند تمكين enable_partitionwise_aggregate، ينفّذ PostgreSQL تجميعًا جزئيًا
أسفل Append، ثم تدمج عملية التجميع النهائي أعلاه هذه النتائج الجزئية في نتيجة واحدة.
ويدفع pg_clickhouse التجميع الجزئي للقسم الأجنبي إلى ClickHouse:
try=# EXPLAIN (VERBOSE, COSTS OFF)
SELECT count(*), sum(val), min(ts), max(ts) FROM events;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate
Output: count(*), sum(events.val), min(events.ts), max(events.ts)
-> Append
-> Foreign Scan
Output: (PARTIAL count(*)), (PARTIAL sum(events.val)), (PARTIAL min(events.ts)), (PARTIAL max(events.ts))
Relations: Aggregate on (events_2023 events)
Remote SQL: SELECT count(*), sum(val), min(ts), max(ts) FROM "default".events
-> Partial Aggregate
Output: PARTIAL count(*), PARTIAL sum(events_1.val), PARTIAL min(events_1.ts), PARTIAL max(events_1.ts)
-> Seq Scan on public.events_2024 events_1
Output: events_1.val, events_1.tsمتى يُدفَع التجميع الجزئي إلى المصدر
يمثّل PostgreSQL التجميع الجزئي بوصفه حالة انتقالية تجمعها خطوة الإنهاء عبر الأقسام. لا يستطيع pg_clickhouse دفع التجميع الجزئي لقسم ما إلى المصدر إلا إذا أمكن تمثيله كقيمة في ClickHouse:
- دوال التجميع القابلة للتفكيك التي تكون حالتها الانتقالية هي القيمة
النهائية بالفعل، تُدفَع مباشرةً إلى المصدر:
countوsumوminوmaxوbool_and/everyوbool_orوbit_andوbit_orوbit_xor. - تُدفَع
avgعلى الأعداد الصحيحة إلى المصدر بحالتها{count, sum}على شكل مصفوفة. - تُدفَع
avgوvar_popوvar_sampوstddev_popوstddev_sampعلى الأعداد ذات الفاصلة العائمة إلى المصدر بحالتها{N, sum, sum of squared deviations}على شكل مصفوفة.
يُدفَع FILTER (WHERE …) إلى المصدر مع دوال التجميع هذه.
الحالات التي تلجأ فيها إلى البديل
لا يتوفر للتجميعات التي تكون حالة انتقالها من نوع PostgreSQL المعتم internal
تمثيل قابل للنقل، لذا يجلب القسم الخارجي صفوفها ويجمعها محليًا بدلًا من ذلك.
يشمل ذلك كل ما يتعلق بـ numeric، إضافةً إلى avg(bigint) وavg(interval).
كما تلجأ تجميعات DISTINCT وتجميعات المجموعات المرتبة والتجميعات متغيرة الوسائط إلى البديل.
PREPARE, EXECUTE, DEALLOCATE
اعتبارًا من الإصدار v0.1.2، يدعم pg_clickhouse الاستعلامات المعلَّمة بمعلمات، ويُنشأ معظمها باستخدام الأمر PREPARE:
try=# PREPARE avg_durations_between_dates(date, date) AS
SELECT date(start_at), round(avg(duration)) AS average_duration
FROM logs
WHERE date(start_at) BETWEEN $1 AND $2
GROUP BY date(start_at)
ORDER BY date(start_at);
PREPAREاستخدم EXECUTE كالمعتاد لتنفيذ عبارة مُحضَّرة:
try=# EXECUTE avg_durations_between_dates('2025-12-09', '2025-12-13');
date | average_duration
------------+------------------
2025-12-09 | 190
2025-12-10 | 194
2025-12-11 | 197
2025-12-12 | 190
2025-12-13 | 195
(5 rows)يقوم pg_clickhouse، كالمعتاد، بتمرير عمليات التجميع إلى النظام البعيد، كما يظهر في مخرجات EXPLAIN التفصيلية:
try=# EXPLAIN (VERBOSE) EXECUTE avg_durations_between_dates('2025-12-09', '2025-12-13');
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan (cost=1.00..5.10 rows=1000 width=36)
Output: (date(start_at)), (round(avg(duration), 0))
Relations: Aggregate on (logs)
Remote SQL: SELECT date(start_at), round(avg(duration), 0) FROM "default".logs WHERE ((date(start_at) >= '2025-12-09')) AND ((date(start_at) <= '2025-12-13')) GROUP BY (date(start_at)) ORDER BY date(start_at) ASC NULLS LAST
(4 rows)لاحظ أنه أرسل قيم التاريخ الكاملة، وليس العناصر النائبة للمعلمات.
وينطبق ذلك على الطلبات الخمسة الأولى، كما هو موضح في
[ملاحظات PREPARE في PostgreSQL]. وعند التنفيذ السادس، يرسل ClickHouse
[معلمات الاستعلام] بالنمط {param:type}:
المعلمات:
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan (cost=1.00..5.10 rows=1000 width=36)
Output: (date(start_at)), (round(avg(duration), 0))
Relations: Aggregate on (logs)
Remote SQL: SELECT date(start_at), round(avg(duration), 0) FROM "default".logs WHERE ((date(start_at) >= {p1:Date})) AND ((date(start_at) <= {p2:Date})) GROUP BY (date(start_at)) ORDER BY date(start_at) ASC NULLS LAST
(4 rows)استخدم DEALLOCATE لإلغاء تخصيص تعليمة مُعَدّة:
try=# DEALLOCATE avg_durations_between_dates;
DEALLOCATEINSERT
استخدم الأمر INSERT لإدراج القيم في جدول ClickHouse موجود على خادم بعيد:
try=# INSERT INTO nodes(node_id, name, region, arch, os)
VALUES (9, 'Augustin Gamarra', 'us-west-2', 'amd64', 'Linux')
, (10, 'Cerisier', 'us-east-2', 'amd64', 'Linux')
, (11, 'Dewalt', 'use-central-1', 'arm64', 'macOS')
;
INSERT 0 3COPY
استخدم الأمر COPY لإدراج دفعة من الصفوف في جدول ClickHouse على خادم بعيد:
try=# COPY logs FROM stdin CSV;
4285871863,2025-12-05 11:13:58.360760,206,/widgets,POST,8,401
4020882978,2025-12-05 11:33:48.248450,199,/users/1321945,HEAD,3,200
3231273177,2025-12-05 12:20:42.158575,220,/search,GET,2,201
\.
>> COPY 3⚠️ قيود Batch API
لم يوفّر pg_clickhouse بعد دعم واجهة insert الدُفعية الخاصة بـ PostgreSQL FDW. لذلك يستخدم COPY حاليًا عبارات INSERT من أجل إدراج السجلات. وسيُحسَّن ذلك في إصدار مستقبلي.
LOAD
استخدم LOAD لتحميل المكتبة المشتركة pg_clickhouse:
try=# LOAD 'pg_clickhouse';
LOADلا تكون هناك حاجة عادةً إلى استخدام LOAD، لأن Postgres سيحمّل pg_clickhouse تلقائيًا عند استخدام أيٍ من وظائفه (الدوال، والجداول الخارجية، وما إلى ذلك) للمرة الأولى.
الحالة الوحيدة التي قد يكون فيها LOAD pg_clickhouse مفيدًا هي عند SET معلمات pg_clickhouse قبل تنفيذ الاستعلامات التي تعتمد عليها.
SET
استخدم SET لتعيين معلمات الإعداد المخصّصة لـ pg_clickhouse.
pg_clickhouse.session_settings
تُستخدم المعلمة pg_clickhouse.session_settings لتحديد [إعدادات
ClickHouse] التي ستُطبَّق على الاستعلامات اللاحقة. مثال:
SET pg_clickhouse.session_settings = 'join_use_nulls 1, final 1';القيمة الافتراضية هي
join_use_nulls 1, group_by_use_nulls 1, final 1, transform_null_in 0عيّنه إلى سلسلة فارغة للعودة إلى إعدادات خادم ClickHouse —
لكن لاحظ أن صحة تمرير إلى النظام البعيد تعتمد على بعض هذه الإعدادات الافتراضية:
join_use_nulls لعمليات الربط الخارجي وtransform_null_in لفئة IN
(راجع دلالات IN وNULL).
SET pg_clickhouse.session_settings = '';البنية هي قائمة مفصولة بفواصل من أزواج المفتاح/القيمة، تفصل بينها مسافة واحدة أو أكثر. يجب أن تتوافق المفاتيح مع [إعدادات ClickHouse]. استخدم الشرطة المائلة العكسية قبل المسافات، والفواصل، والشرطات المائلة العكسية في القيم:
SET pg_clickhouse.session_settings = 'join_algorithm grace_hash\,hash';أو استخدم قيَمًا محاطة بعلامات اقتباس مفردة لتجنّب الحاجة إلى استخدام محارف الهروب للمسافات والفواصل؛ وفكّر في استخدام [الاقتباس بالدولار] لتجنّب الحاجة إلى وضع علامات اقتباس مزدوجة:
SET pg_clickhouse.session_settings = $$join_algorithm 'grace_hash,hash'$$;إذا كنت تهتم بوضوح النص وتحتاج إلى تعيين الكثير من الإعدادات، فاستخدم عدة أسطر، على سبيل المثال:
SET pg_clickhouse.session_settings TO $$
connect_timeout 2,
count_distinct_implementation uniq,
final 1,
group_by_use_nulls 1,
join_algorithm 'prefer_partial_merge',
join_use_nulls 1,
log_queries_min_type QUERY_FINISH,
max_block_size 32768,
max_execution_time 45,
max_result_rows 1024,
metrics_perf_events_list 'this,that',
network_compression_method ZSTD,
poll_interval 5,
totals_mode after_having_auto
$$;سيتم تجاهل بعض الإعدادات في الحالات التي قد تتداخل فيها مع عمل pg_clickhouse نفسه. وتشمل ما يلي:
date_time_output_format: يتطلبhttp driverأن تكون قيمته "iso"format_tsv_null_representation: يتطلبhttp driverالقيمة الافتراضيةoutput_format_tsv_crlf_end_of_lineيتطلبhttp driverالقيمة الافتراضية
بخلاف ذلك، لا يتحقق pg_clickhouse من صحة الإعدادات، بل يمررها إلى ClickHouse مع كل استعلام. وبالتالي فهو يدعم جميع الإعدادات لكل إصدار من ClickHouse.
لاحظ أنه يجب تحميل pg_clickhouse قبل تعيين
pg_clickhouse.session_settings؛ إما باستخدام [التحميل المسبق للمكتبة المشتركة] أو
ببساطة باستخدام أحد الكائنات في الامتداد لضمان تحميله.
pg_clickhouse.pushdown_regex
تتحكم المعلمة pg_clickhouse.pushdown_regex في ما إذا كان pg_clickhouse
يقوم بتمرير دوال التعبيرات النمطية والعوامل إلى النظام البعيد. ويحدث ذلك افتراضيًا؛
اضبط هذه المعلمة على false لمنع تمريرها إلى النظام البعيد:
SET pg_clickhouse.pushdown_regex = 'false';راجع التعبيرات النمطية لمزيد من التفاصيل.
ALTER ROLE
استخدم الأمر SET في ALTER ROLE لإجراء التحميل المسبق لـ pg_clickhouse
و/أو SET معلماته لأدوار محددة:
try=# ALTER ROLE CURRENT_USER SET session_preload_libraries = pg_clickhouse;
ALTER ROLE
try=# ALTER ROLE CURRENT_USER SET pg_clickhouse.session_settings = 'final 1';
ALTER ROLEاستخدم الأمر RESET في ALTER ROLE لإعادة ضبط التحميل المسبق لـ pg_clickhouse
و/أو المعلمات:
try=# ALTER ROLE CURRENT_USER RESET session_preload_libraries;
ALTER ROLE
try=# ALTER ROLE CURRENT_USER RESET pg_clickhouse.session_settings;
ALTER ROLEالتحميل المسبق
إذا كان كل اتصال بـPostgres أو يكاد كل اتصال يحتاج إلى استخدام pg_clickhouse، ففكّر في استخدام [التحميل المسبق للمكتبات المشتركة] لتحميله تلقائيًا:
session_preload_libraries
يُحمِّل المكتبة المشتركة مع كل اتصال جديد بـ PostgreSQL:
session_preload_libraries = pg_clickhouseيفيد ذلك في الاستفادة من التحديثات دون إعادة تشغيل الخادم: ما عليك سوى إعادة الاتصال. ويمكن أيضًا ضبطه لمستخدمين أو أدوار محددة عبر ALTER ROLE.
يحمّل المكتبة المشتركة إلى العملية الرئيسية لـ PostgreSQL عند بدء التشغيل:
shared_preload_libraries = pg_clickhouseمفيد لتوفير الذاكرة وتقليل العبء الإضافي لكل جلسة، لكنه يتطلب إعادة تشغيل الكتلة عند تحديث المكتبة.
أنواع البيانات
يربط pg_clickhouse أنواع بيانات ClickHouse التالية بأنواع بيانات PostgreSQL. يستخدم IMPORT FOREIGN SCHEMA النوع الأول في عمود PostgreSQL عند استيراد الأعمدة؛ ويمكن استخدام أنواع إضافية في تعليمات CREATE FOREIGN TABLE:
| ClickHouse | PostgreSQL | ملاحظات |
|---|---|---|
| Bool | boolean | |
| Date | date | |
| Date32 | date | |
| DateTime | timestamptz | |
| Decimal | numeric | |
| Float32 | real | |
| Float64 | double precision | |
| IPv4 | inet | |
| IPv6 | inet | |
| Int16 | smallint | |
| Int32 | integer | |
| Int64 | bigint | |
| Int8 | smallint | |
| JSON | jsonb, json | |
| String | text, bytea | |
| UInt16 | integer | |
| UInt32 | bigint | |
| UInt64 | bigint | ينتج عنه خطأ عند القيم > BIGINT max |
| UInt8 | smallint | |
| UUID | uuid |
يمكن أيضاً قراءة أي عمود كنوع text أو varchar أو أي نوع سلسلة آخر. تأخذ القيمة
نوع PostgreSQL المذكور أعلاه، ثم تُعرَض عبر دالة الإخراج الخاصة بذلك النوع.
تظل قيم UInt64 التي تتجاوز الحد الأقصى لـ bigint تُنتج خطأً، لذا اعرضها
باستخدام دالة ClickHouse toString().
ترد أدناه ملاحظات وتفاصيل إضافية.
BYTEA
لا يوفر ClickHouse ما يعادل النوع BYTEA في PostgreSQL، غير أنه يتيح تخزين أي بايتات في النوع String. بوجه عام، ينبغي ربط سلاسل ClickHouse بالنوع TEXT في PostgreSQL، أما عند استخدام البيانات الثنائية فيُربط بالنوع BYTEA. مثال:
-- Create ClickHouse table with String columns.
CALL clickhouse_perform('ch_srv', $$
CREATE TABLE bytes (
c1 Int8, c2 String, c3 String
) ENGINE = MergeTree ORDER BY (c1);
$$);
-- Create foreign table with BYTEA columns.
CREATE FOREIGN TABLE bytes (
c1 int,
c2 BYTEA,
c3 BYTEA
) SERVER ch_srv OPTIONS( table_name 'bytes' );
-- Insert binary data into the foreign table.
INSERT INTO bytes
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
FROM generate_series(1, 4) n;
-- View the results.
SELECT * FROM bytes;سيُخرج استعلام SELECT الأخير:
c1 | c2 | c3
----+------------------------------------------------------------+------------------------------------
1 | \x1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | \xae3b28cde02542f81acce8783245430d
2 | \x5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | \x23e7c6cacb8383f878ad093b0027d72b
3 | \x53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | \x7e969132fc656148b97b6a2ee8bc83c1
4 | \x4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | \x8ef30f44c65480d12b650ab6b2b04245
(4 rows)تجدر الإشارة إلى أنه في حال وجود أي بايتات فارغة (nul) في أعمدة ClickHouse، فلن يُخرج الجدول الأجنبي الذي يستخدم أعمدة TEXT القيم الصحيحة:
-- Create foreign table with TEXT columns.
CREATE FOREIGN TABLE texts (
c1 int,
c2 TEXT,
c3 TEXT
) SERVER ch_srv OPTIONS( table_name 'bytes' );
-- Encode binary data as hex.
SELECT c1, encode(c2::bytea, 'hex'), encode(c3::bytea, 'hex') FROM texts ORDER BY c1;سيُخرج:
c1 | encode | encode
----+----------------------------------------------------------+----------------------------------
1 | 1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | ae3b28cde02542f81acce8783245430d
2 | 5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | 23e7c6cacb8383f878ad093b
3 | 53ac2c1fa83c8f64603fe9568d883331 | 7e969132fc656148b97b6a2ee8bc83c1
4 | 4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | 8ef30f44c65480d12b650ab6b2b04245
(4 rows)لاحظ أن الصفين الثاني والثالث يحتويان على قيم مبتورة. ويعود ذلك إلى أن PostgreSQL يعتمد على السلاسل المنتهية بـ nul ولا يدعم أحرف nul داخل سلاسله.
ستنجح محاولة إدراج القيم الثنائية في أعمدة TEXT وتعمل كما هو متوقع:
-- Insert via text columns:
TRUNCATE texts;
INSERT INTO texts
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
FROM generate_series(1, 4) n;
-- View the data.
SELECT c1, encode(c2::bytea, 'hex'), encode(c3::bytea, 'hex') FROM texts ORDER BY c1;ستكون أعمدة النص صحيحة:
c1 | encode | encode
----+----------------------------------------------------------+----------------------------------
1 | 1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | ae3b28cde02542f81acce8783245430d
2 | 5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | 23e7c6cacb8383f878ad093b0027d72b
3 | 53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | 7e969132fc656148b97b6a2ee8bc83c1
4 | 4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | 8ef30f44c65480d12b650ab6b2b04245
(4 rows)لكن قراءتها على أنها BYTEA لن تنجح:
# SELECT * FROM bytes;
c1 | c2 | c3
----+------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------
1 | \x5c783162663766306363383231643331313738363136613535613865306335323637373733353339376364646536663431353361396664336437 | \x5c786165336232386364653032353432663831616363653837383332343534333064
2 | \x5c783566366539653132636438353932373132653633383031366634623161326537333233306565343064623439386330663062316463383431 | \x5c783233653763366361636238333833663837386164303933623030323764373262
3 | \x5c783533616332633166613833633866363436303366653935363864383833333331303037643632383164653333306134623565373238663965 | \x5c783765393639313332666336353631343862393762366132656538626338336331
4 | \x5c783465336332653463623735343261343531373361386461633933396464633462633735323032653334326562633736396230663564613266 | \x5c783865663330663434633635343830643132623635306162366232623034323435
(4 rows)مرجع الدوال والعوامل
الدوال
توفّر هذه الدوال واجهةً للاستعلام عن قاعدة بيانات ClickHouse.
clickhouse_raw_query
SELECT clickhouse_raw_query(
'CREATE TABLE t1 (x String) ENGINE = Memory',
'host=localhost port=8123'
);اتصل بخدمة ClickHouse، ونفّذ استعلامًا واحدًا
ثم افصل الاتصال. يحدّد الوسيط الاختياري الثاني سلسلة اتصال تكون قيمتها
الافتراضية host=localhost port=8123. معاملات الاتصال
المدعومة هي:
driver: برنامج تشغيل الاتصال المراد استخدامه، إما "http" أو "binary"؛ والقيمة الافتراضية هي "http"host: المضيف المراد الاتصال به؛ مطلوب.port: المنفذ المراد الاتصال به. القيمة الافتراضية هي8123لبرنامج التشغيل "http" أو9000لبرنامج التشغيل "binary"، وتتغير إلى8443أو9440على التوالي عندما يكونhostمضيف ClickHouse Clouddbname: اسم قاعدة البيانات المراد الاتصال بها.username: اسم المستخدم الذي سيتم الاتصال باسمه؛ القيمة الافتراضيةdefaultpassword: كلمة المرور المستخدمة للمصادقة؛ والقيمة الافتراضية هي عدم استخدام كلمة مرور
يعيد كلا برنامجي التشغيل صفوفًا مفصولة بعلامات الجدولة (مع تمثيل القيم الخالية كـ \N)، لكن
تختلف طريقة تمثيل كل قيمة: يعيد برنامج التشغيل "http" تنسيق TSV
الخاص بـ ClickHouse حرفيًا، بينما يمرر برنامج التشغيل "binary" كل قيمة عبر
دالة الإخراج في PostgreSQL.
بشكل افتراضي، لا يملك أي دور صلاحية EXECUTE لهذه الدالة؛ لذا احرص على GRANT
صلاحية الوصول فقط للأدوار التي تحتاج فعليًا إلى تنفيذ استعلامات ClickHouse
مخصّصة، مثل دور Admin مخصّص في ClickHouse:
وهي مفيدة للاستعلامات التي لا تُرجع أي سجلات، لكن الاستعلامات التي تُرجع قيمًا ستُعاد على هيئة قيمة نصية واحدة:
SELECT clickhouse_raw_query(
'SELECT schema_name, schema_owner from information_schema.schemata',
'host=localhost port=8123'
); clickhouse_raw_query
---------------------------------
INFORMATION_SCHEMA default+
default default +
git default +
information_schema default+
system default +
(1 row)clickhouse_server_version
SELECT clickhouse_server_version('taxi_srv');أبلِغ عن إصدار خادم ClickHouse، بصيغة major.minor.patch، للخادم
الخارجي المُسمّى، مع الاتصال به عند الضرورة باستخدام خيارات الخادم وتعيين
المستخدم الخاص بالمستخدم الحالي:
clickhouse_server_version
---------------------------
25.8.1
(1 row)يقرأ الإصدار من مصافحة الاتصال عبر البروتوكول الأصلي، أو عبر HTTP
باستخدام استعلام واحد هو SELECT version()، ويخزّنه مؤقتًا طوال مدة
الاتصال.
clickhouse_query
SELECT * FROM clickhouse_query(
'server',
'SELECT id, name, salary FROM remote_table WHERE salary > 50000'
) AS ch(id int, name text, salary numeric);نفّذ استعلامًا على خادم خارجي مُهيّأ مسبقًا وأعِد
صفوفه في صورة علاقة، مع تعيين كل عمود في نتائج ClickHouse إلى نوع PostgreSQL
المسمّى في قائمة تعريف الأعمدة. ويُعاد استخدام driver الخاص بالخادم،
وبيانات الاعتماد وقاعدة البيانات وذاكرة التخزين المؤقت للاتصال.
الوسيط الأول هو اسم خادم أُنشئ باستخدام CREATE SERVER. يجب توفير
قائمة تعريف أعمدة (AS name(col type, ...)): يحتاج PostgreSQL إلى
معرفة بنية النتيجة قبل جلب الصفوف، ويجب أن تطابق الأعمدة التي يعيدها الاستعلام. تُحوَّل القيم
من ClickHouse إلى الأنواع المعلنة بالطريقة نفسها التي يُحوَّل بها عمود في جدول أجنبي.
أما العبارات التي لا تعيد نتائج، مثل DDL،
فلا تتطلب تعريفًا؛ نفّذها باستخدام
clickhouse_perform بدلًا من ذلك.
لا يملك أي دور صلاحية EXECUTE افتراضيًا؛ استخدم GRANT لمنح دور
إمكانية استخدام الدالة.
GRANT EXECUTE ON FUNCTION clickhouse_query(text, text) TO ch_admin;clickhouse_perform
CALL clickhouse_perform(
'server',
'CREATE TABLE remote_table (id Int32) ENGINE = MergeTree ORDER BY id'
);نفّذ عبارةً على خادم خارجي مُهيّأ مسبقًا وتجاهل
أي نتيجة. استخدمها للعبارات التي لا تُرجع صفوفًا، مثل DDL، حيث لا يملك
clickhouse_query شكل نتيجة ليُصرَّح عنه. وتحدّد
الخادم بالطريقة نفسها التي يستخدمها clickhouse_query، مع إعادة استخدام driver،
وبيانات الاعتماد، وقاعدة البيانات، وذاكرة التخزين المؤقت للاتصال.
وبوصفها إجراءً، يجب استدعاؤها باستخدام CALL، وليس SELECT، ولا تُرجع
صفوفًا. لا يملك أي دور صلاحية EXECUTE افتراضيًا؛ استخدم GRANT لمنح دورٍ ما صلاحية
استخدام الإجراء.
GRANT EXECUTE ON PROCEDURE clickhouse_perform(text, text) TO ch_admin;دوال Pushdown
يقوم pg_clickhouse بتمرير مجموعة فرعية من دوال PostgreSQL المضمّنة المستخدمة
في الشروط (البندين HAVING وWHERE). وتتوافق هذه المجموعة الفرعية مع
مكافئاتها في ClickHouse كما يلي:
abs: absfactorial: factorialmod(int2/int4/int8/numeric): modulopow&power(float8/numeric): powround: roundsin,cos,tan,atan,atan2,sinh,cosh,tanh,asinh,degrees,radians,pi: دوال الرياضيات في ClickHouse التي تحمل الاسم نفسه. لا يتم pushdown للدوالasinوacosوatanhوacosh: إذ يُصدر PG خطأً عند إدخال قيم خارج النطاق، بينما يعيد CH القيمةNaN.date_part:date_part('day'): toDayOfMonthdate_part('doy'): toDayOfYeardate_part('dow'): toDayOfWeekdate_part('year'): toYeardate_part('month'): toMonthdate_part('hour'): toHourdate_part('minute'): toMinutedate_part('second'): toSeconddate_part('quarter'): toQuarterdate_part('isoyear'): toISOYeardate_part('week'): toISOYeardate_part('epoch'): toISOYear
date_trunc:date_trunc('week'): toMondaydate_trunc('second'): toStartOfSeconddate_trunc('minute'): toStartOfMinutedate_trunc('hour'): toStartOfHourdate_trunc('day'): toStartOfDaydate_trunc('month'): toStartOfMonthdate_trunc('quarter'): toStartOfQuarterdate_trunc('year'): toStartOfYear
extract(field FROM source): نفس التعيينات كما فيdate_partdate(timestamp)&date(timestamptz): toDate (تُفكّك إلى الاسم المستعارdateفي CH)array_position: indexOf مع nullIf لتحويل0إلىNULL، واستخدام arraySlice عند وجود وسيطة ثالثة تحدد فهرس بدء البحث؛ لاحظ أنnanلا يطابق حاليًا أي قيمةarray_cat: arrayConcatarray_append: arrayPushBackarray_prepend: arrayPushFrontarray_remove: arrayRemovecardinality: lengtharray_length(array, 1):nullIf(length(array), 0)array_length&cardinality: lengtharray_to_string: arrayStringConcatstring_to_array: splitByStringsplit_part: splitByString + فهرسة المصفوفةtrim_array: arrayResizearray_fill: arrayWithConstantarray_reverse: arrayReversearray_shuffle: arrayShufflearray_sample: arrayRandomSamplearray_sort: arraySort / arrayReverseSortbtrim: trimBothltrim: trimLeftrtrim: trimRightconcat_ws: concatWithSeparatorlower(text): lowerUTF8upper(text): upperUTF8substring(text, ...)&substr(text, ...): substringUTF8substring(bytea, ...)&substr(bytea, ...): substringlength(text): lengthUTF8length(bytea)&octet_length: lengthreverse(text): reverseUTF8reverse(bytea): reversestrpos: positionUTF8regexp_like: matchregexp_match: extractGroups إذا كان التعبير النمطي يحتوي على تعبيرات فرعية بين قوسين؛ وإلا extractAll ثم تُقتطع باستخدام arraySlice.regexp_replace: replaceRegexpOne أو replaceRegexpOne عند وجود العلامةgregexp_split_to_array: splitByRegexpmd5: MD5encode(bytea, fmt)عندما يكونfmtثابتًا نصيًا (دون تمييز بين حالة الأحرف):encode(bytea, 'hex'): hex مُغلّفة بـ lower، لأن PostgreSQL يُنتج قيم hex بأحرف صغيرة.encode(bytea, 'base64'): base64Encode مُغلّفة بـ replaceRegexpAll لمحاكاة فاصل الأسطر بتنسيق MIME (RFC 2045) في PostgreSQL كل 76 حرفًا.encode(bytea, 'base64url')(PostgreSQL 19+): base64URLEncode، التي تطابق أبجدية URL في PostgreSQL وفق RFC 4648 من دون padding.
json_extract_path_text: صياغة العمود الفرعيjson_extract_path: toJSONString + صياغة العمود الفرعيjsonb_extract_path_text: صياغة العمود الفرعيjsonb_extract_path: toJSONString + صياغة العمود الفرعيbit_count(bytea): bitCountto_timestamp(float8): toDateTime64to_char(timestamp[tz], fmt): formatDateTime عندما يكونfmtثابتًا نصيًا وكانت كل كلمة مفتاحية فيه لها مقابل مطابق في ClickHouse. راجع to_char() ضمن ملاحظات التوافق للاطلاع على الكلمات المفتاحية المدعومة. بخلاف ذلك، تُقيَّم الدالة محليًا في PostgreSQL.statement_timestamp,transaction_timestamp, &clock_timestamp: nowInBlock64 (nowInBlock64(9, $session_timezone))CURRENT_DATE: now و toDate (toDate(now($session_timezone)))now,CURRENT_TIMESTAMP, &LOCALTIMESTAMP: now64 (now64(9, $session_timezone))CURRENT_TIMESTAMP(n)&LOCALTIMESTAMP(n): now64 (now64(n, $session_timezone))CURRENT_DATABASE: تُمرَّر كقيمة من دالة PostgreSQL.CURRENT_SCHEMA: تُمرَّر كقيمة من دالة PostgreSQL.CURRENT_CATALOG: تُمرَّر كقيمة من دالة PostgreSQL.CURRENT_USER: تُمرَّر كقيمة من دالة PostgreSQL.USER: تُمرَّر كقيمة من دالة PostgreSQL.CURRENT_ROLE: تُمرَّر كقيمة من دالة PostgreSQL.SESSION_USER: تُمرَّر كقيمة من دالة PostgreSQL.
معاملات Pushdown
- شريحة Array (
arr[L:U]): arraySlice @>(تحتوي المصفوفة على): hasAll<@(المصفوفة محتواة في): hasAll&&(تداخل المصفوفات): hasAny~(مطابقة تعبير نمطي): match!~(عدم مطابقة تعبير نمطي): match~*(عدم مطابقة تعبير نمطي دون مراعاة حالة الأحرف): match!~*(عدم مطابقة تعبير نمطي دون مراعاة حالة الأحرف): match->>(استخراج عنصر من JSON/JSONB كنص): sub-column syntax->(استخراج من JSON/JSONB): toJSONString + sub-column syntax
دلالات IN وNULL
يقيّم ClickHouse التعبير IN وفق منطق ثنائي القيم: فعندما لا يجد الفحص
تطابقاً، يعيد 0 حتى عند وجود NULL، بينما يحسب PostgreSQL
NULL. وللحفاظ على دلالات PostgreSQL، يدفع pg_clickhouse عائلة IN
إلى المصدر عند استخدامها مع قائمة أو مصفوفة ثابتة (IN، NOT IN، = ANY، = ALL،
<> ANY، <> ALL) دون استثناء: إما الصيغة الأصلية أو منخفضة التكلفة عندما يمكنه
إثبات أن لا قيمة الفحص ولا أي عنصر في المصفوفة يمكن أن يكون NULL، أو صيغة CASE
محمية في الحالات الأخرى تتحقق من قيم NULL وقت التشغيل،
وتحسب الإجابة الدقيقة ثلاثية القيم في PostgreSQL (TRUE، FALSE، NULL)
في جميع السياقات، بما في ذلك مواضع القيم مثل قائمة SELECT أو
GROUP BY.
يُدفع أيضاً عامل التصفية NOT IN (SELECT ...) المستخدم مع الأعمدة القابلة لـNULL
إلى المصدر، ويُعاد تحويله إلى نص مع حواجز تعويضية تحافظ على سلوك PostgreSQL: فالمجموعة
التي تحتوي على NULL تستبعد كل صف، ولا تنجح قيمة فحص NULL إلا
مع مجموعة فارغة. يُحذف كل حاجز عندما يثبت تعريف NOT NULL
عدم الحاجة إليه. وعلى خلاف صيغ المصفوفة أعلاه، لا ينطبق هذا الحاجز إلا في شرط تصفية
عادي (أو تحت NOT)؛ وما زلنا لا ندفع
IN (SELECT ...) إلى المصدر (في موضع قيمة)، ولا أجسام الاستعلامات الفرعية
المجمّعة أو المجمّعة تجميعياً. يؤدي تعريف الأعمدة بأنها NOT NULL إلى زيادة الدفع إلى المصدر
إلى أقصى حد، إذ يتيح إرسال الصيغة الأرخص غير المحمية بدلاً من ذلك؛ ويطبّق IMPORT FOREIGN SCHEMA هذا تلقائياً على
أعمدة ClickHouse غير Nullable. ويتتبع الإثبات الثوابت غير NULL،
وأعمدة NOT NULL، والعمليات الحسابية الأساسية (+، -، *، و- الأحادية) عليها.
تفترض هذه القواعد الإعداد الافتراضي في ClickHouse، وهو transform_null_in = 0، الذي
يضبطه pg_clickhouse لكل استعلام عبر القيمة الافتراضية للمعلمة
pg_clickhouse.session_settings
حتى لا يتمكن ملف تعريف خادم ClickHouse من تغييره بصمت. يؤدي ضبط
transform_null_in = 1 إلى الإخلال بدلالات كل تعبير IN مدفوع إلى المصدر.
الدوال المخصصة
توفّر هذه الدوال المخصصة التي أنشأها pg_clickhouse دعم pushdown للاستعلامات الخارجية لبعض دوال ClickHouse التي لا يوجد لها ما يكافئها في PostgreSQL. وإذا تعذّر تطبيق pushdown على أيٍّ من هذه الدوال، فسيُرفع استثناء.
Pushdown الامتدادات
يتعرّف pg_clickhouse على الدوال في بعض الامتدادات الأساسية وامتدادات الجهات الخارجية، ويُمرِّر تنفيذها إلى ما يقابلها في ClickHouse.
re2
تُمرَّر جميع معاملات ودوال [امتداد re2] مباشرةً إلى ClickHouse بنسبة 1:1:
@~→ matchre2match→ matchre2extract→ extractre2extractall→ extractAllre2regexpextract→ regexpExtractre2extractgroups→ extractGroupsre2replaceregexpone→ replaceRegexpOnere2replaceregexpall→ replaceRegexpAllre2countmatches→ countMatchesre2countmatchescaseinsensitive→ countMatchesCaseInsensitivere2multimatchany→ multiMatchAnyre2multimatchanyindex→ multiMatchAnyIndexre2multimatchallindices→ multiMatchAllIndices
intarray
تُنفَّذ إحدى دوال intarray مباشرةً في ClickHouse:
idx→ indexOf
fuzzystrmatch
يمكن تمرير دالتين من fuzzystrmatch إلى ClickHouse:
soundex: soundexlevenshtein(بوسيطتين): editDistanceUTF8
عمليات CAST ذات pushdown
يقوم pg_clickhouse بتنفيذ pushdown لعمليات CAST مثل CAST(x AS bigint) عند استخدام
أنواع بيانات متوافقة. أما مع الأنواع غير المتوافقة، فستفشل عملية الـ pushdown؛
فإذا كانت x في هذا المثال من نوع ClickHouse UInt64، فسيرفض ClickHouse
تحويل القيمة.
ولتنفيذ pushdown لعمليات CAST لأنواع البيانات غير المتوافقة، يوفّر pg_clickhouse الدوال التالية. وهي تثير استثناءً في PostgreSQL إذا لم يُنفَّذ لها pushdown.
دوال التجميع التي تدعم pushdown
تدعم دوال التجميع التالية في PostgreSQL آلية pushdown إلى ClickHouse.
- any_value
- array_agg
- avg
- bit_and
- bit_or
- bit_xor
- bool_and / every
- bool_or
- count
- corr
- covarpop
- covarsamp
- min
- max
- stddev_pop
- stddev_samp / stddev
- string_agg
- sum
- var_op
- var_samp /variance
دوال التجميع المخصصة
توفّر دوال التجميع المخصصة هذه، التي أنشأها pg_clickhouse، إمكانية pushdown للاستعلامات الخارجية لبعض دوال التجميع في ClickHouse التي لا يقابلها ما يعادلها في PostgreSQL. وإذا تعذر pushdown لأي من هذه الدوال، فسيُرفَع استثناء.
Pushdown دوال التجميع المرتبة حسب المجموعة
تُطابِق دوال [دوال التجميع للمجموعات المرتبة] هذه دوال [دوال التجميع ذات المعلمات] في ClickHouse، وذلك بتمرير المعامل المباشر الخاص بها كـ parameter، وتمرير
تعبيرات ORDER BY الخاصة بها على أنها arguments. على سبيل المثال، query PostgreSQL هذه:
SELECT percentile_cont(0.25) WITHIN GROUP (ORDER BY a) FROM t1;يقابله استعلام ClickHouse التالي:
SELECT quantile(0.25)(a) FROM t1;لاحظ أن اللاحقتين غير الافتراضيتين DESC وNULLS FIRST في ORDER BY
غير مدعومتين، وستؤديان إلى ظهور خطأ.
percentile_cont(double): quantilepercentile_cont(double[]): quantilespercentile_disc(double): quantileExactLowpercentile_disc(double[]): quantilesExactLow
دوال التجميع المخصصة للمجموعات المرتبة
توفر دوال [دوال التجميع للمجموعات المرتبة] المخصصة هذه، التي أنشأها pg_clickhouse، إمكانية pushdown الاستعلامات الخارجية لبعض دوال [دوال التجميع ذات المعلمات] في ClickHouse. إذا تعذر pushdown أي من هذه الدوال، فستُطلق استثناءً.
quantile(double): quantilequantileExact(double): quantileExact
دوال التجميع المخصصة للمجموعات المرتبة
توفّر دوال [دوال التجميع للمجموعات المرتبة] المخصصة هذه، التي أنشأتها pg_clickhouse، ميزة pushdown الاستعلامات الخارجية لتنفيذ دوال ClickHouse [دوال التجميع ذات المعلمات]. وإذا تعذّر pushdown أي من هذه الدوال، فستُطلق استثناءً.
دوال النافذة pushdown
تخضع دوال النافذة التالية في PostgreSQL لـ pushdown إلى ClickHouse باستخدام عبارات OVER (PARTITION BY ... ORDER BY ...)، بما في ذلك مواصفات الإطار عند الاقتضاء.
- row_number
- rank
- dense_rank
- ntile
- cume_dist
- percent_rank
- lead
- lag
- first_value
- last_value
- nth_value
min/max(مع عبارةOVER)
تُهمل دوال الترتيب (row_number وrank وdense_rank وntile وcume_dist
وpercent_rank) عبارة الإطار الخاصة بها عند pushdown، لأن ClickHouse
لا يقبل مواصفات الإطار لهذه الدوال.
ملاحظات حول التوافق
التعبيرات النمطية
مع أن pg_clickhouse يحوّل التعبيرات النمطية إلى ما يكافئها في ClickHouse عندما تكون pg_clickhouse.pushdown_regex مضبوطة على true (وهو الإعداد الافتراضي)، ويسعى إلى ضمان حدٍّ أساسي من التوافق، فاحرص على فهم الاختلافات بينهما وكيفية تعامل pg_clickhouse معها.
-
يدعم PostgreSQL [التعبيرات النمطية وفق POSIX] بينما يدعم ClickHouse التعبيرات النمطية وفق RE2. انتبه إلى اختلافات السلوك: استخدم RE2 عندما يتولى ClickHouse تقييم التعبير النمطي (على سبيل المثال، في عبارة
WHERE) واستخدم POSIX عندما يتولى Postgres تقييمه (على سبيل المثال، في عبارةSELECT). -
يقوم pg_clickhouse بتمرير [مُعدِّلات Postgres] إلى التعبير النمطي في ClickHouse عبر إضافتها في بدايته داخل
(?). على سبيل المثال:regexp_like(val, '^VAL\d', 'i')يصبح
match(val, concat('(?i)', '^VAL\\d')) -
خيارات سطر الأوامر الوحيدة التي يدعمها كلاهما، والتي يمكن بالتالي استخدامها عند تقييمها من قِبل ClickHouse، هي:
الخيار يكافئ ملاحظات iiمطابقة غير حساسة لحالة الأحرف mm-sيطابق ^و$بداية/نهاية السطر، بالإضافة إلى بداية/نهاية النصnm-sالاسم المستعار في Postgres لـ mp-sلا تدع .و[^x]يطابقان\nssدع .و[^x]يطابقان\ntصياغة محكمة، يُتجاهل wmمطابقة عكسية جزئية تراعي محرف السطر الجديد لا يدعم RE2 سوى هذه العلامات؛ لا تستخدم أيًا من [علامات Postgres] الأخرى.
-
يلخّص هذا الجدول تأثير العلامات المختلفة (وكذلك عدم استخدام أي علامة، وهو ما يعادل
s) عند مطابقة الأسطر الجديدة ونهايات الأسطر. لاحظ أنه في Postgres، تمنعmوpفئات المحارف المنفية ([^xyz]) من مطابقة سطر جديد، بينما لا تفعل نظيراتها في ClickHouse ذلك. وبخلاف ذلك، فإن السلوكيات في ClickHouse وPostgres متماثلة:النمط المُطبَّق على a\nbPostgres ClickHouse هل هما متماثلان؟ a.bصحيح صحيح ✔︎ a[^x]bصحيح صحيح ✔︎ a$خطأ خطأ ✔︎ علامة s(?s)a.bصحيح صحيح ✔︎ (?s)a[^x]bصحيح صحيح ✔︎ (?s)a$خطأ خطأ ✔︎ علامة m(?m)a.bخطأ خطأ ✔︎ (?m)a[^x]bصحيح خطأ ✘ (?m)a$صحيح صحيح ✔︎ علامة p(?p)a.bخطأ خطأ ✔︎ (?p)a[^x]bصحيح خطأ ✘ (?p)a$خطأ خطأ ✔︎ علامة w(?w)a.bصحيح صحيح ✔ (?w)a[^x]bصحيح صحيح ✔ (?w)a$صحيح صحيح ✔ -
أي خيارات أخرى تُمرَّر إلى دوال التعبيرات النمطية ستمنع تنفيذ الدالة على المصدر البعيد.
-
الاستثناء هو
regexp_replace()، إذ يدعم أيضًا الخيارg. وعندما يكونgمفعّلًا، يستخدم pg_clickhousereplaceRegexpAll()بدلًا منreplaceRegexpOne()ويزيل هذا الخيار قبل إضافة الخيارات الأخرى كبادئة. -
تدعم وسيطة الاستبدال في
regexp_replace()في Postgres الرمز\&للإشارة إلى المطابقة بالكامل، بينما يدعم ClickHouse الرمز\0للمطابقة بالكامل. احرص على استخدام\0عندما تُنفَّذ الدالة في ClickHouse. -
تعيد
regexp_matchفي Postgres القيمةNULLعند عدم وجود أي تطابقات، بينما تعيد التعبيرات التي يُدفَع تنفيذها إلى النظام البعيد مصفوفة فارغة. استخدمCOALESCE()لإرجاع مصفوفة فارغة بدلًا منNULLحتى تتمكن من مقارنة القيم المعادة بشكل متوافق. على سبيل المثال:SELECT * FROM events WHERE COALESCE(regexp_match(msg, '^ERR'), '{}');
لتجنّب أي لبس تمامًا، فكّر في ضبط pg_clickhouse.pushdown_regex لمنع pushdown التعبيرات النمطية في Postgres إلى ClickHouse، واستخدام [امتداد re2]، الذي يدعم معه pg_clickhouse pushdown المباشر لـ التعبيرات النمطية RE2 المتوافقة مع ClickHouse.
to_char()
لا يُجرى pushdown لـ PostgreSQL to_char() الخاص بـ timestamp وtimestamp with time zone
إلى ClickHouse formatDateTime إلا عندما تكون وسيطة التنسيق
ثابتًا نصيًا غير NULL، ويكون لكل keyword في PostgreSQL
مكافئ مماثل تمامًا على مستوى البايت في ClickHouse. وإذا كان التنسيق ديناميكيًا
(أي ليس Const)، أو كان يحتوي على أي keyword أو modifier غير مدعوم، فإن
الاستدعاء يعود إلى التقييم المحلي في PostgreSQL — ولا تُجرى مطلقًا محاولة
لـ pushdown مع ترجمة جزئية، بحيث يظل الناتج متوافقًا مع PG.
أما صيغ to_char() ذات الوسيطتين المطبَّقة على numeric وinterval وأنواع
أخرى غير timestamp، فلا يُجرى لها pushdown مطلقًا؛ إذ إن ClickHouse formatDateTime لا
ينسّق إلا قيم التاريخ والوقت.
الرموز المترجمة
| PostgreSQL | ClickHouse | المعنى |
|---|---|---|
YYYY, yyyy |
%Y |
سنة من 4 أرقام |
YY, yy |
%y |
سنة من رقمين |
MM, mm |
%m |
شهر مُكمَّل بأصفار بادئة (01–12) |
DD, dd |
%d |
يوم من الشهر مُكمَّل بأصفار بادئة (01–31) |
DDD, ddd |
%j |
رقم اليوم في السنة مُكمَّل بأصفار بادئة (001–366) |
HH24, hh24 |
%H |
ساعة بنظام 24 ساعة مُكمَّلة بأصفار بادئة (00–23) |
HH, hh, HH12, hh12 |
%I |
ساعة بنظام 12 ساعة مُكمَّلة بأصفار بادئة (01–12) |
MI, mi |
%i |
دقيقة مُكمَّلة بأصفار بادئة (00–59) |
SS, ss |
%S |
ثانية مُكمَّلة بأصفار بادئة (00–59) |
Q, q |
%Q |
ربع السنة (1–4) |
Mon |
%b |
اسم شهر مختصر، مثل Oct |
Dy |
%a |
اسم يوم أسبوع مختصر، مثل Mon |
AM, PM |
%p |
مؤشر AM/PM، ويكون دائمًا بأحرف كبيرة |
النصوص المقتبسة والقيم الحرفية
النص المحاط بـ "..." يُمرَّر كما هو حرفيًا، مع مضاعفة أي % حرفية
إلى %% لتحييد بادئة المحدِّد في ClickHouse. كذلك، فإن \" خارج علامات
الاقتباس يُمرَّر أيضًا باعتباره " حرفيًا. وداخل "..."، لا يُستخدم
backslash إلا لإلغاء المعنى الخاص لـ "؛ أما تسلسلات backslash الأخرى
فتُعامَل كنص حرفي.
حقوق الطبع والنشر
حقوق الطبع والنشر (c) 2025-2026، ClickHouse