pgsync 7.0.2__tar.gz → 7.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pgsync-7.0.2 → pgsync-7.1.0}/MANIFEST.in +1 -0
- pgsync-7.1.0/PKG-INFO +263 -0
- pgsync-7.1.0/README.md +380 -0
- pgsync-7.1.0/README.rst +179 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/bin/bootstrap +1 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/bin/parallel_sync +26 -33
- {pgsync-7.0.2 → pgsync-7.1.0}/bin/pgsync +1 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/__init__.py +1 -1
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/base.py +121 -93
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/constants.py +10 -11
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/node.py +14 -5
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/querybuilder.py +22 -5
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/redisqueue.py +23 -7
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/settings.py +161 -135
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/sync.py +69 -5
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/transform.py +84 -40
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/trigger.py +8 -3
- pgsync-7.1.0/pgsync.egg-info/PKG-INFO +263 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync.egg-info/SOURCES.txt +5 -0
- pgsync-7.1.0/pgsync.egg-info/requires.txt +36 -0
- pgsync-7.1.0/pyproject.toml +3 -0
- pgsync-7.1.0/requirements/base.txt +118 -0
- pgsync-7.1.0/requirements/dev.txt +205 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/setup.py +3 -3
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/conftest.py +31 -0
- pgsync-7.1.0/tests/test_base.py +1599 -0
- pgsync-7.1.0/tests/test_bug_regressions.py +136 -0
- pgsync-7.1.0/tests/test_exc.py +322 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_node.py +359 -0
- pgsync-7.1.0/tests/test_plugin.py +437 -0
- pgsync-7.1.0/tests/test_query_builder.py +2552 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_redisqueue.py +58 -0
- pgsync-7.1.0/tests/test_search_client.py +648 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_settings.py +4 -1
- pgsync-7.1.0/tests/test_sync.py +3210 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_transform.py +474 -1
- pgsync-7.1.0/tests/test_utils.py +775 -0
- pgsync-7.1.0/tests/test_view.py +662 -0
- pgsync-7.0.2/PKG-INFO +0 -210
- pgsync-7.0.2/README.md +0 -396
- pgsync-7.0.2/README.rst +0 -129
- pgsync-7.0.2/pgsync.egg-info/PKG-INFO +0 -210
- pgsync-7.0.2/pgsync.egg-info/requires.txt +0 -32
- pgsync-7.0.2/pyproject.toml +0 -3
- pgsync-7.0.2/tests/test_base.py +0 -676
- pgsync-7.0.2/tests/test_query_builder.py +0 -102
- pgsync-7.0.2/tests/test_search_client.py +0 -115
- pgsync-7.0.2/tests/test_sync.py +0 -1450
- pgsync-7.0.2/tests/test_utils.py +0 -229
- pgsync-7.0.2/tests/test_view.py +0 -302
- {pgsync-7.0.2 → pgsync-7.1.0}/AUTHORS.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/CONTRIBUTING.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/HISTORY.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/LICENSE +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/Makefile +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/authors.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/changelog.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/conf.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/contributing.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/history.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/index.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/installation.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/logo.png +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/make.bat +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/readme.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/docs/usage.rst +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/exc.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/helper.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/plugin.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/search_client.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/singleton.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/urls.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/utils.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/view.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync.egg-info/dependency_links.txt +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync.egg-info/not-zip-safe +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/pgsync.egg-info/top_level.txt +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/setup.cfg +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/__init__.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/fixtures/schema.json +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_constants.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_env_vars.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_helper.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_log_handlers.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_sync_nested_children.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_sync_root.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_sync_single_child_fk_on_child.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_sync_single_child_fk_on_parent.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_trigger.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_unique_behaviour.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_urls.py +0 -0
- {pgsync-7.0.2 → pgsync-7.1.0}/tests/testing_utils.py +0 -0
pgsync-7.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgsync
|
|
3
|
+
Version: 7.1.0
|
|
4
|
+
Summary: Postgres/MySQL/MariaDB to Elasticsearch/OpenSearch sync
|
|
5
|
+
Home-page: https://github.com/toluaina/pgsync
|
|
6
|
+
Author: Tolu Aina
|
|
7
|
+
Author-email: tolu@pgsync.com
|
|
8
|
+
Maintainer: Tolu Aina
|
|
9
|
+
Maintainer-email: tolu@pgsync.com
|
|
10
|
+
License: MIT
|
|
11
|
+
Project-URL: Bug Reports, https://github.com/toluaina/pgsync/issues
|
|
12
|
+
Project-URL: Funding, https://github.com/sponsors/toluaina
|
|
13
|
+
Project-URL: Source, https://github.com/toluaina/pgsync
|
|
14
|
+
Project-URL: Web, https://pgsync.com
|
|
15
|
+
Project-URL: Documentation, https://pgsync.com
|
|
16
|
+
Keywords: change data capture,elasticsearch,opensearch,pgsync,postgres,mysql,mariadb
|
|
17
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: Natural Language :: English
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
27
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
28
|
+
Classifier: Operating System :: OS Independent
|
|
29
|
+
Requires-Python: >=3.10.0
|
|
30
|
+
Description-Content-Type: text/x-rst
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
License-File: AUTHORS.rst
|
|
33
|
+
Requires-Dist: async-timeout==5.0.1
|
|
34
|
+
Requires-Dist: backports-datetime-fromisoformat==2.0.3
|
|
35
|
+
Requires-Dist: boto3==1.43.36
|
|
36
|
+
Requires-Dist: botocore==1.43.36
|
|
37
|
+
Requires-Dist: certifi==2026.6.17
|
|
38
|
+
Requires-Dist: charset-normalizer==3.4.7
|
|
39
|
+
Requires-Dist: click==8.4.2
|
|
40
|
+
Requires-Dist: elastic-transport==9.4.2
|
|
41
|
+
Requires-Dist: elasticsearch==7.17.13
|
|
42
|
+
Requires-Dist: elasticsearch-dsl==7.4.1
|
|
43
|
+
Requires-Dist: environs==15.0.1
|
|
44
|
+
Requires-Dist: events==0.5
|
|
45
|
+
Requires-Dist: grpcio==1.81.1
|
|
46
|
+
Requires-Dist: idna==3.18
|
|
47
|
+
Requires-Dist: jmespath==1.1.0
|
|
48
|
+
Requires-Dist: marshmallow==4.3.0
|
|
49
|
+
Requires-Dist: mysql-replication==1.0.15
|
|
50
|
+
Requires-Dist: opensearch-dsl==2.1.0
|
|
51
|
+
Requires-Dist: opensearch-protobufs==1.2.0
|
|
52
|
+
Requires-Dist: opensearch-py==3.2.0
|
|
53
|
+
Requires-Dist: packaging==26.2
|
|
54
|
+
Requires-Dist: protobuf==7.35.1
|
|
55
|
+
Requires-Dist: psycopg2-binary==2.9.12
|
|
56
|
+
Requires-Dist: pymysql==1.2.0
|
|
57
|
+
Requires-Dist: python-dateutil==2.9.0.post0
|
|
58
|
+
Requires-Dist: python-dotenv==1.2.2
|
|
59
|
+
Requires-Dist: redis==8.0.1
|
|
60
|
+
Requires-Dist: requests==2.34.2
|
|
61
|
+
Requires-Dist: requests-aws4auth==1.3.2
|
|
62
|
+
Requires-Dist: s3transfer==0.19.0
|
|
63
|
+
Requires-Dist: six==1.17.0
|
|
64
|
+
Requires-Dist: sniffio==1.3.1
|
|
65
|
+
Requires-Dist: sqlalchemy==2.0.51
|
|
66
|
+
Requires-Dist: sqlparse==0.5.5
|
|
67
|
+
Requires-Dist: typing-extensions==4.15.0
|
|
68
|
+
Requires-Dist: urllib3==2.7.0
|
|
69
|
+
Dynamic: author
|
|
70
|
+
Dynamic: author-email
|
|
71
|
+
Dynamic: classifier
|
|
72
|
+
Dynamic: description
|
|
73
|
+
Dynamic: description-content-type
|
|
74
|
+
Dynamic: home-page
|
|
75
|
+
Dynamic: keywords
|
|
76
|
+
Dynamic: license
|
|
77
|
+
Dynamic: license-file
|
|
78
|
+
Dynamic: maintainer
|
|
79
|
+
Dynamic: maintainer-email
|
|
80
|
+
Dynamic: project-url
|
|
81
|
+
Dynamic: requires-dist
|
|
82
|
+
Dynamic: requires-python
|
|
83
|
+
Dynamic: summary
|
|
84
|
+
|
|
85
|
+
======
|
|
86
|
+
PGSync
|
|
87
|
+
======
|
|
88
|
+
|
|
89
|
+
.. image:: https://img.shields.io/pypi/v/pgsync.svg
|
|
90
|
+
:target: https://pypi.org/project/pgsync/
|
|
91
|
+
:alt: PyPI Version
|
|
92
|
+
|
|
93
|
+
.. image:: https://img.shields.io/pypi/pyversions/pgsync.svg
|
|
94
|
+
:target: https://pypi.org/project/pgsync/
|
|
95
|
+
:alt: Python Versions
|
|
96
|
+
|
|
97
|
+
.. image:: https://img.shields.io/pypi/l/pgsync.svg
|
|
98
|
+
:target: https://opensource.org/licenses/MIT
|
|
99
|
+
:alt: License
|
|
100
|
+
|
|
101
|
+
.. image:: https://img.shields.io/pypi/dm/pgsync.svg
|
|
102
|
+
:target: https://pypi.org/project/pgsync/
|
|
103
|
+
:alt: Downloads
|
|
104
|
+
|
|
105
|
+
|
|
|
106
|
+
|
|
107
|
+
**PostgreSQL/MySQL/MariaDB to Elasticsearch/OpenSearch sync**
|
|
108
|
+
|
|
109
|
+
`PGSync <https://pgsync.com>`_ is a middleware for syncing data from `PostgreSQL <https://www.postgresql.org>`_, `MySQL <https://www.mysql.com>`_, or `MariaDB <https://mariadb.org>`_ to `Elasticsearch <https://www.elastic.co/products/elastic-stack>`_ or `OpenSearch <https://opensearch.org>`_.
|
|
110
|
+
|
|
111
|
+
Keep your relational database as the source of truth and expose structured denormalized documents in your search engine.
|
|
112
|
+
|
|
113
|
+
Key Features
|
|
114
|
+
------------
|
|
115
|
+
|
|
116
|
+
- Real-time sync via logical decoding (PostgreSQL) or binary log (MySQL/MariaDB)
|
|
117
|
+
- Denormalize complex relational data into nested search documents
|
|
118
|
+
- JSON schema-based configuration
|
|
119
|
+
- Support for one-to-one, one-to-many relationships
|
|
120
|
+
- Plugin system for document transformation
|
|
121
|
+
- Multiple operation modes: daemon, polling, or direct WAL streaming
|
|
122
|
+
|
|
123
|
+
Requirements
|
|
124
|
+
------------
|
|
125
|
+
|
|
126
|
+
- `Python <https://www.python.org>`_ 3.10+
|
|
127
|
+
- `PostgreSQL <https://www.postgresql.org>`_ 9.6+ or `MySQL <https://www.mysql.com>`_ 8.0.0+ or `MariaDB <https://mariadb.org>`_ 12.0.0+
|
|
128
|
+
- `Redis <https://redis.io>`_ 3.1.0+ or `Valkey <https://valkey.io>`_ 7.2.0+ (optional in WAL mode)
|
|
129
|
+
- `Elasticsearch <https://www.elastic.co/products/elastic-stack>`_ 6.3.1+ or `OpenSearch <https://opensearch.org>`_ 1.3.7+
|
|
130
|
+
|
|
131
|
+
Installation
|
|
132
|
+
------------
|
|
133
|
+
|
|
134
|
+
Install from `PyPI <https://pypi.org/project/pgsync/>`_:
|
|
135
|
+
|
|
136
|
+
.. code-block:: bash
|
|
137
|
+
|
|
138
|
+
pip install pgsync
|
|
139
|
+
|
|
140
|
+
Database Setup
|
|
141
|
+
--------------
|
|
142
|
+
|
|
143
|
+
PostgreSQL
|
|
144
|
+
~~~~~~~~~~
|
|
145
|
+
|
|
146
|
+
Enable `logical decoding <https://www.postgresql.org/docs/current/logicaldecoding.html>`_ in your PostgreSQL configuration (``postgresql.conf``):
|
|
147
|
+
|
|
148
|
+
.. code-block:: ini
|
|
149
|
+
|
|
150
|
+
wal_level = logical
|
|
151
|
+
max_replication_slots = 1
|
|
152
|
+
|
|
153
|
+
MySQL / MariaDB
|
|
154
|
+
~~~~~~~~~~~~~~~
|
|
155
|
+
|
|
156
|
+
Enable binary logging in your MySQL/MariaDB configuration (``my.cnf``):
|
|
157
|
+
|
|
158
|
+
.. code-block:: ini
|
|
159
|
+
|
|
160
|
+
server-id = 1
|
|
161
|
+
log_bin = mysql-bin
|
|
162
|
+
binlog_row_image = FULL
|
|
163
|
+
binlog_expire_logs_seconds = 604800
|
|
164
|
+
|
|
165
|
+
Create a replication user:
|
|
166
|
+
|
|
167
|
+
.. code-block:: sql
|
|
168
|
+
|
|
169
|
+
CREATE USER 'replicator'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
|
|
170
|
+
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replicator'@'%';
|
|
171
|
+
FLUSH PRIVILEGES;
|
|
172
|
+
|
|
173
|
+
Configuration
|
|
174
|
+
-------------
|
|
175
|
+
|
|
176
|
+
Create a JSON schema file (e.g., ``schema.json``) defining your sync mapping:
|
|
177
|
+
|
|
178
|
+
.. code-block:: json
|
|
179
|
+
|
|
180
|
+
[
|
|
181
|
+
{
|
|
182
|
+
"database": "book",
|
|
183
|
+
"index": "book",
|
|
184
|
+
"nodes": {
|
|
185
|
+
"table": "book",
|
|
186
|
+
"columns": ["isbn", "title", "description"],
|
|
187
|
+
"children": [
|
|
188
|
+
{
|
|
189
|
+
"table": "publisher",
|
|
190
|
+
"columns": ["name"],
|
|
191
|
+
"relationship": {
|
|
192
|
+
"variant": "object",
|
|
193
|
+
"type": "one_to_one"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"table": "author",
|
|
198
|
+
"label": "authors",
|
|
199
|
+
"columns": ["name", "date_of_birth"],
|
|
200
|
+
"relationship": {
|
|
201
|
+
"variant": "object",
|
|
202
|
+
"type": "one_to_many",
|
|
203
|
+
"through_tables": ["book_author"]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
See the `examples directory <https://github.com/toluaina/pgsync/tree/main/examples>`_ for more schema examples (airbnb, social, rental, etc.).
|
|
212
|
+
|
|
213
|
+
Environment Variables
|
|
214
|
+
---------------------
|
|
215
|
+
|
|
216
|
+
Configure PGSync via environment variables:
|
|
217
|
+
|
|
218
|
+
.. code-block:: bash
|
|
219
|
+
|
|
220
|
+
# Schema
|
|
221
|
+
SCHEMA='/path/to/schema.json'
|
|
222
|
+
|
|
223
|
+
# PostgreSQL
|
|
224
|
+
PG_HOST=localhost
|
|
225
|
+
PG_PORT=5432
|
|
226
|
+
PG_USER=postgres
|
|
227
|
+
PG_PASSWORD=*****
|
|
228
|
+
|
|
229
|
+
# Elasticsearch / OpenSearch
|
|
230
|
+
ELASTICSEARCH_HOST=localhost
|
|
231
|
+
ELASTICSEARCH_PORT=9200
|
|
232
|
+
|
|
233
|
+
# Redis (optional in WAL mode)
|
|
234
|
+
REDIS_HOST=localhost
|
|
235
|
+
REDIS_PORT=6379
|
|
236
|
+
|
|
237
|
+
Running
|
|
238
|
+
-------
|
|
239
|
+
|
|
240
|
+
**Bootstrap** (run once to set up triggers and replication slots):
|
|
241
|
+
|
|
242
|
+
.. code-block:: bash
|
|
243
|
+
|
|
244
|
+
bootstrap --config schema.json
|
|
245
|
+
|
|
246
|
+
**Run as daemon**:
|
|
247
|
+
|
|
248
|
+
.. code-block:: bash
|
|
249
|
+
|
|
250
|
+
pgsync --config schema.json --daemon
|
|
251
|
+
|
|
252
|
+
Links
|
|
253
|
+
-----
|
|
254
|
+
|
|
255
|
+
- **Documentation**: https://pgsync.com
|
|
256
|
+
- **Source Code**: https://github.com/toluaina/pgsync
|
|
257
|
+
- **Bug Reports**: https://github.com/toluaina/pgsync/issues
|
|
258
|
+
- **Sponsor**: https://github.com/sponsors/toluaina
|
|
259
|
+
|
|
260
|
+
License
|
|
261
|
+
-------
|
|
262
|
+
|
|
263
|
+
MIT License - see `LICENSE <https://github.com/toluaina/pgsync/blob/main/LICENSE>`_ for details.
|
pgsync-7.1.0/README.md
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# PGSync
|
|
4
|
+
|
|
5
|
+
### Real-time PostgreSQL to Elasticsearch/OpenSearch sync
|
|
6
|
+
|
|
7
|
+
**Keep your relational database as the source of truth while powering lightning-fast search**
|
|
8
|
+
|
|
9
|
+
[](https://pypi.org/project/pgsync)
|
|
10
|
+
[](https://pypi.org/project/pgsync)
|
|
11
|
+
[](https://github.com/toluaina/pgsync/actions)
|
|
12
|
+
[](https://codecov.io/gh/toluaina/pgsync)
|
|
13
|
+
[](https://pypi.org/project/pgsync)
|
|
14
|
+
[](LICENSE)
|
|
15
|
+
|
|
16
|
+
[](https://hub.docker.com/r/toluaina1/pgsync)
|
|
17
|
+
[](https://github.com/psf/black)
|
|
18
|
+
|
|
19
|
+
[Website](https://pgsync.com) · [Documentation](https://pgsync.com) · [Examples](examples/) · [Report Bug](https://github.com/toluaina/pgsync/issues)
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## What is PGSync?
|
|
26
|
+
|
|
27
|
+
PGSync is a change data capture tool that syncs data from **PostgreSQL**, **MySQL**, or **MariaDB** to **Elasticsearch** or **OpenSearch** in real-time. Define your document structure in JSON, and PGSync handles the rest — no custom code required.
|
|
28
|
+
|
|
29
|
+
```mermaid
|
|
30
|
+
%%{init: {'look': 'handDrawn', 'theme': 'neutral'}}%%
|
|
31
|
+
flowchart LR
|
|
32
|
+
subgraph Source["🗄️ Source Database"]
|
|
33
|
+
DB[(PostgreSQL<br/>MySQL<br/>MariaDB)]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
subgraph CDC["⚡ Change Data Capture"]
|
|
37
|
+
P[PGSync]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph Search["🔍 Search Engine"]
|
|
41
|
+
ES[(Elasticsearch<br/>OpenSearch)]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
DB -->|WAL / Binlog| P
|
|
45
|
+
P -->|Bulk Index| ES
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Key Features
|
|
49
|
+
|
|
50
|
+
| Feature | Description |
|
|
51
|
+
|---------|-------------|
|
|
52
|
+
| **Real-time sync** | Changes propagate instantly via logical replication |
|
|
53
|
+
| **Zero code** | Define mappings in JSON — no ETL pipelines to build |
|
|
54
|
+
| **Nested documents** | Automatically denormalize complex relationships |
|
|
55
|
+
| **Fault tolerant** | Resumes from checkpoints after crashes |
|
|
56
|
+
| **Transactionally consistent** | Documents appear in commit order |
|
|
57
|
+
| **Minimal overhead** | Lightweight CDC with negligible database impact |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
### Using Docker (Fastest)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
docker run --rm -it \
|
|
67
|
+
-e PG_URL=postgres://user:pass@host/db \
|
|
68
|
+
-e ELASTICSEARCH_URL=http://localhost:9200 \
|
|
69
|
+
-e REDIS_HOST=localhost \
|
|
70
|
+
-v "$(pwd)/schema.json:/app/schema.json" \
|
|
71
|
+
toluaina1/pgsync:latest -c schema.json -d -b
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Using pip
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install pgsync
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Bootstrap (one-time setup)
|
|
82
|
+
bootstrap --config schema.json
|
|
83
|
+
|
|
84
|
+
# Run sync
|
|
85
|
+
pgsync --config schema.json -d
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Using Docker Compose
|
|
89
|
+
|
|
90
|
+
**Default (Elasticsearch + Kibana):**
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/toluaina/pgsync
|
|
93
|
+
cd pgsync
|
|
94
|
+
docker-compose up
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This starts PostgreSQL, Redis, Elasticsearch, Kibana, and PGSync configured for Elasticsearch.
|
|
98
|
+
|
|
99
|
+
**For OpenSearch:**
|
|
100
|
+
```bash
|
|
101
|
+
docker-compose --profile opensearch up
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This starts PostgreSQL, Redis, OpenSearch, and PGSync configured for OpenSearch.
|
|
105
|
+
|
|
106
|
+
**Ports:**
|
|
107
|
+
- PostgreSQL: `15432`
|
|
108
|
+
- Elasticsearch: `9201` (default)
|
|
109
|
+
- Kibana: `5601` (default)
|
|
110
|
+
- OpenSearch: `9400` (OpenSearch profile)
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## How It Works
|
|
115
|
+
|
|
116
|
+
**1. Define your schema** — Map tables to document structure:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"table": "book",
|
|
121
|
+
"columns": ["isbn", "title", "description"],
|
|
122
|
+
"children": [{
|
|
123
|
+
"table": "author",
|
|
124
|
+
"columns": ["name"]
|
|
125
|
+
}]
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**2. PGSync generates optimized queries** — Complex JOINs handled automatically:
|
|
130
|
+
|
|
131
|
+
```sql
|
|
132
|
+
SELECT JSON_BUILD_OBJECT(
|
|
133
|
+
'isbn', book.isbn,
|
|
134
|
+
'title', book.title,
|
|
135
|
+
'authors', (SELECT JSON_AGG(author.name) FROM author ...)
|
|
136
|
+
) FROM book
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**3. Get denormalized documents** — Ready for search:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"isbn": "9785811243570",
|
|
144
|
+
"title": "Charlie and the Chocolate Factory",
|
|
145
|
+
"authors": ["Roald Dahl"]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Changes to any related table automatically update the document in Elasticsearch/OpenSearch.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Requirements
|
|
154
|
+
|
|
155
|
+
| Component | Version |
|
|
156
|
+
|-----------|---------|
|
|
157
|
+
|  | 3.10+ |
|
|
158
|
+
|  | 9.6+ (or MySQL 5.7.22+ / MariaDB 10.5+) |
|
|
159
|
+
|  | 6.3.1+ (or OpenSearch 1.3.7+) |
|
|
160
|
+
|  | 3.1+ (or Valkey 7.2+) — optional in WAL mode |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Database Setup
|
|
165
|
+
|
|
166
|
+
<details>
|
|
167
|
+
<summary><b>PostgreSQL</b></summary>
|
|
168
|
+
|
|
169
|
+
Enable logical decoding in `postgresql.conf`:
|
|
170
|
+
|
|
171
|
+
```ini
|
|
172
|
+
wal_level = logical
|
|
173
|
+
max_replication_slots = 1
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Optionally limit WAL size:
|
|
177
|
+
|
|
178
|
+
```ini
|
|
179
|
+
max_slot_wal_keep_size = 100GB
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
</details>
|
|
183
|
+
|
|
184
|
+
<details>
|
|
185
|
+
<summary><b>MySQL / MariaDB</b></summary>
|
|
186
|
+
|
|
187
|
+
Enable binary logging in `my.cnf`:
|
|
188
|
+
|
|
189
|
+
```ini
|
|
190
|
+
server-id = 1
|
|
191
|
+
log_bin = mysql-bin
|
|
192
|
+
binlog_row_image = FULL
|
|
193
|
+
binlog_expire_logs_seconds = 604800
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Create replication user:
|
|
197
|
+
|
|
198
|
+
```sql
|
|
199
|
+
CREATE USER 'replicator'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
|
|
200
|
+
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replicator'@'%';
|
|
201
|
+
FLUSH PRIVILEGES;
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
</details>
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Example
|
|
209
|
+
|
|
210
|
+
Consider a book library with related authors:
|
|
211
|
+
|
|
212
|
+
| **Book** | | | |
|
|
213
|
+
|----------|--|--|--|
|
|
214
|
+
| isbn *(PK)* | title | description |
|
|
215
|
+
| 9785811243570 | Charlie and the Chocolate Factory | Willy Wonka's famous... |
|
|
216
|
+
| 9781471331435 | 1984 | George Orwell's chilling... |
|
|
217
|
+
|
|
218
|
+
| **Author** | |
|
|
219
|
+
|------------|--|
|
|
220
|
+
| id *(PK)* | name |
|
|
221
|
+
| 1 | Roald Dahl |
|
|
222
|
+
| 4 | George Orwell |
|
|
223
|
+
|
|
224
|
+
PGSync transforms this into search-ready documents:
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
[
|
|
228
|
+
{
|
|
229
|
+
"isbn": "9785811243570",
|
|
230
|
+
"title": "Charlie and the Chocolate Factory",
|
|
231
|
+
"authors": ["Roald Dahl"]
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"isbn": "9781471331435",
|
|
235
|
+
"title": "1984",
|
|
236
|
+
"authors": ["George Orwell"]
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Any change** — updating an author's name, adding a new book, deleting a relationship — is automatically synced.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Transforms
|
|
246
|
+
|
|
247
|
+
PGSync supports built-in transforms to modify field values before indexing. Transforms are applied in order: `replace` → `rename` → `concat`.
|
|
248
|
+
|
|
249
|
+
### Replace
|
|
250
|
+
|
|
251
|
+
Find and replace substrings within field values:
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"table": "product",
|
|
256
|
+
"columns": ["code", "name"],
|
|
257
|
+
"transform": {
|
|
258
|
+
"replace": {
|
|
259
|
+
"code": {
|
|
260
|
+
"-": "/",
|
|
261
|
+
"_": " "
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
| Before | After |
|
|
269
|
+
|--------|-------|
|
|
270
|
+
| `ABC-DEF_GHI` | `ABC/DEF GHI` |
|
|
271
|
+
|
|
272
|
+
### Rename
|
|
273
|
+
|
|
274
|
+
Rename fields in the output document:
|
|
275
|
+
|
|
276
|
+
```json
|
|
277
|
+
{
|
|
278
|
+
"table": "book",
|
|
279
|
+
"columns": ["id", "title"],
|
|
280
|
+
"transform": {
|
|
281
|
+
"rename": {
|
|
282
|
+
"id": "book_id",
|
|
283
|
+
"title": "book_title"
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Concat
|
|
290
|
+
|
|
291
|
+
Combine multiple fields into a new field:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"table": "user",
|
|
296
|
+
"columns": ["first_name", "last_name"],
|
|
297
|
+
"transform": {
|
|
298
|
+
"concat": {
|
|
299
|
+
"columns": ["first_name", "last_name"],
|
|
300
|
+
"destination": "full_name",
|
|
301
|
+
"delimiter": " "
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Combined Example
|
|
308
|
+
|
|
309
|
+
Transforms can be combined and applied to nested children:
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"table": "book",
|
|
314
|
+
"columns": ["isbn", "title"],
|
|
315
|
+
"children": [{
|
|
316
|
+
"table": "publisher",
|
|
317
|
+
"columns": ["code", "name"],
|
|
318
|
+
"transform": {
|
|
319
|
+
"replace": { "code": { "-": "." } },
|
|
320
|
+
"rename": { "name": "publisher_name" }
|
|
321
|
+
}
|
|
322
|
+
}],
|
|
323
|
+
"transform": {
|
|
324
|
+
"concat": {
|
|
325
|
+
"columns": ["isbn", "title"],
|
|
326
|
+
"destination": "search_text",
|
|
327
|
+
"delimiter": " - "
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Why PGSync?
|
|
336
|
+
|
|
337
|
+
| Challenge | PGSync Solution |
|
|
338
|
+
|-----------|-----------------|
|
|
339
|
+
| Dual writes are error-prone | Captures changes from WAL — single source of truth |
|
|
340
|
+
| Complex JOIN queries | Auto-generates optimized SQL from your schema |
|
|
341
|
+
| Nested document updates | Detects changes in any related table |
|
|
342
|
+
| Data consistency | Transactionally consistent, ordered delivery |
|
|
343
|
+
| Crash recovery | Checkpoint-based resumption |
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Environment Variables
|
|
348
|
+
|
|
349
|
+
Full list at [pgsync.com/env-vars](https://pgsync.com/env-vars)
|
|
350
|
+
|
|
351
|
+
| Variable | Description |
|
|
352
|
+
|----------|-------------|
|
|
353
|
+
| `PG_URL` | PostgreSQL connection string |
|
|
354
|
+
| `ELASTICSEARCH_URL` | Elasticsearch/OpenSearch URL |
|
|
355
|
+
| `REDIS_HOST` | Redis/Valkey host |
|
|
356
|
+
| `REDIS_CHECKPOINT` | Use Redis for checkpoints (recommended for production) |
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## One-Click Deploy
|
|
361
|
+
|
|
362
|
+
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/toluaina/pgsync/tree/main)
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Sponsors
|
|
367
|
+
|
|
368
|
+
<a href="https://www.digitalocean.com/?utm_medium=opensource&utm_source=pgsync">
|
|
369
|
+
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" alt="DigitalOcean" width="200">
|
|
370
|
+
</a>
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Contributing
|
|
375
|
+
|
|
376
|
+
Contributions welcome! See [CONTRIBUTING.rst](CONTRIBUTING.rst) for guidelines.
|
|
377
|
+
|
|
378
|
+
## License
|
|
379
|
+
|
|
380
|
+
[MIT](LICENSE) — use it freely in your projects.
|