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.
Files changed (92) hide show
  1. {pgsync-7.0.2 → pgsync-7.1.0}/MANIFEST.in +1 -0
  2. pgsync-7.1.0/PKG-INFO +263 -0
  3. pgsync-7.1.0/README.md +380 -0
  4. pgsync-7.1.0/README.rst +179 -0
  5. {pgsync-7.0.2 → pgsync-7.1.0}/bin/bootstrap +1 -0
  6. {pgsync-7.0.2 → pgsync-7.1.0}/bin/parallel_sync +26 -33
  7. {pgsync-7.0.2 → pgsync-7.1.0}/bin/pgsync +1 -0
  8. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/__init__.py +1 -1
  9. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/base.py +121 -93
  10. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/constants.py +10 -11
  11. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/node.py +14 -5
  12. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/querybuilder.py +22 -5
  13. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/redisqueue.py +23 -7
  14. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/settings.py +161 -135
  15. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/sync.py +69 -5
  16. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/transform.py +84 -40
  17. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/trigger.py +8 -3
  18. pgsync-7.1.0/pgsync.egg-info/PKG-INFO +263 -0
  19. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync.egg-info/SOURCES.txt +5 -0
  20. pgsync-7.1.0/pgsync.egg-info/requires.txt +36 -0
  21. pgsync-7.1.0/pyproject.toml +3 -0
  22. pgsync-7.1.0/requirements/base.txt +118 -0
  23. pgsync-7.1.0/requirements/dev.txt +205 -0
  24. {pgsync-7.0.2 → pgsync-7.1.0}/setup.py +3 -3
  25. {pgsync-7.0.2 → pgsync-7.1.0}/tests/conftest.py +31 -0
  26. pgsync-7.1.0/tests/test_base.py +1599 -0
  27. pgsync-7.1.0/tests/test_bug_regressions.py +136 -0
  28. pgsync-7.1.0/tests/test_exc.py +322 -0
  29. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_node.py +359 -0
  30. pgsync-7.1.0/tests/test_plugin.py +437 -0
  31. pgsync-7.1.0/tests/test_query_builder.py +2552 -0
  32. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_redisqueue.py +58 -0
  33. pgsync-7.1.0/tests/test_search_client.py +648 -0
  34. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_settings.py +4 -1
  35. pgsync-7.1.0/tests/test_sync.py +3210 -0
  36. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_transform.py +474 -1
  37. pgsync-7.1.0/tests/test_utils.py +775 -0
  38. pgsync-7.1.0/tests/test_view.py +662 -0
  39. pgsync-7.0.2/PKG-INFO +0 -210
  40. pgsync-7.0.2/README.md +0 -396
  41. pgsync-7.0.2/README.rst +0 -129
  42. pgsync-7.0.2/pgsync.egg-info/PKG-INFO +0 -210
  43. pgsync-7.0.2/pgsync.egg-info/requires.txt +0 -32
  44. pgsync-7.0.2/pyproject.toml +0 -3
  45. pgsync-7.0.2/tests/test_base.py +0 -676
  46. pgsync-7.0.2/tests/test_query_builder.py +0 -102
  47. pgsync-7.0.2/tests/test_search_client.py +0 -115
  48. pgsync-7.0.2/tests/test_sync.py +0 -1450
  49. pgsync-7.0.2/tests/test_utils.py +0 -229
  50. pgsync-7.0.2/tests/test_view.py +0 -302
  51. {pgsync-7.0.2 → pgsync-7.1.0}/AUTHORS.rst +0 -0
  52. {pgsync-7.0.2 → pgsync-7.1.0}/CONTRIBUTING.rst +0 -0
  53. {pgsync-7.0.2 → pgsync-7.1.0}/HISTORY.rst +0 -0
  54. {pgsync-7.0.2 → pgsync-7.1.0}/LICENSE +0 -0
  55. {pgsync-7.0.2 → pgsync-7.1.0}/docs/Makefile +0 -0
  56. {pgsync-7.0.2 → pgsync-7.1.0}/docs/authors.rst +0 -0
  57. {pgsync-7.0.2 → pgsync-7.1.0}/docs/changelog.rst +0 -0
  58. {pgsync-7.0.2 → pgsync-7.1.0}/docs/conf.py +0 -0
  59. {pgsync-7.0.2 → pgsync-7.1.0}/docs/contributing.rst +0 -0
  60. {pgsync-7.0.2 → pgsync-7.1.0}/docs/history.rst +0 -0
  61. {pgsync-7.0.2 → pgsync-7.1.0}/docs/index.rst +0 -0
  62. {pgsync-7.0.2 → pgsync-7.1.0}/docs/installation.rst +0 -0
  63. {pgsync-7.0.2 → pgsync-7.1.0}/docs/logo.png +0 -0
  64. {pgsync-7.0.2 → pgsync-7.1.0}/docs/make.bat +0 -0
  65. {pgsync-7.0.2 → pgsync-7.1.0}/docs/readme.rst +0 -0
  66. {pgsync-7.0.2 → pgsync-7.1.0}/docs/usage.rst +0 -0
  67. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/exc.py +0 -0
  68. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/helper.py +0 -0
  69. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/plugin.py +0 -0
  70. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/search_client.py +0 -0
  71. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/singleton.py +0 -0
  72. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/urls.py +0 -0
  73. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/utils.py +0 -0
  74. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync/view.py +0 -0
  75. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync.egg-info/dependency_links.txt +0 -0
  76. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync.egg-info/not-zip-safe +0 -0
  77. {pgsync-7.0.2 → pgsync-7.1.0}/pgsync.egg-info/top_level.txt +0 -0
  78. {pgsync-7.0.2 → pgsync-7.1.0}/setup.cfg +0 -0
  79. {pgsync-7.0.2 → pgsync-7.1.0}/tests/__init__.py +0 -0
  80. {pgsync-7.0.2 → pgsync-7.1.0}/tests/fixtures/schema.json +0 -0
  81. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_constants.py +0 -0
  82. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_env_vars.py +0 -0
  83. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_helper.py +0 -0
  84. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_log_handlers.py +0 -0
  85. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_sync_nested_children.py +0 -0
  86. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_sync_root.py +0 -0
  87. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_sync_single_child_fk_on_child.py +0 -0
  88. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_sync_single_child_fk_on_parent.py +0 -0
  89. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_trigger.py +0 -0
  90. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_unique_behaviour.py +0 -0
  91. {pgsync-7.0.2 → pgsync-7.1.0}/tests/test_urls.py +0 -0
  92. {pgsync-7.0.2 → pgsync-7.1.0}/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.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
+ [![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
+ %%{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
+ | ![Python](https://img.shields.io/badge/Python-3.10+-3776AB?logo=python&logoColor=white) | 3.10+ |
158
+ | ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-9.6+-4169E1?logo=postgresql&logoColor=white) | 9.6+ (or MySQL 5.7.22+ / MariaDB 10.5+) |
159
+ | ![Elasticsearch](https://img.shields.io/badge/Elasticsearch-6.3+-005571?logo=elasticsearch&logoColor=white) | 6.3.1+ (or OpenSearch 1.3.7+) |
160
+ | ![Redis](https://img.shields.io/badge/Redis-3.1+-DC382D?logo=redis&logoColor=white) | 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
+ [![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)
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.