pgsync 7.0.1__tar.gz → 7.0.5__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.
Files changed (81) hide show
  1. {pgsync-7.0.1 → pgsync-7.0.5}/MANIFEST.in +1 -0
  2. pgsync-7.0.5/PKG-INFO +261 -0
  3. pgsync-7.0.5/README.md +273 -0
  4. pgsync-7.0.5/README.rst +179 -0
  5. {pgsync-7.0.1 → pgsync-7.0.5}/bin/parallel_sync +25 -33
  6. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/__init__.py +1 -1
  7. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/base.py +71 -19
  8. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/constants.py +25 -13
  9. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/node.py +9 -1
  10. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/querybuilder.py +4 -4
  11. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/settings.py +152 -132
  12. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/sync.py +2 -1
  13. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/trigger.py +8 -3
  14. pgsync-7.0.5/pgsync.egg-info/PKG-INFO +261 -0
  15. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync.egg-info/SOURCES.txt +2 -0
  16. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync.egg-info/requires.txt +4 -4
  17. pgsync-7.0.5/requirements/base.txt +105 -0
  18. pgsync-7.0.5/requirements/dev.txt +192 -0
  19. {pgsync-7.0.1 → pgsync-7.0.5}/setup.py +1 -1
  20. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_base.py +2 -2
  21. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_constants.py +228 -0
  22. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_sync.py +2 -6
  23. pgsync-7.0.1/PKG-INFO +0 -211
  24. pgsync-7.0.1/README.md +0 -396
  25. pgsync-7.0.1/README.rst +0 -129
  26. pgsync-7.0.1/pgsync.egg-info/PKG-INFO +0 -211
  27. {pgsync-7.0.1 → pgsync-7.0.5}/AUTHORS.rst +0 -0
  28. {pgsync-7.0.1 → pgsync-7.0.5}/CONTRIBUTING.rst +0 -0
  29. {pgsync-7.0.1 → pgsync-7.0.5}/HISTORY.rst +0 -0
  30. {pgsync-7.0.1 → pgsync-7.0.5}/LICENSE +0 -0
  31. {pgsync-7.0.1 → pgsync-7.0.5}/bin/bootstrap +0 -0
  32. {pgsync-7.0.1 → pgsync-7.0.5}/bin/pgsync +0 -0
  33. {pgsync-7.0.1 → pgsync-7.0.5}/docs/Makefile +0 -0
  34. {pgsync-7.0.1 → pgsync-7.0.5}/docs/authors.rst +0 -0
  35. {pgsync-7.0.1 → pgsync-7.0.5}/docs/changelog.rst +0 -0
  36. {pgsync-7.0.1 → pgsync-7.0.5}/docs/conf.py +0 -0
  37. {pgsync-7.0.1 → pgsync-7.0.5}/docs/contributing.rst +0 -0
  38. {pgsync-7.0.1 → pgsync-7.0.5}/docs/history.rst +0 -0
  39. {pgsync-7.0.1 → pgsync-7.0.5}/docs/index.rst +0 -0
  40. {pgsync-7.0.1 → pgsync-7.0.5}/docs/installation.rst +0 -0
  41. {pgsync-7.0.1 → pgsync-7.0.5}/docs/logo.png +0 -0
  42. {pgsync-7.0.1 → pgsync-7.0.5}/docs/make.bat +0 -0
  43. {pgsync-7.0.1 → pgsync-7.0.5}/docs/readme.rst +0 -0
  44. {pgsync-7.0.1 → pgsync-7.0.5}/docs/usage.rst +0 -0
  45. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/exc.py +0 -0
  46. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/helper.py +0 -0
  47. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/plugin.py +0 -0
  48. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/redisqueue.py +0 -0
  49. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/search_client.py +0 -0
  50. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/singleton.py +0 -0
  51. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/transform.py +0 -0
  52. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/urls.py +0 -0
  53. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/utils.py +0 -0
  54. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync/view.py +0 -0
  55. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync.egg-info/dependency_links.txt +0 -0
  56. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync.egg-info/not-zip-safe +0 -0
  57. {pgsync-7.0.1 → pgsync-7.0.5}/pgsync.egg-info/top_level.txt +0 -0
  58. {pgsync-7.0.1 → pgsync-7.0.5}/pyproject.toml +0 -0
  59. {pgsync-7.0.1 → pgsync-7.0.5}/setup.cfg +0 -0
  60. {pgsync-7.0.1 → pgsync-7.0.5}/tests/__init__.py +0 -0
  61. {pgsync-7.0.1 → pgsync-7.0.5}/tests/conftest.py +0 -0
  62. {pgsync-7.0.1 → pgsync-7.0.5}/tests/fixtures/schema.json +0 -0
  63. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_env_vars.py +0 -0
  64. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_helper.py +0 -0
  65. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_log_handlers.py +0 -0
  66. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_node.py +0 -0
  67. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_query_builder.py +0 -0
  68. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_redisqueue.py +0 -0
  69. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_search_client.py +0 -0
  70. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_settings.py +0 -0
  71. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_sync_nested_children.py +0 -0
  72. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_sync_root.py +0 -0
  73. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_sync_single_child_fk_on_child.py +0 -0
  74. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_sync_single_child_fk_on_parent.py +0 -0
  75. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_transform.py +0 -0
  76. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_trigger.py +0 -0
  77. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_unique_behaviour.py +0 -0
  78. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_urls.py +0 -0
  79. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_utils.py +0 -0
  80. {pgsync-7.0.1 → pgsync-7.0.5}/tests/test_view.py +0 -0
  81. {pgsync-7.0.1 → pgsync-7.0.5}/tests/testing_utils.py +0 -0
@@ -4,6 +4,7 @@ include HISTORY.rst
4
4
  include LICENSE
5
5
  include README.md
6
6
  include README.rst
7
+ recursive-include requirements *.txt
7
8
 
8
9
  recursive-include tests *
9
10
  recursive-exclude * __pycache__
pgsync-7.0.5/PKG-INFO ADDED
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: pgsync
3
+ Version: 7.0.5
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.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Programming Language :: Python :: Implementation :: CPython
27
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
28
+ Classifier: License :: OSI Approved :: MIT License
29
+ Classifier: Operating System :: OS Independent
30
+ Requires-Python: >=3.9.0
31
+ Description-Content-Type: text/x-rst
32
+ License-File: LICENSE
33
+ License-File: AUTHORS.rst
34
+ Requires-Dist: async-timeout==5.0.1
35
+ Requires-Dist: backports-datetime-fromisoformat==2.0.3
36
+ Requires-Dist: boto3==1.42.21
37
+ Requires-Dist: botocore==1.42.21
38
+ Requires-Dist: certifi==2026.1.4
39
+ Requires-Dist: charset-normalizer==3.4.4
40
+ Requires-Dist: click==8.1.8
41
+ Requires-Dist: elastic-transport==9.1.0
42
+ Requires-Dist: elasticsearch==7.17.12
43
+ Requires-Dist: elasticsearch-dsl==7.4.1
44
+ Requires-Dist: environs==14.4.0
45
+ Requires-Dist: events==0.5
46
+ Requires-Dist: greenlet==3.2.4
47
+ Requires-Dist: idna==3.11
48
+ Requires-Dist: jmespath==1.0.1
49
+ Requires-Dist: marshmallow==4.0.1
50
+ Requires-Dist: mysql-replication==1.0.12
51
+ Requires-Dist: opensearch-dsl==2.1.0
52
+ Requires-Dist: opensearch-py==3.0.0
53
+ Requires-Dist: packaging==25.0
54
+ Requires-Dist: psycopg2-binary==2.9.11
55
+ Requires-Dist: pymysql==1.1.2
56
+ Requires-Dist: python-dateutil==2.9.0.post0
57
+ Requires-Dist: python-dotenv==1.2.1
58
+ Requires-Dist: redis==7.0.1
59
+ Requires-Dist: requests==2.32.5
60
+ Requires-Dist: requests-aws4auth==1.3.1
61
+ Requires-Dist: s3transfer==0.16.0
62
+ Requires-Dist: six==1.17.0
63
+ Requires-Dist: sqlalchemy==2.0.45
64
+ Requires-Dist: sqlparse==0.5.5
65
+ Requires-Dist: typing-extensions==4.15.0
66
+ Requires-Dist: urllib3==1.26.20
67
+ Dynamic: author
68
+ Dynamic: author-email
69
+ Dynamic: classifier
70
+ Dynamic: description
71
+ Dynamic: description-content-type
72
+ Dynamic: home-page
73
+ Dynamic: keywords
74
+ Dynamic: license
75
+ Dynamic: license-file
76
+ Dynamic: maintainer
77
+ Dynamic: maintainer-email
78
+ Dynamic: project-url
79
+ Dynamic: requires-dist
80
+ Dynamic: requires-python
81
+ Dynamic: summary
82
+
83
+ ======
84
+ PGSync
85
+ ======
86
+
87
+ .. image:: https://img.shields.io/pypi/v/pgsync.svg
88
+ :target: https://pypi.org/project/pgsync/
89
+ :alt: PyPI Version
90
+
91
+ .. image:: https://img.shields.io/pypi/pyversions/pgsync.svg
92
+ :target: https://pypi.org/project/pgsync/
93
+ :alt: Python Versions
94
+
95
+ .. image:: https://img.shields.io/pypi/l/pgsync.svg
96
+ :target: https://opensource.org/licenses/MIT
97
+ :alt: License
98
+
99
+ .. image:: https://img.shields.io/pypi/dm/pgsync.svg
100
+ :target: https://pypi.org/project/pgsync/
101
+ :alt: Downloads
102
+
103
+ |
104
+
105
+ **PostgreSQL/MySQL/MariaDB to Elasticsearch/OpenSearch sync**
106
+
107
+ `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>`_.
108
+
109
+ Keep your relational database as the source of truth and expose structured denormalized documents in your search engine.
110
+
111
+ Key Features
112
+ ------------
113
+
114
+ - Real-time sync via logical decoding (PostgreSQL) or binary log (MySQL/MariaDB)
115
+ - Denormalize complex relational data into nested search documents
116
+ - JSON schema-based configuration
117
+ - Support for one-to-one, one-to-many relationships
118
+ - Plugin system for document transformation
119
+ - Multiple operation modes: daemon, polling, or direct WAL streaming
120
+
121
+ Requirements
122
+ ------------
123
+
124
+ - `Python <https://www.python.org>`_ 3.9+
125
+ - `PostgreSQL <https://www.postgresql.org>`_ 9.6+ or `MySQL <https://www.mysql.com>`_ 8.0.0+ or `MariaDB <https://mariadb.org>`_ 12.0.0+
126
+ - `Redis <https://redis.io>`_ 3.1.0+ or `Valkey <https://valkey.io>`_ 7.2.0+ (optional in WAL mode)
127
+ - `Elasticsearch <https://www.elastic.co/products/elastic-stack>`_ 6.3.1+ or `OpenSearch <https://opensearch.org>`_ 1.3.7+
128
+
129
+ Installation
130
+ ------------
131
+
132
+ Install from `PyPI <https://pypi.org/project/pgsync/>`_:
133
+
134
+ .. code-block:: bash
135
+
136
+ pip install pgsync
137
+
138
+ Database Setup
139
+ --------------
140
+
141
+ PostgreSQL
142
+ ~~~~~~~~~~
143
+
144
+ Enable `logical decoding <https://www.postgresql.org/docs/current/logicaldecoding.html>`_ in your PostgreSQL configuration (``postgresql.conf``):
145
+
146
+ .. code-block:: ini
147
+
148
+ wal_level = logical
149
+ max_replication_slots = 1
150
+
151
+ MySQL / MariaDB
152
+ ~~~~~~~~~~~~~~~
153
+
154
+ Enable binary logging in your MySQL/MariaDB configuration (``my.cnf``):
155
+
156
+ .. code-block:: ini
157
+
158
+ server-id = 1
159
+ log_bin = mysql-bin
160
+ binlog_row_image = FULL
161
+ binlog_expire_logs_seconds = 604800
162
+
163
+ Create a replication user:
164
+
165
+ .. code-block:: sql
166
+
167
+ CREATE USER 'replicator'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
168
+ GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replicator'@'%';
169
+ FLUSH PRIVILEGES;
170
+
171
+ Configuration
172
+ -------------
173
+
174
+ Create a JSON schema file (e.g., ``schema.json``) defining your sync mapping:
175
+
176
+ .. code-block:: json
177
+
178
+ [
179
+ {
180
+ "database": "book",
181
+ "index": "book",
182
+ "nodes": {
183
+ "table": "book",
184
+ "columns": ["isbn", "title", "description"],
185
+ "children": [
186
+ {
187
+ "table": "publisher",
188
+ "columns": ["name"],
189
+ "relationship": {
190
+ "variant": "object",
191
+ "type": "one_to_one"
192
+ }
193
+ },
194
+ {
195
+ "table": "author",
196
+ "label": "authors",
197
+ "columns": ["name", "date_of_birth"],
198
+ "relationship": {
199
+ "variant": "object",
200
+ "type": "one_to_many",
201
+ "through_tables": ["book_author"]
202
+ }
203
+ }
204
+ ]
205
+ }
206
+ }
207
+ ]
208
+
209
+ See the `examples directory <https://github.com/toluaina/pgsync/tree/main/examples>`_ for more schema examples (airbnb, social, rental, etc.).
210
+
211
+ Environment Variables
212
+ ---------------------
213
+
214
+ Configure PGSync via environment variables:
215
+
216
+ .. code-block:: bash
217
+
218
+ # Schema
219
+ SCHEMA='/path/to/schema.json'
220
+
221
+ # PostgreSQL
222
+ PG_HOST=localhost
223
+ PG_PORT=5432
224
+ PG_USER=postgres
225
+ PG_PASSWORD=*****
226
+
227
+ # Elasticsearch / OpenSearch
228
+ ELASTICSEARCH_HOST=localhost
229
+ ELASTICSEARCH_PORT=9200
230
+
231
+ # Redis (optional in WAL mode)
232
+ REDIS_HOST=localhost
233
+ REDIS_PORT=6379
234
+
235
+ Running
236
+ -------
237
+
238
+ **Bootstrap** (run once to set up triggers and replication slots):
239
+
240
+ .. code-block:: bash
241
+
242
+ bootstrap --config schema.json
243
+
244
+ **Run as daemon**:
245
+
246
+ .. code-block:: bash
247
+
248
+ pgsync --config schema.json --daemon
249
+
250
+ Links
251
+ -----
252
+
253
+ - **Documentation**: https://pgsync.com
254
+ - **Source Code**: https://github.com/toluaina/pgsync
255
+ - **Bug Reports**: https://github.com/toluaina/pgsync/issues
256
+ - **Sponsor**: https://github.com/sponsors/toluaina
257
+
258
+ License
259
+ -------
260
+
261
+ MIT License - see `LICENSE <https://github.com/toluaina/pgsync/blob/main/LICENSE>`_ for details.
pgsync-7.0.5/README.md ADDED
@@ -0,0 +1,273 @@
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
+ [![PyPI version](https://img.shields.io/pypi/v/pgsync?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/pgsync)
10
+ [![Python versions](https://img.shields.io/pypi/pyversions/pgsync?logo=python&logoColor=white)](https://pypi.org/project/pgsync)
11
+ [![Build status](https://img.shields.io/github/actions/workflow/status/toluaina/pgsync/python-build.yml?branch=main&logo=github)](https://github.com/toluaina/pgsync/actions)
12
+ [![codecov](https://img.shields.io/codecov/c/github/toluaina/pgsync?logo=codecov&logoColor=white)](https://codecov.io/gh/toluaina/pgsync)
13
+ [![Downloads](https://img.shields.io/pypi/dm/pgsync?color=brightgreen&logo=pypi&logoColor=white)](https://pypi.org/project/pgsync)
14
+ [![License](https://img.shields.io/github/license/toluaina/pgsync?color=blue)](LICENSE)
15
+
16
+ [![Docker](https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white)](https://hub.docker.com/r/toluaina1/pgsync)
17
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](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
+ flowchart LR
31
+ subgraph Source["🗄️ Source Database"]
32
+ DB[(PostgreSQL<br/>MySQL<br/>MariaDB)]
33
+ end
34
+
35
+ subgraph CDC["⚡ Change Data Capture"]
36
+ P[PGSync]
37
+ end
38
+
39
+ subgraph Search["🔍 Search Engine"]
40
+ ES[(Elasticsearch<br/>OpenSearch)]
41
+ end
42
+
43
+ DB -->|WAL / Binlog| P
44
+ P -->|Bulk Index| ES
45
+ ```
46
+
47
+ ### Key Features
48
+
49
+ | Feature | Description |
50
+ |---------|-------------|
51
+ | **Real-time sync** | Changes propagate instantly via logical replication |
52
+ | **Zero code** | Define mappings in JSON — no ETL pipelines to build |
53
+ | **Nested documents** | Automatically denormalize complex relationships |
54
+ | **Fault tolerant** | Resumes from checkpoints after crashes |
55
+ | **Transactionally consistent** | Documents appear in commit order |
56
+ | **Minimal overhead** | Lightweight CDC with negligible database impact |
57
+
58
+ ---
59
+
60
+ ## Quick Start
61
+
62
+ ### Using Docker (Fastest)
63
+
64
+ ```bash
65
+ docker run --rm -it \
66
+ -e PG_URL=postgres://user:pass@host/db \
67
+ -e ELASTICSEARCH_URL=http://localhost:9200 \
68
+ -e REDIS_HOST=localhost \
69
+ -v "$(pwd)/schema.json:/app/schema.json" \
70
+ toluaina1/pgsync:latest -c schema.json -d -b
71
+ ```
72
+
73
+ ### Using pip
74
+
75
+ ```bash
76
+ pip install pgsync
77
+ ```
78
+
79
+ ```bash
80
+ # Bootstrap (one-time setup)
81
+ bootstrap --config schema.json
82
+
83
+ # Run sync
84
+ pgsync --config schema.json -d
85
+ ```
86
+
87
+ ### Using Docker Compose
88
+
89
+ ```bash
90
+ git clone https://github.com/toluaina/pgsync
91
+ cd pgsync
92
+ docker-compose up
93
+ ```
94
+
95
+ ---
96
+
97
+ ## How It Works
98
+
99
+ **1. Define your schema** — Map tables to document structure:
100
+
101
+ ```json
102
+ {
103
+ "table": "book",
104
+ "columns": ["isbn", "title", "description"],
105
+ "children": [{
106
+ "table": "author",
107
+ "columns": ["name"]
108
+ }]
109
+ }
110
+ ```
111
+
112
+ **2. PGSync generates optimized queries** — Complex JOINs handled automatically:
113
+
114
+ ```sql
115
+ SELECT JSON_BUILD_OBJECT(
116
+ 'isbn', book.isbn,
117
+ 'title', book.title,
118
+ 'authors', (SELECT JSON_AGG(author.name) FROM author ...)
119
+ ) FROM book
120
+ ```
121
+
122
+ **3. Get denormalized documents** — Ready for search:
123
+
124
+ ```json
125
+ {
126
+ "isbn": "9785811243570",
127
+ "title": "Charlie and the Chocolate Factory",
128
+ "authors": ["Roald Dahl"]
129
+ }
130
+ ```
131
+
132
+ Changes to any related table automatically update the document in Elasticsearch/OpenSearch.
133
+
134
+ ---
135
+
136
+ ## Requirements
137
+
138
+ | Component | Version |
139
+ |-----------|---------|
140
+ | ![Python](https://img.shields.io/badge/Python-3.9+-3776AB?logo=python&logoColor=white) | 3.9+ |
141
+ | ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-9.6+-4169E1?logo=postgresql&logoColor=white) | 9.6+ (or MySQL 5.7.22+ / MariaDB 10.5+) |
142
+ | ![Elasticsearch](https://img.shields.io/badge/Elasticsearch-6.3+-005571?logo=elasticsearch&logoColor=white) | 6.3.1+ (or OpenSearch 1.3.7+) |
143
+ | ![Redis](https://img.shields.io/badge/Redis-3.1+-DC382D?logo=redis&logoColor=white) | 3.1+ (or Valkey 7.2+) — optional in WAL mode |
144
+
145
+ ---
146
+
147
+ ## Database Setup
148
+
149
+ <details>
150
+ <summary><b>PostgreSQL</b></summary>
151
+
152
+ Enable logical decoding in `postgresql.conf`:
153
+
154
+ ```ini
155
+ wal_level = logical
156
+ max_replication_slots = 1
157
+ ```
158
+
159
+ Optionally limit WAL size:
160
+
161
+ ```ini
162
+ max_slot_wal_keep_size = 100GB
163
+ ```
164
+
165
+ </details>
166
+
167
+ <details>
168
+ <summary><b>MySQL / MariaDB</b></summary>
169
+
170
+ Enable binary logging in `my.cnf`:
171
+
172
+ ```ini
173
+ server-id = 1
174
+ log_bin = mysql-bin
175
+ binlog_row_image = FULL
176
+ binlog_expire_logs_seconds = 604800
177
+ ```
178
+
179
+ Create replication user:
180
+
181
+ ```sql
182
+ CREATE USER 'replicator'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
183
+ GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replicator'@'%';
184
+ FLUSH PRIVILEGES;
185
+ ```
186
+
187
+ </details>
188
+
189
+ ---
190
+
191
+ ## Example
192
+
193
+ Consider a book library with related authors:
194
+
195
+ | **Book** | | | |
196
+ |----------|--|--|--|
197
+ | isbn *(PK)* | title | description |
198
+ | 9785811243570 | Charlie and the Chocolate Factory | Willy Wonka's famous... |
199
+ | 9781471331435 | 1984 | George Orwell's chilling... |
200
+
201
+ | **Author** | |
202
+ |------------|--|
203
+ | id *(PK)* | name |
204
+ | 1 | Roald Dahl |
205
+ | 4 | George Orwell |
206
+
207
+ PGSync transforms this into search-ready documents:
208
+
209
+ ```json
210
+ [
211
+ {
212
+ "isbn": "9785811243570",
213
+ "title": "Charlie and the Chocolate Factory",
214
+ "authors": ["Roald Dahl"]
215
+ },
216
+ {
217
+ "isbn": "9781471331435",
218
+ "title": "1984",
219
+ "authors": ["George Orwell"]
220
+ }
221
+ ]
222
+ ```
223
+
224
+ **Any change** — updating an author's name, adding a new book, deleting a relationship — is automatically synced.
225
+
226
+ ---
227
+
228
+ ## Why PGSync?
229
+
230
+ | Challenge | PGSync Solution |
231
+ |-----------|-----------------|
232
+ | Dual writes are error-prone | Captures changes from WAL — single source of truth |
233
+ | Complex JOIN queries | Auto-generates optimized SQL from your schema |
234
+ | Nested document updates | Detects changes in any related table |
235
+ | Data consistency | Transactionally consistent, ordered delivery |
236
+ | Crash recovery | Checkpoint-based resumption |
237
+
238
+ ---
239
+
240
+ ## Environment Variables
241
+
242
+ Full list at [pgsync.com/env-vars](https://pgsync.com/env-vars)
243
+
244
+ | Variable | Description |
245
+ |----------|-------------|
246
+ | `PG_URL` | PostgreSQL connection string |
247
+ | `ELASTICSEARCH_URL` | Elasticsearch/OpenSearch URL |
248
+ | `REDIS_HOST` | Redis/Valkey host |
249
+ | `REDIS_CHECKPOINT` | Use Redis for checkpoints (recommended for production) |
250
+
251
+ ---
252
+
253
+ ## One-Click Deploy
254
+
255
+ [![Deploy to DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/toluaina/pgsync/tree/main)
256
+
257
+ ---
258
+
259
+ ## Sponsors
260
+
261
+ <a href="https://www.digitalocean.com/?utm_medium=opensource&utm_source=pgsync">
262
+ <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" alt="DigitalOcean" width="200">
263
+ </a>
264
+
265
+ ---
266
+
267
+ ## Contributing
268
+
269
+ Contributions welcome! See [CONTRIBUTING.rst](CONTRIBUTING.rst) for guidelines.
270
+
271
+ ## License
272
+
273
+ [MIT](LICENSE) — use it freely in your projects.