├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── NOTICE
├── README.cn.md
├── README.md
├── expected
├── actions.out
├── array.out
├── binData.out
├── binary.out
├── boolean.out
├── identity.out
├── json.out
├── numeric.out
├── pkey.out
├── specval.out
├── specval_1.out
├── string.out
├── timestamptz.out
├── transaction.out
├── xml.out
└── xml_1.out
├── logical.conf
├── sql
├── actions.sql
├── array.sql
├── binData.sql
├── binary.sql
├── boolean.sql
├── identity.sql
├── json.sql
├── numeric.sql
├── pkey.sql
├── specval.sql
├── string.sql
├── timestamptz.sql
├── transaction.sql
└── xml.sql
└── wal2mongo.c
/.gitignore:
--------------------------------------------------------------------------------
1 | # regressio test outputs
2 | /regression.diffs
3 | /regression.out
4 | /results/
5 |
6 | # Prerequisites
7 | *.d
8 |
9 | # Object files
10 | *.o
11 | *.ko
12 | *.obj
13 | *.elf
14 |
15 | # Linker output
16 | *.ilk
17 | *.map
18 | *.exp
19 |
20 | # Precompiled Headers
21 | *.gch
22 | *.pch
23 |
24 | # Libraries
25 | *.lib
26 | *.a
27 | *.la
28 | *.lo
29 |
30 | # Shared objects (inc. Windows DLLs)
31 | *.dll
32 | *.so
33 | *.so.*
34 | *.dylib
35 |
36 | # Executables
37 | *.exe
38 | *.app
39 | *.i*86
40 | *.x86_64
41 | *.hex
42 |
43 | # Debug files
44 | *.dSYM/
45 | *.su
46 | *.idb
47 | *.pdb
48 |
49 | # Kernel Module Compile Results
50 | *.mod*
51 | *.cmd
52 | .tmp_versions/
53 | modules.order
54 | Module.symvers
55 | Mkfile.old
56 | dkms.conf
57 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # run the testsuite on travis-ci.org
2 | ---
3 | # run once for each of these
4 | env:
5 | global:
6 | - enable_coverage=yes
7 | - PGVERSION=12
8 |
9 | language: C
10 | dist: bionic
11 | sudo: required
12 |
13 | before_install:
14 | - sudo apt-get update -qq
15 | - pip install --user cpp-coveralls
16 |
17 | install:
18 | # remove all existing postgresql
19 | - sudo rm -rf /etc/postgresql /var/lib/postgresql
20 | - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
21 | - echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" |sudo tee /etc/apt/sources.list.d/pgdg.list
22 | - sudo apt update
23 | - sudo pip install cpp-coveralls
24 | - sudo apt -y install postgresql-12 postgresql-client-12 postgresql-server-dev-12
25 | - sudo apt-get install -y postgresql-common
26 | - sudo apt-get install -y bison flex libicu-dev libssl-dev git
27 | - sudo -u postgres createuser --superuser $USER
28 | - sudo sed -i 's/#wal_level = replica/wal_level = logical/g' /etc/postgresql/12/main/postgresql.conf
29 | - sudo service postgresql@12-main restart
30 | - git clone https://github.com/idrawone/wal2mongo.git
31 |
32 | script:
33 | - cd wal2mongo
34 | - export PATH=/usr/lib/postgresql/12/bin:$PATH
35 | - USE_PGXS=1 make
36 | - sudo USE_PGXS=1 make install
37 | - USE_PGXS=1 make installcheck-force
38 | - if test -s regression.diffs; then cat regression.diffs; cat results/string.out; cat expected/string.out; fi
39 |
40 | after_success:
41 | - coveralls --gcov-options '\-lp'
42 |
43 | deploy:
44 | provider: releases
45 | api_key:
46 | secure: "Cikugg6yUXIyHoRYN+oLzSbRNyJ5N7JR1IYkJjgBS2ZogqxMHhZszdYCTI7FyfdzzUA3qJLiARuYpOjtk/OxzZ1b4c1+kk4miwjeNEfSZFhEUfhPq9sAD+gBq/akCQhFUWf5OQiA/DQBivL0sVIxmHc8oE9YpMMKPze2WQZvQO/g6fctzkP2eKVA2/Mq+JO4uU/VCm0PTNKNSEnO54j+riKf3qsElOzJ9GX5kWl3gOV4UQoAn+GzvH7SwzB5iex3FXEtSPuW5d80LmtOb5vJB3YJH9Yk49DMhER5W6BBgcYC/BNgfpHjSeR5fCDV4pfmfkSLD6mV2Rsy2ke8NQDFeVXbr8SZtMsEAfdTb7M8ima+CtjoawBe/SwNxEA5QuhpuQGeAvfSJzAMU+dGvvajtC1dU6GehWvBcdbxiU7VcWjItfCrV9BVh3zCdo+1keAkpO078eeb5C8qiqK4yiJ6GN1S7jIx9Y6I9VeNLjOqIRuxjt4dqmT7LBNsksA1SGddvt5jzQXjGzV34P26b31aba0Xf0eCPqpMEcv28O7vB2wBB05TC+yjgUWBUrWvVz3sDcTrhBFxlZwQc2li37brIx5bHB4lG2SG8KoUatujYUuNP2mifJaW5lXv9ibIefPac+SNjG4A+lW7x35/vvVcwcOoFK9fgZav+Q8JbzbrPH8="
47 | file_glob: true
48 | file: "*.so"
49 | skip_cleanup: true
50 | on:
51 | repo: HighgoSoftware/wal2mongo
52 | branch: release
53 | tags: true
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # contrib/wal2mongo/Makefile
2 |
3 | MODULES = wal2mongo
4 | PGFILEDESC = "wal2mongo - a logical decoding output plugin for MongoDB"
5 |
6 | REGRESS = binary actions transaction boolean numeric binData timestamptz \
7 | array json string xml identity pkey specval
8 |
9 | #ISOLATION = mxact delayed_startup ondisk_startup concurrent_ddl_dml \
10 | oldest_xmin snapshot_transfer
11 |
12 | REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/wal2mongo/logical.conf
13 | #ISOLATION_OPTS = --temp-config $(top_srcdir)/contrib/wal2mongo/logical.conf
14 |
15 | # Disabled because these tests require "wal_level=logical", which
16 | # typical installcheck users do not have (e.g. buildfarm clients).
17 | NO_INSTALLCHECK = 0
18 |
19 | ifdef USE_PGXS
20 | PG_CONFIG = pg_config
21 | PGXS := $(shell $(PG_CONFIG) --pgxs)
22 | include $(PGXS)
23 | else
24 | subdir = contrib/wal2mongo
25 | top_builddir = ../..
26 | include $(top_builddir)/src/Makefile.global
27 | include $(top_srcdir)/contrib/contrib-global.mk
28 | endif
29 |
30 | # But it can nonetheless be very helpful to run tests on preexisting
31 | # installation, allow to do so, but only if requested explicitly.
32 | installcheck-force:
33 | $(pg_regress_installcheck) $(REGRESS)
34 | #$(pg_isolation_regress_installcheck) $(ISOLATION)
35 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 HighGo Software, Inc. (Canada). All Rights Reserved.
2 |
3 | This project contains software that is Copyright (c) 2020 HighGo Software, Inc.
4 |
5 | This project is licensed to you under the Apache License, Version 2.0 (the "License").
6 |
7 | You may not use this project except in compliance with the License.
8 |
9 | This project may include a number of subcomponents with separate copyright notices
10 | and license terms. Your use of these subcomponents is subject to the terms and
11 | conditions of the subcomponent's license, as noted in the LICENSE file.
12 |
--------------------------------------------------------------------------------
/README.cn.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | [](https://github.com/HighgoSoftware/wal2mongo/releases)
4 |
5 | ### 介绍
6 | `wal2mongo` 是一个PostgreSQL逻辑解码输出插件,用来输出mongo能够接受JSON格式,使PostgreSQL到MongoDB的逻辑复制更加容易。
7 |
8 | ### 使用条件
9 | 要使用wal2mongo逻辑解码输出插件,需要安装以下PostgreSQL服务器之一,
10 | * [PostgreSQL 12.x](https://www.postgresql.org/download)
11 | * [HighGo PostgreSQL Server 1.x](https://www.highgo.ca/products/highgo-postgresql-server)
12 |
13 | ### 编译,测试和安装
14 | `wal2mongo` 支持两种编译方式:一种是针对希望在PostgreSQL源代码树结构下管理 wal2mongo 代码的开发人员。 另一个是针对希望将wal2mongo集成到已编译好的PostgreSQL的开发人员或DBA。
15 |
16 | #### 类似Linux的环境中
17 | * 在PostgreSQL源代码树下构建
18 | ```
19 | cd /path/to/postgres/contrib/
20 | git clone https://github.com/HighgoSoftware/wal2mongo.git
21 | cd wal2mongo
22 | make
23 | make install
24 | make check
25 | ```
26 |
27 | * 针对PostgreSQL二进制文件编译
28 | ```
29 | mkdir sandbox
30 | cd sandbox
31 | git clone https://github.com/HighgoSoftware/wal2mongo.git
32 | cd wal2mongo
33 | ```
34 |
35 | 将 “PATH” 指向现有的PostgreSQL二进制目录。 使用`PGHOST`和`PGPORT`指定一个PostgreSQL服务器和端口来运行installcheck-force测试。
36 | ```
37 | $ export PATH=/path/to/postgres/bin:$PATH
38 | USE_PGXS=1 make
39 | USE_PGXS=1 make install
40 | USE_PGXS=1 make installcheck-force
41 | ```
42 |
43 | #### 在Windows 7、10和2019服务器上
44 | * 在PostgreSQL源代码树下编译
45 | 1. 按照 [此处](https://www.postgresql.org/docs/12/install-windows-full.html) 的说明使用Microsoft Windows SDK设置编译环境. [Visual Studio 2019社区版](https://visualstudio.microsoft.com/downloads/) 足以编译Postgres 12.x和wal2mongo逻辑解码输出插件。 成功安装VS 2019之后,请下载 [`ActivePerl 5.28`](https://www.activestate.com/products/perl/downloads/), [`ActiveTcl 8.6`](https://www.activestate.com/products/tcl/downloads/) 和 [`GnuWin32 0.6.3`](https://sourceforge.net/projects/getgnuwin32/files/getgnuwin32/0.6.30/GetGnuWin32-0.6.3.exe/download) 然后使用默认设置安装即可。
46 |
47 | 2. 在环境变量管理面板中检查系统变量的 “ActivePerl”,“ActiveTcl” 和 “GnuWin32” 的二进制路径,如果不存在,请添加它们。
48 |
49 | 3. 编译并安装
50 |
51 | ```
52 | cd \path\to\postgres\contrib\
53 | git clone https://github.com/HighgoSoftware/wal2mongo.git
54 | cd \path\to\postgres\src\tools\msvc\
55 | build
56 | install \path\to\install\foler\
57 | ```
58 | 4. 运行回归测试(注意,vcregress尚不支持单个扩展的回归测试)
59 | ```
60 | vcregress contribcheck
61 | ```
62 | * 针对PostgreSQL二进制文件编译
63 |
64 | 可以按照以下步骤使用Visual Studio 2019编译`wal2mongo`
65 | 1. 使用空项目 (empty project)模板创建一个新项目,例如,将项目名称设置为 `wal2mongo`
66 | 2. 右键单击 `wal2mongo` 项目->添加->新项目...,选择 `C ++ File`,但将其命名为`wal2mongo.c`。
67 | 3. 将来自github的所有c源代码粘贴到本地“ wal2mongo.c” 文件中。
68 | 4. 将 “PGDLLEXPORT” 添加到函数 “_PG_init” 和 “_PG_output_plugin_init”中,如下所示,
69 | ```
70 | extern PGDLLEXPORT void _PG_init(void);
71 | extern PGDLLEXPORT void _PG_output_plugin_init(OutputPluginCallbacks* cb);
72 | ```
73 | 5. 右键点击`wal2mongo`项目->属性,然后更改以下设置,
74 | 常规->配置类型“动态库(.dll)”
75 |
76 | 设置C / C++ ->代码生成->启用C++ exceptions 的设置改为`否`
77 |
78 | 设置C / C ++->高级->编译为`编译为C代码`
79 |
80 | 将链接器->清单文件->生成清单设置为“否”
81 |
82 | 将`postgres.lib`添加到链接器->输入->其他依赖项
83 |
84 | 根据PostgreSQL二进制文件的安装位置,按以下顺序将以下路径添加到 C / C++ ->常规->其他include目录
85 |
86 | ```
87 | C:\Users\Administrator\Downloads\pg12.2\include\server\port\win32_msvc
88 | C:\Users\Administrator\Downloads\pg12.2\include\server\port\win32
89 | C:\Users\Administrator\Downloads\pg12.2\include\server
90 | C:\Users\Administrator\Downloads\pg12.2\include
91 | ```
92 | 同样,根据PostgreSQL二进制文件的安装位置,在Linker-> General-> Additional Library Directories 中添加如下所示的库路径
93 | ```
94 | C:\Users\Administrator\Downloads\pg12.2\lib
95 | ```
96 | 6. 右键点击`wal2mongo`,然后点击`build`。 如果一切正常,则应该显示以下消息。
97 | ```
98 | 1>wal2mongo.vcxproj -> C:\Users\Administrator\source\repos\wal2mongo\Debug\wal2mongo.dll
99 | 1>Done building project "wal2mongo.vcxproj".
100 | ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
101 | ```
102 | 7. 手动将 “wal2mongo.dll” 复制到安装了PostgreaSQL的 “lib”中,然后按照示例部分进行测试。
103 |
104 |
105 | ### 设置和配置
106 | 编辑PostgreSQl配置文件 “postgresql.conf”,并确保将 “wal_level” 设置为 “logical”,并且将 “max_replication_slots” 设置为至少1(默认设置为10)。
107 | 重新启动postgres服务。
108 |
109 | ### 选件
110 | 有待确定...
111 |
112 | ### 样例
113 | 下面是两种使用wal2mongo将数据从PostgreSQL复制到MongoDB的简单方法:一种使用psql console;另一个使用pg_recvlogical工具。
114 |
115 | #### 使用psql console
116 | * 创建一个逻辑插槽
117 | 例如,使用输出插件 “wal2mongo” 创建一个名为 “w2m_slot”的逻辑插槽。
118 | ```
119 | postgres=# SELECT * FROM pg_create_logical_replication_slot('w2m_slot', 'wal2mongo');
120 | slot_name | lsn
121 | -----------+------------
122 | w2m_slot | 1/3CB04148
123 | (1 row)
124 | ```
125 |
126 | * 检查刚创建的插槽
127 | ```
128 | postgres=# SELECT slot_name, plugin, slot_type, database, active, restart_lsn, confirmed_flush_lsn FROM pg_replication_slots;
129 | slot_name | plugin | slot_type | database | active | restart_lsn | confirmed_flush_lsn
130 | -----------+-----------+-----------+----------+--------+-------------+---------------------
131 | w2m_slot | wal2mongo | logical | postgres | f | 1/3CB04110 | 1/3CB04148
132 | (1 row)
133 | ```
134 |
135 | * 创建一个表并插入数据
136 | ```
137 | postgres=# CREATE TABLE books (
138 | id SERIAL PRIMARY KEY,
139 | title VARCHAR(100) NOT NULL,
140 | author VARCHAR(100) NULL
141 | );
142 |
143 | postgres=# insert into books
144 | (id, title, author)
145 | values
146 | (123, 'HG-PGSQL1.1', 'Highgo');
147 | ```
148 |
149 | * 查看有无任何改动
150 | ```
151 | postgres=# SELECT * FROM pg_logical_slot_peek_changes('w2m_slot', NULL, NULL);
152 | lsn | xid | data
153 | ------------+------+------------------------------------------------------------------------
154 | 1/3CB247F8 | 1793 | db.books.insertOne( { id:123, title:"HG-PGSQL1.1", author:"Highgo" } )
155 | (1 row)
156 | ```
157 |
158 | * 取得改动
159 | ```
160 | postgres=# SELECT * FROM pg_logical_slot_get_changes('w2m_slot', NULL, NULL);
161 | lsn | xid | data
162 | ------------+------+------------------------------------------------------------------------
163 | 1/3CB247F8 | 1793 | db.books.insertOne( { id:123, title:"HG-PGSQL1.1", author:"Highgo" } )
164 | (1 row)
165 | ```
166 |
167 | * 使用 mongo 客户端中复制数据(选项1)
168 |
169 | 登录 mongoDB,然后复制数据部分中的所有字符串,然后粘贴到 mongo 客户端上
170 | ```
171 | > db.books.insertOne( { id:123, title:"HG-PGSQL1.1", author:"Highgo" } )
172 | {
173 | "acknowledged" : true,
174 | "insertedId" : ObjectId("5e5ea92be9684c562aae5b7a")
175 | }
176 | ```
177 |
178 | * 使用.js文件复制数据(选项2)
179 |
180 | 复制输出的所有字符串,然后将其粘贴到文件中,例如 test.js,然后使用mongo客户端导入文件
181 | ```
182 | $ mongo < test.js
183 | MongoDB shell version v4.0.16
184 | connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
185 | Implicit session: session { "id" : UUID("86ddf177-9704-43f9-9f66-31ac1f9f89e0") }
186 | MongoDB server version: 4.0.16
187 | {
188 | "acknowledged" : true,
189 | "insertedId" : ObjectId("5e5ea8f3bb2265ca8fa4b7ae")
190 | }
191 | bye
192 | ```
193 |
194 | * 检查复制的数据
195 | ```
196 | > db.books.find();
197 | { "_id" : ObjectId("5e5ea8f3bb2265ca8fa4b7ae"), "id" : 123, "title" : "HG-PGSQL1.1", "author" : "Highgo" }
198 | >
199 | ```
200 |
201 | * 删除逻辑插槽如果不再使用的话
202 | ```
203 | postgres=# SELECT pg_drop_replication_slot('w2m_slot');
204 | pg_drop_replication_slot
205 | --------------------------
206 |
207 | (1 row)
208 | ```
209 |
210 | #### 使用 pg_recvlogical
211 | * 创建一个逻辑插槽
212 | ```
213 | $ pg_recvlogical -d postgres --slot w2m_slot2 --create-slot --plugin=wal2mongo
214 | ```
215 |
216 | * 在终端1上启动pg_recvlogical
217 | ```
218 | $ pg_recvlogical -d postgres --slot w2m_slot2 --start -f -
219 | ```
220 | 或者让 pg_recvlogical 记录所有数据改动到一个文件,例如
221 | ```
222 | $ pg_recvlogical -d postgres --slot w2m_slot2 --start -f test2.js
223 | ```
224 |
225 | * 创建一个表并从终端2插入数据
226 | ```
227 | postgres=# CREATE TABLE books (
228 | id SERIAL PRIMARY KEY,
229 | title VARCHAR(100) NOT NULL,
230 | author VARCHAR(100) NULL
231 | );
232 |
233 | postgres=# insert into books
234 | (id, title, author)
235 | values
236 | (124, 'HG-PGSQL1.2', 'Highgo');
237 | ```
238 |
239 | * 切换回终端1来检查更改
240 |
241 | 如下所示的一条记录应该显示在终端1或文件 test2.js 中,
242 | ```
243 | db.books.insertOne( { id:124, title:"HG-PGSQL1.2", author:"Highgo" } )
244 | ```
245 |
246 | * 将数据复制到mongoDB
247 |
248 | * 删除逻辑插槽如果不再使用的话
249 | ```
250 | $ pg_recvlogical -d postgres --slot w2m_slot2 --drop-slot
251 | ```
252 |
253 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | [](https://github.com/HighgoSoftware/wal2mongo/releases)
4 |
5 | ### Introduction
6 | `wal2mongo` is a PostgreSQL logical decoding output plugin designed to make the logical replication easier from `PostgreSQL` to `MongoDB` by formating the output to a JSON-like format accepted by `mongo`.
7 |
8 | ### Prerequisites
9 | To use wal2mongo logical decoding output plugin, either one of below PostgreSQL servers need to be installed,
10 | * [PostgreSQL 12.x](https://www.postgresql.org/download)
11 | * [HighGo PostgreSQL Server 1.x](https://www.highgo.ca/products/highgo-postgresql-server)
12 |
13 | ### Build, Test and Install
14 | `wal2mongo` is designed to support two typical ways for building PostgreSQL extension: one is for developers who want to manage `wal2mongo` source code under PostgreSQL source code tree structure; the other one is for developers or DBA who want to integrate `wal2mongo` to existing PostgreSQL binaries.
15 |
16 | #### On a Linux-like environment
17 | * Build under PostgreSQL Source Code Tree
18 | ```
19 | cd /path/to/postgres/contrib/
20 | git clone https://github.com/HighgoSoftware/wal2mongo.git
21 | cd wal2mongo
22 | make
23 | make install
24 | make check
25 | ```
26 |
27 | * Build against PostgreSQL binaries
28 | ```
29 | mkdir sandbox
30 | cd sandbox
31 | git clone https://github.com/HighgoSoftware/wal2mongo.git
32 | cd wal2mongo
33 | ```
34 |
35 | Set the `PATH` to point to existing PostgreSQL binary directory. Use `PGHOST` and `PGPORT` to specify a PostgreSQL server and port to run the installcheck-force test against if different from default ones.
36 | ```
37 | $ export PATH=/path/to/postgres/bin:$PATH
38 | USE_PGXS=1 make
39 | USE_PGXS=1 make install
40 | USE_PGXS=1 make installcheck-force
41 | ```
42 |
43 | #### On Windows7, 10 and 2019 Server
44 | * Build under PostgreSQL Source Code Tree
45 | 1. Following the instruction [here](https://www.postgresql.org/docs/12/install-windows-full.html) to setup the build environment using Microsoft Windows SDK. The [Visual Studio 2019 Community](https://visualstudio.microsoft.com/downloads/) is enough for building Postgres 12.x and wal2mongo logical decoding output plugin. After VS 2019 has been installed successfully, download [`ActivePerl 5.28`](https://www.activestate.com/products/perl/downloads/), [`ActiveTcl 8.6`](https://www.activestate.com/products/tcl/downloads/) and [`GnuWin32 0.6.3`](https://sourceforge.net/projects/getgnuwin32/files/getgnuwin32/0.6.30/GetGnuWin32-0.6.3.exe/download) and install them with the default setting would be enough.
46 |
47 | 2. Check the binaries path for `ActivePerl`, `ActiveTcl` and `GnuWin32` for System variables in Environment variables management panel, if not exist then add them in.
48 |
49 | 3. Build and install
50 |
51 | ```
52 | cd \path\to\postgres\contrib\
53 | git clone https://github.com/HighgoSoftware/wal2mongo.git
54 | cd \path\to\postgres\src\tools\msvc\
55 | build
56 | install \path\to\install\foler\
57 | ```
58 | 4. Run regress test (notes, regress test a single extension is not supported by vcregress yet)
59 | ```
60 | vcregress contribcheck
61 | ```
62 | * Build against PostgreSQL binaries
63 |
64 | `wal2mongo` can be built in a separate project folder using Visual Studio 2019 by following the steps below:
65 | 1. Create a `new projec`t using `Empty Project` template, and set `Project name` to `wal2mong`, for example.
66 | 2. Right click `wal2mongo` project -> Add -> New items ..., select `C++ File` but name it as `wal2mongo.c`
67 | 3. Paste all the c source code from github to local `wal2mongo.c` file.
68 | 4. Add `PGDLLEXPORT` to function `_PG_init` and `_PG_output_plugin_init`, like below,
69 | ```
70 | extern PGDLLEXPORT void _PG_init(void);
71 | extern PGDLLEXPORT void _PG_output_plugin_init(OutputPluginCallbacks* cb);
72 | ```
73 | 5. Right click `wal2mongo` project -> Properties, then change below settings,
74 | General -> Configuration Type `Dynamic Library (.dll)`
75 |
76 | Set C/C++ -> Code Generation -> Enable C++ Exceptions to `NO`
77 |
78 | Set C/C++ -> Advanced -> Compile As to `Compile As C Code`
79 |
80 | Set Linker -> Manifest File -> Generate Manifest to `No`
81 |
82 | Add `postgres.lib` to Linker -> Input -> Additional Dependencies
83 |
84 | Depends on where PostgreSQL binaries are installed, add below pathes in below order to C/C++ -> General -> Additional Include Directories
85 | ```
86 | C:\Users\Administrator\Downloads\pg12.2\include\server\port\win32_msvc
87 | C:\Users\Administrator\Downloads\pg12.2\include\server\port\win32
88 | C:\Users\Administrator\Downloads\pg12.2\include\server
89 | C:\Users\Administrator\Downloads\pg12.2\include
90 | ```
91 | Again, depends on where PostgreSQL binaries are installed, add a path like below to Linker -> General -> Additional Library Directories
92 | ```
93 | C:\Users\Administrator\Downloads\pg12.2\lib
94 | ```
95 | 6. Right click `wal2mongo1, then click `build`. If everything goes fine, then below message should show up.
96 | ```
97 | 1>wal2mongo.vcxproj -> C:\Users\Administrator\source\repos\wal2mongo\Debug\wal2mongo.dll
98 | 1>Done building project "wal2mongo.vcxproj".
99 | ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
100 | ```
101 | 7. Manually copy `wal2mongo.dll` to the `lib` where PostgreaSQL is installed, then run test following `Examples` section.
102 |
103 |
104 | ### Setup and configuration
105 | Edit PostgreSQl configuration file `postgresql.conf` and make sure `wal_level` is set to `logical`, and `max_replication_slots` is set at least 1 (default settings is 10).
106 | Restart postgres services.
107 |
108 | ### Options
109 | TBD...
110 |
111 | ### Examples
112 | Below are two simple ways to replicate data from PostgreSQL to MongoDB using `wal2mongo`: one use psql console; the other one use pg_recvlogical tools.
113 |
114 | #### using psql
115 | * Create a slot
116 | For example, create a slot nameed 'w2m_slot' using the output plugin `wal2mongo`.
117 | ```
118 | postgres=# SELECT * FROM pg_create_logical_replication_slot('w2m_slot', 'wal2mongo');
119 | slot_name | lsn
120 | -----------+------------
121 | w2m_slot | 1/3CB04148
122 | (1 row)
123 | ```
124 |
125 | * Check the slot just created
126 | ```
127 | postgres=# SELECT slot_name, plugin, slot_type, database, active, restart_lsn, confirmed_flush_lsn FROM pg_replication_slots;
128 | slot_name | plugin | slot_type | database | active | restart_lsn | confirmed_flush_lsn
129 | -----------+-----------+-----------+----------+--------+-------------+---------------------
130 | w2m_slot | wal2mongo | logical | postgres | f | 1/3CB04110 | 1/3CB04148
131 | (1 row)
132 | ```
133 |
134 | * Create a table and insert data
135 | ```
136 | postgres=# CREATE TABLE books (
137 | id SERIAL PRIMARY KEY,
138 | title VARCHAR(100) NOT NULL,
139 | author VARCHAR(100) NULL
140 | );
141 |
142 | postgres=# insert into books
143 | (id, title, author)
144 | values
145 | (123, 'HG-PGSQL1.1', 'Highgo');
146 | ```
147 |
148 | * Peek if any changes
149 | ```
150 | postgres=# SELECT * FROM pg_logical_slot_peek_changes('w2m_slot', NULL, NULL);
151 | lsn | xid | data
152 | ------------+------+------------------------------------------------------------------------
153 | 1/3CB247F8 | 1793 | db.books.insertOne( { id:123, title:"HG-PGSQL1.1", author:"Highgo" } )
154 | (1 row)
155 | ```
156 |
157 | * Retrieve the changes
158 | ```
159 | postgres=# SELECT * FROM pg_logical_slot_get_changes('w2m_slot', NULL, NULL);
160 | lsn | xid | data
161 | ------------+------+------------------------------------------------------------------------
162 | 1/3CB247F8 | 1793 | db.books.insertOne( { id:123, title:"HG-PGSQL1.1", author:"Highgo" } )
163 | (1 row)
164 | ```
165 |
166 | * Replicate data within mongo console (option 1)
167 |
168 | log into mongoDB, and copy all the strings from data section, and paste to mongo console
169 | ```
170 | > db.books.insertOne( { id:123, title:"HG-PGSQL1.1", author:"Highgo" } )
171 | {
172 | "acknowledged" : true,
173 | "insertedId" : ObjectId("5e5ea92be9684c562aae5b7a")
174 | }
175 | ```
176 |
177 | * Replicate data using .js file (option 2)
178 |
179 | Copy all the strings from data section, and paste it to a file, e.g. test.js, then import the file using mongo
180 | ```
181 | $ mongo < test.js
182 | MongoDB shell version v4.0.16
183 | connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
184 | Implicit session: session { "id" : UUID("86ddf177-9704-43f9-9f66-31ac1f9f89e0") }
185 | MongoDB server version: 4.0.16
186 | {
187 | "acknowledged" : true,
188 | "insertedId" : ObjectId("5e5ea8f3bb2265ca8fa4b7ae")
189 | }
190 | bye
191 | ```
192 |
193 | * check the data replicated
194 | ```
195 | > db.books.find();
196 | { "_id" : ObjectId("5e5ea8f3bb2265ca8fa4b7ae"), "id" : 123, "title" : "HG-PGSQL1.1", "author" : "Highgo" }
197 | >
198 | ```
199 |
200 | * Drop a slot if not used any more
201 | ```
202 | postgres=# SELECT pg_drop_replication_slot('w2m_slot');
203 | pg_drop_replication_slot
204 | --------------------------
205 |
206 | (1 row)
207 | ```
208 |
209 | #### using pg_recvlogical
210 | * create a slot
211 | ```
212 | $ pg_recvlogical -d postgres --slot w2m_slot2 --create-slot --plugin=wal2mongo
213 | ```
214 |
215 | * start logical decoding stream on terminal 1
216 | ```
217 | $ pg_recvlogical -d postgres --slot w2m_slot2 --start -f -
218 | ```
219 | Or let pg_recvlogical record all the changes to a file, e.g.
220 | ```
221 | $ pg_recvlogical -d postgres --slot w2m_slot2 --start -f test2.js
222 | ```
223 |
224 | * Create a table and insert data from terminal 2
225 | ```
226 | postgres=# CREATE TABLE books (
227 | id SERIAL PRIMARY KEY,
228 | title VARCHAR(100) NOT NULL,
229 | author VARCHAR(100) NULL
230 | );
231 |
232 | postgres=# insert into books
233 | (id, title, author)
234 | values
235 | (124, 'HG-PGSQL1.2', 'Highgo');
236 | ```
237 |
238 | * Check the changes by switching back to terminal 1
239 |
240 | One record like below should be showing up either in console or inside file test2.js,
241 | ```
242 | db.books.insertOne( { id:124, title:"HG-PGSQL1.2", author:"Highgo" } )
243 | ```
244 |
245 | * replcate data to mongoDB same as above
246 |
247 | * Drop a slot if not used any more
248 | ```
249 | $ pg_recvlogical -d postgres --slot w2m_slot2 --drop-slot
250 | ```
251 |
252 |
--------------------------------------------------------------------------------
/expected/actions.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- actions
11 | CREATE TABLE testing (a integer primary key);
12 | INSERT INTO testing (a) VALUES(200);
13 | UPDATE testing SET a = 500 WHERE a = 200;
14 | DELETE FROM testing WHERE a = 500;
15 | TRUNCATE TABLE testing;
16 | -- peek changes according to action configuration
17 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'insert', 'regress', 'true');
18 | data
19 | --------------------------------------------------------------------------------------
20 | use mycluster_mydb_regression_slot;
21 | db.testing.insertOne( { a: NumberInt("200") } );
22 | use mycluster_mydb_regression_slot;
23 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
24 | use mycluster_mydb_regression_slot;
25 | db.testing.deleteOne( { a: NumberInt("500") } );
26 | (6 rows)
27 |
28 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'update', 'regress', 'true');
29 | data
30 | --------------------------------------------------------------------------------------
31 | use mycluster_mydb_regression_slot;
32 | db.testing.insertOne( { a: NumberInt("200") } );
33 | use mycluster_mydb_regression_slot;
34 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
35 | use mycluster_mydb_regression_slot;
36 | db.testing.deleteOne( { a: NumberInt("500") } );
37 | (6 rows)
38 |
39 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'delete', 'regress', 'true');
40 | data
41 | --------------------------------------------------------------------------------------
42 | use mycluster_mydb_regression_slot;
43 | db.testing.insertOne( { a: NumberInt("200") } );
44 | use mycluster_mydb_regression_slot;
45 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
46 | use mycluster_mydb_regression_slot;
47 | db.testing.deleteOne( { a: NumberInt("500") } );
48 | (6 rows)
49 |
50 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'truncate', 'regress', 'true');
51 | data
52 | --------------------------------------------------------------------------------------
53 | use mycluster_mydb_regression_slot;
54 | db.testing.insertOne( { a: NumberInt("200") } );
55 | use mycluster_mydb_regression_slot;
56 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
57 | use mycluster_mydb_regression_slot;
58 | db.testing.deleteOne( { a: NumberInt("500") } );
59 | (6 rows)
60 |
61 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'insert, update, delete, truncate', 'regress', 'true');
62 | data
63 | --------------------------------------------------------------------------------------
64 | use mycluster_mydb_regression_slot;
65 | db.testing.insertOne( { a: NumberInt("200") } );
66 | use mycluster_mydb_regression_slot;
67 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
68 | use mycluster_mydb_regression_slot;
69 | db.testing.deleteOne( { a: NumberInt("500") } );
70 | (6 rows)
71 |
72 | -- peek changes with default action configuraiton
73 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
74 | data
75 | --------------------------------------------------------------------------------------
76 | use mycluster_mydb_regression_slot;
77 | db.testing.insertOne( { a: NumberInt("200") } );
78 | use mycluster_mydb_regression_slot;
79 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
80 | use mycluster_mydb_regression_slot;
81 | db.testing.deleteOne( { a: NumberInt("500") } );
82 | (6 rows)
83 |
84 | -- peek changes with several configuration parameter combinations
85 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include_cluster_name', 'true', 'regress', 'true');
86 | data
87 | --------------------------------------------------------------------------------------
88 | use mycluster_mydb_regression_slot;
89 | db.testing.insertOne( { a: NumberInt("200") } );
90 | use mycluster_mydb_regression_slot;
91 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
92 | use mycluster_mydb_regression_slot;
93 | db.testing.deleteOne( { a: NumberInt("500") } );
94 | (6 rows)
95 |
96 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'skip_empty_xacts', 'true', 'regress', 'true');
97 | data
98 | --------------------------------------------------------------------------------------
99 | use mycluster_mydb_regression_slot;
100 | db.testing.insertOne( { a: NumberInt("200") } );
101 | use mycluster_mydb_regression_slot;
102 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
103 | use mycluster_mydb_regression_slot;
104 | db.testing.deleteOne( { a: NumberInt("500") } );
105 | (6 rows)
106 |
107 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'only_local', 'true', 'regress', 'true');
108 | data
109 | --------------------------------------------------------------------------------------
110 | use mycluster_mydb_regression_slot;
111 | db.testing.insertOne( { a: NumberInt("200") } );
112 | use mycluster_mydb_regression_slot;
113 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
114 | use mycluster_mydb_regression_slot;
115 | db.testing.deleteOne( { a: NumberInt("500") } );
116 | (6 rows)
117 |
118 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'use_transaction', 'true', 'regress', 'true');
119 | data
120 | --------------------------------------------------------------------------------------
121 | session0regression_slot = db.getMongo().startSession();
122 | session0regression_slot.startTransaction();
123 | session0regression_slot.commitTransaction();
124 | session0regression_slot.endSession();
125 | session0regression_slot = db.getMongo().startSession();
126 | session0regression_slot.startTransaction();
127 | use mycluster_mydb_regression_slot;
128 | db.testing.insertOne( { a: NumberInt("200") } );
129 | session0regression_slot.commitTransaction();
130 | session0regression_slot.endSession();
131 | session0regression_slot = db.getMongo().startSession();
132 | session0regression_slot.startTransaction();
133 | use mycluster_mydb_regression_slot;
134 | db.testing.updateOne( { a: NumberInt("200") }, { $set: { a: NumberInt("500") } } );
135 | session0regression_slot.commitTransaction();
136 | session0regression_slot.endSession();
137 | session0regression_slot = db.getMongo().startSession();
138 | session0regression_slot.startTransaction();
139 | use mycluster_mydb_regression_slot;
140 | db.testing.deleteOne( { a: NumberInt("500") } );
141 | session0regression_slot.commitTransaction();
142 | session0regression_slot.endSession();
143 | session0regression_slot = db.getMongo().startSession();
144 | session0regression_slot.startTransaction();
145 | session0regression_slot.commitTransaction();
146 | session0regression_slot.endSession();
147 | (26 rows)
148 |
149 | -- peek changes with invalid actions
150 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'insert, xxx, delete, xxx', 'regress', 'true');
151 | ERROR: could not parse value "xxx" for parameter "actions"
152 | DROP TABLE testing;
153 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
154 | ?column?
155 | ----------
156 | end
157 | (1 row)
158 |
159 |
--------------------------------------------------------------------------------
/expected/array.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- create table with different numeric type
11 | CREATE TABLE tbl_array(id serial primary key, a bool[], c char[], d name[], e int2[], f int4[], g text[], h varchar[], i int8[], j float4[], k float8[], l timestamptz[], m numeric[], n uuid[] );
12 | -- different data types for array
13 | INSERT INTO tbl_array (a, c, d, e, f, g, h, i, j, k, l, m, n) VALUES(
14 | ARRAY[true, false],
15 | ARRAY['c'::char,'h'::char],
16 | ARRAY['student'::name, 'teacher'::name],
17 | ARRAY['123'::int2, '456'::int2],
18 | ARRAY['123456789'::int4,'987654321'::int4],
19 | ARRAY['abc'::text, '123'::text],
20 | ARRAY['ABCD'::varchar, '1234'::varchar],
21 | ARRAY['112233445566778899'::int8, '998877665544332211'::int8],
22 | ARRAY['123.456'::float4, '2222.3333'::float4],
23 | ARRAY['123456.123'::float8, '654321.123'::float8],
24 | ARRAY['2020-03-30 10:18:40.12-07'::timestamptz, '2020-03-30 20:28:40.12-07'::timestamptz],
25 | ARRAY['123456789'::numeric, '987654321'::numeric],
26 | ARRAY['40e6215d-b5c6-4896-987c-f30f3678f608'::uuid, '3f333df6-90a4-4fda-8dd3-9485d27cee36'::uuid]
27 | );
28 | UPDATE tbl_array SET
29 | a=ARRAY[false, true],
30 | c=ARRAY['h'::char, 'c'::char],
31 | d=ARRAY['teacher'::name, 'student'::name],
32 | e=ARRAY['456'::int2, '123'::int2],
33 | f=ARRAY['987654321'::int4, '123456789'::int4],
34 | g=ARRAY['123'::text, 'abc'::text],
35 | h=ARRAY['1234'::varchar, 'ABCD'::varchar],
36 | i=ARRAY['998877665544332211'::int8, '112233445566778899'::int8],
37 | j=ARRAY['2222.3333'::float4, '123.456'::float4],
38 | k=ARRAY['654321.123'::float8, '123456.123'::float8],
39 | l=ARRAY['2020-03-30 20:28:40.12-07'::timestamptz, '2020-03-30 10:18:40.12-07'::timestamptz],
40 | m=ARRAY['987654321'::numeric, '123456789'::numeric],
41 | n=ARRAY['3f333df6-90a4-4fda-8dd3-9485d27cee36'::uuid, '40e6215d-b5c6-4896-987c-f30f3678f608'::uuid]
42 | WHERE id=1;
43 | DELETE FROM tbl_array WHERE id = 1;
44 | TRUNCATE TABLE tbl_array;
45 | -- peek changes according to action configuration
46 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
47 | data
48 | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
49 | use mycluster_mydb_regression_slot;
50 | db.tbl_array.insertOne( { id: NumberInt("1"), a:[true,false], c:["c","h"], d:["student","teacher"], e:[NumberInt(123),NumberInt(456)], f:[NumberInt(123456789),NumberInt(987654321)], g:["abc","123"], h:["ABCD","1234"], i:[NumberLong(112233445566778899),NumberLong(998877665544332211)], j:[123.456,2222.3333], k:[NumberDecimal("123456.123"),NumberDecimal("654321.123")], l:[ISODate("Mon Mar 30 10:18:40.12 2020 PDT"),ISODate("Mon Mar 30 20:28:40.12 2020 PDT")], m:[NumberDecimal("123456789"),NumberDecimal("987654321")], n:[UUID("40e6215d-b5c6-4896-987c-f30f3678f608"),UUID("3f333df6-90a4-4fda-8dd3-9485d27cee36")] } );
51 | use mycluster_mydb_regression_slot;
52 | db.tbl_array.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a:[false,true], c:["h","c"], d:["teacher","student"], e:[NumberInt(456),NumberInt(123)], f:[NumberInt(987654321),NumberInt(123456789)], g:["123","abc"], h:["1234","ABCD"], i:[NumberLong(998877665544332211),NumberLong(112233445566778899)], j:[2222.3333,123.456], k:[NumberDecimal("654321.123"),NumberDecimal("123456.123")], l:[ISODate("Mon Mar 30 20:28:40.12 2020 PDT"),ISODate("Mon Mar 30 10:18:40.12 2020 PDT")], m:[NumberDecimal("987654321"),NumberDecimal("123456789")], n:[UUID("3f333df6-90a4-4fda-8dd3-9485d27cee36"),UUID("40e6215d-b5c6-4896-987c-f30f3678f608")] } } );
53 | use mycluster_mydb_regression_slot;
54 | db.tbl_array.deleteOne( { id: NumberInt("1") } );
55 | (6 rows)
56 |
57 | -- get changes according to action configuration
58 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
59 | data
60 | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
61 | use mycluster_mydb_regression_slot;
62 | db.tbl_array.insertOne( { id: NumberInt("1"), a:[true,false], c:["c","h"], d:["student","teacher"], e:[NumberInt(123),NumberInt(456)], f:[NumberInt(123456789),NumberInt(987654321)], g:["abc","123"], h:["ABCD","1234"], i:[NumberLong(112233445566778899),NumberLong(998877665544332211)], j:[123.456,2222.3333], k:[NumberDecimal("123456.123"),NumberDecimal("654321.123")], l:[ISODate("Mon Mar 30 10:18:40.12 2020 PDT"),ISODate("Mon Mar 30 20:28:40.12 2020 PDT")], m:[NumberDecimal("123456789"),NumberDecimal("987654321")], n:[UUID("40e6215d-b5c6-4896-987c-f30f3678f608"),UUID("3f333df6-90a4-4fda-8dd3-9485d27cee36")] } );
63 | use mycluster_mydb_regression_slot;
64 | db.tbl_array.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a:[false,true], c:["h","c"], d:["teacher","student"], e:[NumberInt(456),NumberInt(123)], f:[NumberInt(987654321),NumberInt(123456789)], g:["123","abc"], h:["1234","ABCD"], i:[NumberLong(998877665544332211),NumberLong(112233445566778899)], j:[2222.3333,123.456], k:[NumberDecimal("654321.123"),NumberDecimal("123456.123")], l:[ISODate("Mon Mar 30 20:28:40.12 2020 PDT"),ISODate("Mon Mar 30 10:18:40.12 2020 PDT")], m:[NumberDecimal("987654321"),NumberDecimal("123456789")], n:[UUID("3f333df6-90a4-4fda-8dd3-9485d27cee36"),UUID("40e6215d-b5c6-4896-987c-f30f3678f608")] } } );
65 | use mycluster_mydb_regression_slot;
66 | db.tbl_array.deleteOne( { id: NumberInt("1") } );
67 | (6 rows)
68 |
69 | -- drop table
70 | DROP TABLE tbl_array;
71 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
72 | ?column?
73 | ----------
74 | end
75 | (1 row)
76 |
77 |
--------------------------------------------------------------------------------
/expected/binData.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- UUID
11 | CREATE TABLE tbl_uuid ( id serial PRIMARY KEY, a_uuid UUID NOT NULL );
12 | INSERT INTO tbl_uuid (a_uuid) VALUES('47deacb1-3ad0-4a0e-8254-9ad3f589c9f3') ;
13 | UPDATE tbl_uuid SET a_uuid = 'e7d8e462-12cc-49dc-aac2-2b5dccdabeda' WHERE id=1;
14 | DELETE FROM tbl_uuid WHERE id = 1;
15 | TRUNCATE TABLE tbl_uuid;
16 | -- BYTEA
17 | CREATE TABLE tbl_bytea ( id serial PRIMARY KEY, a_bytea bytea NOT NULL );
18 | ALTER TABLE tbl_bytea REPLICA IDENTITY FULL;
19 | INSERT INTO tbl_bytea(a_bytea) SELECT 'abc \153\154\155 \052\251\124'::bytea RETURNING a_bytea;
20 | a_bytea
21 | --------------------------
22 | \x616263206b6c6d202aa954
23 | (1 row)
24 |
25 | UPDATE tbl_bytea SET a_bytea = SELECT '\134'::bytea RETURNING a_bytea;
26 | ERROR: syntax error at or near "SELECT" at character 32
27 | DELETE FROM tbl_bytea WHERE id = 1;
28 | TRUNCATE TABLE tbl_bytea;
29 | -- peek changes according to action configuration
30 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
31 | data
32 | -------------------------------------------------------------------------------------------------------------------------------------------
33 | use mycluster_mydb_regression_slot;
34 | db.tbl_uuid.insertOne( { id: NumberInt("1"), a_uuid: UUID("47deacb1-3ad0-4a0e-8254-9ad3f589c9f3") } );
35 | use mycluster_mydb_regression_slot;
36 | db.tbl_uuid.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a_uuid: UUID("e7d8e462-12cc-49dc-aac2-2b5dccdabeda") } } );
37 | use mycluster_mydb_regression_slot;
38 | db.tbl_uuid.deleteOne( { id: NumberInt("1") } );
39 | use mycluster_mydb_regression_slot;
40 | db.tbl_bytea.insertOne( { id: NumberInt("1"), a_bytea: HexData(0, "616263206b6c6d202aa954") } );
41 | use mycluster_mydb_regression_slot;
42 | db.tbl_bytea.deleteOne( { id: NumberInt("1") } );
43 | (10 rows)
44 |
45 | -- get changes according to action configuration
46 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
47 | data
48 | -------------------------------------------------------------------------------------------------------------------------------------------
49 | use mycluster_mydb_regression_slot;
50 | db.tbl_uuid.insertOne( { id: NumberInt("1"), a_uuid: UUID("47deacb1-3ad0-4a0e-8254-9ad3f589c9f3") } );
51 | use mycluster_mydb_regression_slot;
52 | db.tbl_uuid.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a_uuid: UUID("e7d8e462-12cc-49dc-aac2-2b5dccdabeda") } } );
53 | use mycluster_mydb_regression_slot;
54 | db.tbl_uuid.deleteOne( { id: NumberInt("1") } );
55 | use mycluster_mydb_regression_slot;
56 | db.tbl_bytea.insertOne( { id: NumberInt("1"), a_bytea: HexData(0, "616263206b6c6d202aa954") } );
57 | use mycluster_mydb_regression_slot;
58 | db.tbl_bytea.deleteOne( { id: NumberInt("1") } );
59 | (10 rows)
60 |
61 | -- drop tables
62 | DROP TABLE tbl_uuid;
63 | DROP TABLE tbl_bytea;
64 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
65 | ?column?
66 | ----------
67 | end
68 | (1 row)
69 |
70 |
--------------------------------------------------------------------------------
/expected/binary.out:
--------------------------------------------------------------------------------
1 | -- predictability
2 | SET synchronous_commit = on;
3 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
4 | ?column?
5 | ----------
6 | init
7 | (1 row)
8 |
9 | -- succeeds, textual plugin, textual consumer
10 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force_binary', '0');
11 | data
12 | ------
13 | (0 rows)
14 |
15 | -- fails, binary plugin, textual consumer
16 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force_binary', '1');
17 | ERROR: logical decoding output plugin "wal2mongo" produces binary output, but function "pg_logical_slot_get_changes(name,pg_lsn,integer,text[])" expects textual data
18 | -- succeeds, textual plugin, binary consumer
19 | SELECT data FROM pg_logical_slot_get_binary_changes('regression_slot', NULL, NULL, 'force_binary', '0');
20 | data
21 | ------
22 | (0 rows)
23 |
24 | -- succeeds, binary plugin, binary consumer
25 | SELECT data FROM pg_logical_slot_get_binary_changes('regression_slot', NULL, NULL, 'force_binary', '1');
26 | data
27 | ------
28 | (0 rows)
29 |
30 | SELECT 'init' FROM pg_drop_replication_slot('regression_slot');
31 | ?column?
32 | ----------
33 | init
34 | (1 row)
35 |
36 |
--------------------------------------------------------------------------------
/expected/boolean.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- create table with boolean type
11 | CREATE TABLE tbl_boolean (a integer primary key, b BOOLEAN);
12 | -- true
13 | INSERT INTO tbl_boolean (a, b) VALUES(1, true);
14 | UPDATE tbl_boolean SET b = false WHERE a = 1;
15 | DELETE FROM tbl_boolean WHERE a = 1;
16 | TRUNCATE TABLE tbl_boolean;
17 | -- 'true'
18 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'true');
19 | UPDATE tbl_boolean SET b = 'false' WHERE a = 1;
20 | DELETE FROM tbl_boolean WHERE a = 1;
21 | TRUNCATE TABLE tbl_boolean;
22 | -- 't'
23 | INSERT INTO tbl_boolean (a, b) VALUES(1, 't');
24 | UPDATE tbl_boolean SET b = 'f' WHERE a = 1;
25 | DELETE FROM tbl_boolean WHERE a = 1;
26 | TRUNCATE TABLE tbl_boolean;
27 | -- TRUE
28 | INSERT INTO tbl_boolean (a, b) VALUES(1, TRUE);
29 | UPDATE tbl_boolean SET b = TRUE WHERE a = 1;
30 | DELETE FROM tbl_boolean WHERE a = 1;
31 | TRUNCATE TABLE tbl_boolean;
32 | -- 'TRUE'
33 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'TRUE');
34 | UPDATE tbl_boolean SET b = 'TRUE' WHERE a = 1;
35 | DELETE FROM tbl_boolean WHERE a = 1;
36 | TRUNCATE TABLE tbl_boolean;
37 | -- 'T'
38 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'T');
39 | UPDATE tbl_boolean SET b = 'T' WHERE a = 1;
40 | DELETE FROM tbl_boolean WHERE a = 1;
41 | TRUNCATE TABLE tbl_boolean;
42 | -- 'on'
43 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'on');
44 | UPDATE tbl_boolean SET b = 'off' WHERE a = 1;
45 | DELETE FROM tbl_boolean WHERE a = 1;
46 | TRUNCATE TABLE tbl_boolean;
47 | -- '1'
48 | INSERT INTO tbl_boolean (a, b) VALUES(1, '1');
49 | UPDATE tbl_boolean SET b = '0' WHERE a = 1;
50 | DELETE FROM tbl_boolean WHERE a = 1;
51 | TRUNCATE TABLE tbl_boolean;
52 | -- false
53 | INSERT INTO tbl_boolean (a, b) VALUES(1, false);
54 | UPDATE tbl_boolean SET b = true WHERE a = 1;
55 | DELETE FROM tbl_boolean WHERE a = 1;
56 | TRUNCATE TABLE tbl_boolean;
57 | -- 'false'
58 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'false');
59 | UPDATE tbl_boolean SET b = 'true' WHERE a = 1;
60 | DELETE FROM tbl_boolean WHERE a = 1;
61 | TRUNCATE TABLE tbl_boolean;
62 | -- 'f'
63 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'f');
64 | UPDATE tbl_boolean SET b = 't' WHERE a = 1;
65 | DELETE FROM tbl_boolean WHERE a = 1;
66 | TRUNCATE TABLE tbl_boolean;
67 | -- FALSE
68 | INSERT INTO tbl_boolean (a, b) VALUES(1, FALSE);
69 | UPDATE tbl_boolean SET b = TRUE WHERE a = 1;
70 | DELETE FROM tbl_boolean WHERE a = 1;
71 | TRUNCATE TABLE tbl_boolean;
72 | -- 'FALSE'
73 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'FALSE');
74 | UPDATE tbl_boolean SET b = 'TRUE' WHERE a = 1;
75 | DELETE FROM tbl_boolean WHERE a = 1;
76 | TRUNCATE TABLE tbl_boolean;
77 | -- 'F'
78 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'F');
79 | UPDATE tbl_boolean SET b = 'T' WHERE a = 1;
80 | DELETE FROM tbl_boolean WHERE a = 1;
81 | TRUNCATE TABLE tbl_boolean;
82 | -- 'off'
83 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'off');
84 | UPDATE tbl_boolean SET b = 'on' WHERE a = 1;
85 | DELETE FROM tbl_boolean WHERE a = 1;
86 | TRUNCATE TABLE tbl_boolean;
87 | -- '0'
88 | INSERT INTO tbl_boolean (a, b) VALUES(1, '0');
89 | UPDATE tbl_boolean SET b = '1' WHERE a = 1;
90 | DELETE FROM tbl_boolean WHERE a = 1;
91 | TRUNCATE TABLE tbl_boolean;
92 | -- 'on' exception
93 | INSERT INTO tbl_boolean (a, b) VALUES(1, on);
94 | ERROR: syntax error at or near "on" at character 42
95 | UPDATE tbl_boolean SET b = off WHERE a = 1;
96 | ERROR: column "off" does not exist at character 28
97 | DELETE FROM tbl_boolean WHERE a = 1;
98 | TRUNCATE TABLE tbl_boolean;
99 | -- 'enable' exception
100 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'enable');
101 | ERROR: invalid input syntax for type boolean: "enable" at character 42
102 | UPDATE tbl_boolean SET b = 'disable' WHERE a = 1;
103 | ERROR: invalid input syntax for type boolean: "disable" at character 28
104 | DELETE FROM tbl_boolean WHERE a = 1;
105 | TRUNCATE TABLE tbl_boolean;
106 | -- 'disable' exception
107 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'disable');
108 | ERROR: invalid input syntax for type boolean: "disable" at character 42
109 | UPDATE tbl_boolean SET b = 'enable' WHERE a = 1;
110 | ERROR: invalid input syntax for type boolean: "enable" at character 28
111 | DELETE FROM tbl_boolean WHERE a = 1;
112 | TRUNCATE TABLE tbl_boolean;
113 | -- peek changes according to action configuration
114 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
115 | data
116 | -----------------------------------------------------------------------------------------------
117 | use mycluster_mydb_regression_slot;
118 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
119 | use mycluster_mydb_regression_slot;
120 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
121 | use mycluster_mydb_regression_slot;
122 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
123 | use mycluster_mydb_regression_slot;
124 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
125 | use mycluster_mydb_regression_slot;
126 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
127 | use mycluster_mydb_regression_slot;
128 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
129 | use mycluster_mydb_regression_slot;
130 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
131 | use mycluster_mydb_regression_slot;
132 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
133 | use mycluster_mydb_regression_slot;
134 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
135 | use mycluster_mydb_regression_slot;
136 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
137 | use mycluster_mydb_regression_slot;
138 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
139 | use mycluster_mydb_regression_slot;
140 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
141 | use mycluster_mydb_regression_slot;
142 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
143 | use mycluster_mydb_regression_slot;
144 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
145 | use mycluster_mydb_regression_slot;
146 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
147 | use mycluster_mydb_regression_slot;
148 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
149 | use mycluster_mydb_regression_slot;
150 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
151 | use mycluster_mydb_regression_slot;
152 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
153 | use mycluster_mydb_regression_slot;
154 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
155 | use mycluster_mydb_regression_slot;
156 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
157 | use mycluster_mydb_regression_slot;
158 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
159 | use mycluster_mydb_regression_slot;
160 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
161 | use mycluster_mydb_regression_slot;
162 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
163 | use mycluster_mydb_regression_slot;
164 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
165 | use mycluster_mydb_regression_slot;
166 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
167 | use mycluster_mydb_regression_slot;
168 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
169 | use mycluster_mydb_regression_slot;
170 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
171 | use mycluster_mydb_regression_slot;
172 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
173 | use mycluster_mydb_regression_slot;
174 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
175 | use mycluster_mydb_regression_slot;
176 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
177 | use mycluster_mydb_regression_slot;
178 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
179 | use mycluster_mydb_regression_slot;
180 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
181 | use mycluster_mydb_regression_slot;
182 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
183 | use mycluster_mydb_regression_slot;
184 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
185 | use mycluster_mydb_regression_slot;
186 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
187 | use mycluster_mydb_regression_slot;
188 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
189 | use mycluster_mydb_regression_slot;
190 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
191 | use mycluster_mydb_regression_slot;
192 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
193 | use mycluster_mydb_regression_slot;
194 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
195 | use mycluster_mydb_regression_slot;
196 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
197 | use mycluster_mydb_regression_slot;
198 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
199 | use mycluster_mydb_regression_slot;
200 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
201 | use mycluster_mydb_regression_slot;
202 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
203 | use mycluster_mydb_regression_slot;
204 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
205 | use mycluster_mydb_regression_slot;
206 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
207 | use mycluster_mydb_regression_slot;
208 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
209 | use mycluster_mydb_regression_slot;
210 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
211 | use mycluster_mydb_regression_slot;
212 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
213 | (96 rows)
214 |
215 | -- get changes according to action configuration
216 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
217 | data
218 | -----------------------------------------------------------------------------------------------
219 | use mycluster_mydb_regression_slot;
220 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
221 | use mycluster_mydb_regression_slot;
222 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
223 | use mycluster_mydb_regression_slot;
224 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
225 | use mycluster_mydb_regression_slot;
226 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
227 | use mycluster_mydb_regression_slot;
228 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
229 | use mycluster_mydb_regression_slot;
230 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
231 | use mycluster_mydb_regression_slot;
232 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
233 | use mycluster_mydb_regression_slot;
234 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
235 | use mycluster_mydb_regression_slot;
236 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
237 | use mycluster_mydb_regression_slot;
238 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
239 | use mycluster_mydb_regression_slot;
240 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
241 | use mycluster_mydb_regression_slot;
242 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
243 | use mycluster_mydb_regression_slot;
244 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
245 | use mycluster_mydb_regression_slot;
246 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
247 | use mycluster_mydb_regression_slot;
248 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
249 | use mycluster_mydb_regression_slot;
250 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
251 | use mycluster_mydb_regression_slot;
252 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
253 | use mycluster_mydb_regression_slot;
254 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
255 | use mycluster_mydb_regression_slot;
256 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
257 | use mycluster_mydb_regression_slot;
258 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
259 | use mycluster_mydb_regression_slot;
260 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
261 | use mycluster_mydb_regression_slot;
262 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:true } );
263 | use mycluster_mydb_regression_slot;
264 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:false } } );
265 | use mycluster_mydb_regression_slot;
266 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
267 | use mycluster_mydb_regression_slot;
268 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
269 | use mycluster_mydb_regression_slot;
270 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
271 | use mycluster_mydb_regression_slot;
272 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
273 | use mycluster_mydb_regression_slot;
274 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
275 | use mycluster_mydb_regression_slot;
276 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
277 | use mycluster_mydb_regression_slot;
278 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
279 | use mycluster_mydb_regression_slot;
280 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
281 | use mycluster_mydb_regression_slot;
282 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
283 | use mycluster_mydb_regression_slot;
284 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
285 | use mycluster_mydb_regression_slot;
286 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
287 | use mycluster_mydb_regression_slot;
288 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
289 | use mycluster_mydb_regression_slot;
290 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
291 | use mycluster_mydb_regression_slot;
292 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
293 | use mycluster_mydb_regression_slot;
294 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
295 | use mycluster_mydb_regression_slot;
296 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
297 | use mycluster_mydb_regression_slot;
298 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
299 | use mycluster_mydb_regression_slot;
300 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
301 | use mycluster_mydb_regression_slot;
302 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
303 | use mycluster_mydb_regression_slot;
304 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
305 | use mycluster_mydb_regression_slot;
306 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
307 | use mycluster_mydb_regression_slot;
308 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
309 | use mycluster_mydb_regression_slot;
310 | db.tbl_boolean.insertOne( { a: NumberInt("1"), b:false } );
311 | use mycluster_mydb_regression_slot;
312 | db.tbl_boolean.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b:true } } );
313 | use mycluster_mydb_regression_slot;
314 | db.tbl_boolean.deleteOne( { a: NumberInt("1") } );
315 | (96 rows)
316 |
317 | -- peek changes with invalid actions
318 | DROP TABLE tbl_boolean;
319 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
320 | ?column?
321 | ----------
322 | end
323 | (1 row)
324 |
325 |
--------------------------------------------------------------------------------
/expected/identity.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- Replica identity nothing
11 | CREATE TABLE testing (a varchar(30) primary key, b INT, c INT);
12 | ALTER TABLE testing REPLICA IDENTITY NOTHING;
13 | BEGIN;
14 | INSERT INTO testing VALUES('aaa', 123, 456);
15 | UPDATE testing set b = 789 where a = 'aaa';
16 | UPDATE testing set a = 'bbb';
17 | DELETE from testing where a = 'bbb';
18 | COMMIT;
19 | -- Replica identity default
20 | BEGIN;
21 | ALTER TABLE testing REPLICA IDENTITY DEFAULT;
22 | INSERT INTO testing VALUES('aaa', 123, 456);
23 | UPDATE testing set b = 789 where a = 'aaa';
24 | UPDATE testing set a = 'bbb';
25 | DELETE from testing where a = 'bbb';
26 | COMMIT;
27 | -- Replica identity full
28 | BEGIN;
29 | ALTER TABLE testing REPLICA IDENTITY FULL;
30 | INSERT INTO testing VALUES('aaa', 123, 456);
31 | UPDATE testing set b = 789 where a = 'aaa';
32 | UPDATE testing set a = 'bbb';
33 | DELETE from testing where a = 'bbb';
34 | COMMIT;
35 | -- Replica identity index
36 | CREATE INDEX idx_test ON testing(b);
37 | ALTER TABLE testing REPLICA IDENTITY INDEX;
38 | ERROR: syntax error at or near "INDEX" at character 38
39 | INSERT INTO testing VALUES('aaa', 123, 456);
40 | UPDATE testing set b = 789 where a = 'aaa';
41 | UPDATE testing set a = 'bbb';
42 | DELETE from testing where a = 'bbb';
43 | COMMIT;
44 | WARNING: there is no transaction in progress
45 | -- get changes
46 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true', 'skip_empty_xacts', 'true');
47 | data
48 | --------------------------------------------------------------------------------------------------------
49 | use mycluster_mydb_regression_slot;
50 | db.testing.insertOne( { a:"aaa", b: NumberInt("123"), c: NumberInt("456") } );
51 | use mycluster_mydb_regression_slot;
52 | db.testing.updateOne( { a:"aaa" }, { $set: { a:"aaa", b: NumberInt("789"), c: NumberInt("456") } } );
53 | use mycluster_mydb_regression_slot;
54 | db.testing.updateOne( { a:"bbb" }, { $set: { a:"bbb", b: NumberInt("789"), c: NumberInt("456") } } );
55 | use mycluster_mydb_regression_slot;
56 | db.testing.deleteOne( (no-tuple-data) );
57 | use mycluster_mydb_regression_slot;
58 | db.testing.insertOne( { a:"aaa", b: NumberInt("123"), c: NumberInt("456") } );
59 | use mycluster_mydb_regression_slot;
60 | db.testing.updateOne( { a:"aaa" }, { $set: { a:"aaa", b: NumberInt("789"), c: NumberInt("456") } } );
61 | use mycluster_mydb_regression_slot;
62 | db.testing.updateOne( { a:"aaa" }, { $set: { a:"bbb", b: NumberInt("789"), c: NumberInt("456") } } );
63 | use mycluster_mydb_regression_slot;
64 | db.testing.deleteOne( { a:"bbb" } );
65 | use mycluster_mydb_regression_slot;
66 | db.testing.insertOne( { a:"aaa", b: NumberInt("123"), c: NumberInt("456") } );
67 | use mycluster_mydb_regression_slot;
68 | db.testing.updateOne( { a:"aaa" }, { $set: { a:"aaa", b: NumberInt("789"), c: NumberInt("456") } } );
69 | use mycluster_mydb_regression_slot;
70 | db.testing.updateOne( { a:"aaa" }, { $set: { a:"bbb", b: NumberInt("789"), c: NumberInt("456") } } );
71 | use mycluster_mydb_regression_slot;
72 | db.testing.deleteOne( { a:"bbb" } );
73 | use mycluster_mydb_regression_slot;
74 | db.testing.insertOne( { a:"aaa", b: NumberInt("123"), c: NumberInt("456") } );
75 | use mycluster_mydb_regression_slot;
76 | db.testing.updateOne( { a:"aaa" }, { $set: { a:"aaa", b: NumberInt("789"), c: NumberInt("456") } } );
77 | use mycluster_mydb_regression_slot;
78 | db.testing.updateOne( { a:"aaa" }, { $set: { a:"bbb", b: NumberInt("789"), c: NumberInt("456") } } );
79 | use mycluster_mydb_regression_slot;
80 | db.testing.deleteOne( { a:"bbb" } );
81 | (32 rows)
82 |
83 | DROP TABLE testing;
84 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
85 | ?column?
86 | ----------
87 | end
88 | (1 row)
89 |
90 |
--------------------------------------------------------------------------------
/expected/json.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | CREATE TABLE testing (a integer primary key,
11 | j1 json,
12 | j2 jsonb,
13 | j3 jsonpath,
14 | j4 json[],
15 | j5 jsonb[]);
16 | -- JSON data type
17 | INSERT INTO testing (a, j1) VALUES(1, '{"customer": "John", "items":{"product":"beer","qty":20}}');
18 | INSERT INTO testing (a, j1) VALUES(2, '{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10},{"product":"cooler","qty":10}]}');
19 | INSERT INTO testing (a, j1) VALUES(3, '{"customer": "Joe", "items":{"product":"wine","qty":20}}');
20 | UPDATE testing set j1='{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10}]}' where a = 3;
21 | DELETE FROM testing where a = 1;
22 | -- JSONB data type
23 | INSERT INTO testing (a, j2) VALUES(4, '{"customer": "John", "items":{"product":"beer","qty":20}}');
24 | INSERT INTO testing (a, j2) VALUES(5, '{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10},{"product":"cooler","qty":10}]}');
25 | INSERT INTO testing (a, j2) VALUES(6, '{"customer": "Joe", "items":{"product":"wine","qty":20}}');
26 | UPDATE testing set j2='{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10}]}' where a = 3;
27 | DELETE FROM testing where a = 1;
28 | -- JSONPATH data type
29 | INSERT INTO testing (a, j3) VALUES(7, '$.equipment.rings[*] ? (@.track.segments > 1)');
30 | UPDATE testing set j3 = '$.equipment.rings[*]' where a = 7;
31 | DELETE from testing where a = 7;
32 | -- JSON[] data type
33 | INSERT into testing (a, j4) VALUES
34 | (
35 | 8,
36 | array[
37 | '{"customer": "Cary", "items":{"product":"beer","qty":1000}}',
38 | '{"customer": "John", "items":{"product":"beer","qty":2000}}',
39 | '{"customer": "David", "items":{"product":"beer","qty":3000}}'
40 | ]::json[]
41 | );
42 | UPDATE testing set j4 =
43 | array[
44 | '{"customer": "Cary"}',
45 | '{"customer": "John"}',
46 | '{"customer": "David"}'
47 | ]::json[] where a = 8;
48 | DELETE FROM testing where a = 8;
49 | -- JSONB[] data type
50 | INSERT into testing (a, j5) VALUES
51 | (
52 | 9,
53 | array[
54 | '{"customer": "Cary", "items":{"product":"beer","qty":1000}}',
55 | '{"customer": "John", "items":{"product":"beer","qty":2000}}',
56 | '{"customer": "David", "items":{"product":"beer","qty":3000}}'
57 | ]::json[]
58 | );
59 | UPDATE testing set j5 =
60 | array[
61 | '{"customer": "Cary"}',
62 | '{"customer": "John"}',
63 | '{"customer": "David"}'
64 | ]::json[] where a = 9;
65 | DELETE FROM testing where a = 9;
66 | -- get the changes
67 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'use_transaction', 'false', 'regress', 'true');
68 | data
69 | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
70 | use mycluster_mydb_regression_slot;
71 | db.testing.insertOne( { a: NumberInt("1"), j1:{"customer": "John", "items":{"product":"beer","qty":20}} } );
72 | use mycluster_mydb_regression_slot;
73 | db.testing.insertOne( { a: NumberInt("2"), j1:{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10},{"product":"cooler","qty":10}]} } );
74 | use mycluster_mydb_regression_slot;
75 | db.testing.insertOne( { a: NumberInt("3"), j1:{"customer": "Joe", "items":{"product":"wine","qty":20}} } );
76 | use mycluster_mydb_regression_slot;
77 | db.testing.updateOne( { a: NumberInt("3") }, { $set: { a: NumberInt("3"), j1:{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10}]}, j2:null, j3:null, j4:null, j5:null } } );
78 | use mycluster_mydb_regression_slot;
79 | db.testing.deleteOne( { a: NumberInt("1") } );
80 | use mycluster_mydb_regression_slot;
81 | db.testing.insertOne( { a: NumberInt("4"), j2:{"items": {"qty": 20, "product": "beer"}, "customer": "John"} } );
82 | use mycluster_mydb_regression_slot;
83 | db.testing.insertOne( { a: NumberInt("5"), j2:{"items": [{"qty": 5, "product": "vodka"}, {"qty": 10, "product": "whiskey"}, {"qty": 10, "product": "cooler"}], "customer": "Michael"} } );
84 | use mycluster_mydb_regression_slot;
85 | db.testing.insertOne( { a: NumberInt("6"), j2:{"items": {"qty": 20, "product": "wine"}, "customer": "Joe"} } );
86 | use mycluster_mydb_regression_slot;
87 | db.testing.updateOne( { a: NumberInt("3") }, { $set: { a: NumberInt("3"), j1:{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10}]}, j2:{"items": [{"qty": 5, "product": "vodka"}, {"qty": 10, "product": "whiskey"}], "customer": "Michael"}, j3:null, j4:null, j5:null } } );
88 | use mycluster_mydb_regression_slot;
89 | db.testing.insertOne( { a: NumberInt("7"), j3:"$.equipment.rings[*]?(@.track.segments > 1)" } );
90 | use mycluster_mydb_regression_slot;
91 | db.testing.updateOne( { a: NumberInt("7") }, { $set: { a: NumberInt("7"), j1:null, j2:null, j3:"$.equipment.rings[*]", j4:null, j5:null } } );
92 | use mycluster_mydb_regression_slot;
93 | db.testing.deleteOne( { a: NumberInt("7") } );
94 | use mycluster_mydb_regression_slot;
95 | db.testing.insertOne( { a: NumberInt("8"), j4:[{"customer": "Cary", "items":{"product":"beer","qty":1000}},{"customer": "John", "items":{"product":"beer","qty":2000}},{"customer": "David", "items":{"product":"beer","qty":3000}}] } );
96 | use mycluster_mydb_regression_slot;
97 | db.testing.updateOne( { a: NumberInt("8") }, { $set: { a: NumberInt("8"), j1:null, j2:null, j3:null, j4:[{"customer": "Cary"},{"customer": "John"},{"customer": "David"}], j5:null } } );
98 | use mycluster_mydb_regression_slot;
99 | db.testing.deleteOne( { a: NumberInt("8") } );
100 | use mycluster_mydb_regression_slot;
101 | db.testing.insertOne( { a: NumberInt("9"), j5:[{"items": {"qty": 1000, "product": "beer"}, "customer": "Cary"},{"items": {"qty": 2000, "product": "beer"}, "customer": "John"},{"items": {"qty": 3000, "product": "beer"}, "customer": "David"}] } );
102 | use mycluster_mydb_regression_slot;
103 | db.testing.updateOne( { a: NumberInt("9") }, { $set: { a: NumberInt("9"), j1:null, j2:null, j3:null, j4:null, j5:[{"customer": "Cary"},{"customer": "John"},{"customer": "David"}] } } );
104 | use mycluster_mydb_regression_slot;
105 | db.testing.deleteOne( { a: NumberInt("9") } );
106 | (36 rows)
107 |
108 | DROP TABLE testing;
109 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
110 | ?column?
111 | ----------
112 | end
113 | (1 row)
114 |
115 |
--------------------------------------------------------------------------------
/expected/numeric.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- create table with different numeric type
11 | CREATE TABLE tbl_int(id serial primary key, a smallint, b integer, c serial, d real, e bigint, f bigserial, g double precision, h decimal, i numeric);
12 | -- minumum values
13 | INSERT INTO tbl_int values(1, -32768, -2147483648, 1, -0.123456, -9223372036854775808, 1, -1.123456789123456, -1234567890.1234567891, -9876543210.0987654321);
14 | UPDATE tbl_int SET a=a+1, b=b+1, c=c+1, d=d+1, e=e+1, f=f+1, g=g+1, h=h+1, i=i+1 WHERE id=1;
15 | DELETE FROM tbl_int WHERE id = 1;
16 | TRUNCATE TABLE tbl_int;
17 | -- maximum values
18 | INSERT INTO tbl_int values(1, 32767, 2147483647, 2147483647, 0.123456, 9223372036854775807, 9223372036854775807, 1.123456789123456, 1234567890.1234567891, 9876543210.0987654321);
19 | UPDATE tbl_int SET a=a-1, b=b-1, c=c-1, d=d-1, e=e-1, f=f-1, g=g-1, h=h-1, i=i-1 WHERE id=1;
20 | DELETE FROM tbl_int WHERE id = 1;
21 | TRUNCATE TABLE tbl_int;
22 | -- peek changes according to action configuration
23 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
24 | data
25 | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
26 | use mycluster_mydb_regression_slot;
27 | db.tbl_int.insertOne( { id: NumberInt("1"), a: NumberInt("-32768"), b: NumberInt("-2147483648"), c: NumberInt("1"), d:-0.123456, e: NumberLong("-9223372036854775808"), f: NumberLong("1"), g: NumberDecimal("-1.123456789123456"), h: NumberDecimal("-1234567890.1234567891"), i: NumberDecimal("-9876543210.0987654321") } );
28 | use mycluster_mydb_regression_slot;
29 | db.tbl_int.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a: NumberInt("-32767"), b: NumberInt("-2147483647"), c: NumberInt("2"), d:0.876544, e: NumberLong("-9223372036854775807"), f: NumberLong("2"), g: NumberDecimal("-0.12345678912345592"), h: NumberDecimal("-1234567889.1234567891"), i: NumberDecimal("-9876543209.0987654321") } } );
30 | use mycluster_mydb_regression_slot;
31 | db.tbl_int.deleteOne( { id: NumberInt("1") } );
32 | use mycluster_mydb_regression_slot;
33 | db.tbl_int.insertOne( { id: NumberInt("1"), a: NumberInt("32767"), b: NumberInt("2147483647"), c: NumberInt("2147483647"), d:0.123456, e: NumberLong("9223372036854775807"), f: NumberLong("9223372036854775807"), g: NumberDecimal("1.123456789123456"), h: NumberDecimal("1234567890.1234567891"), i: NumberDecimal("9876543210.0987654321") } );
34 | use mycluster_mydb_regression_slot;
35 | db.tbl_int.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a: NumberInt("32766"), b: NumberInt("2147483646"), c: NumberInt("2147483646"), d:-0.876544, e: NumberLong("9223372036854775806"), f: NumberLong("9223372036854775806"), g: NumberDecimal("0.12345678912345592"), h: NumberDecimal("1234567889.1234567891"), i: NumberDecimal("9876543209.0987654321") } } );
36 | use mycluster_mydb_regression_slot;
37 | db.tbl_int.deleteOne( { id: NumberInt("1") } );
38 | (12 rows)
39 |
40 | -- get changes according to action configuration
41 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
42 | data
43 | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
44 | use mycluster_mydb_regression_slot;
45 | db.tbl_int.insertOne( { id: NumberInt("1"), a: NumberInt("-32768"), b: NumberInt("-2147483648"), c: NumberInt("1"), d:-0.123456, e: NumberLong("-9223372036854775808"), f: NumberLong("1"), g: NumberDecimal("-1.123456789123456"), h: NumberDecimal("-1234567890.1234567891"), i: NumberDecimal("-9876543210.0987654321") } );
46 | use mycluster_mydb_regression_slot;
47 | db.tbl_int.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a: NumberInt("-32767"), b: NumberInt("-2147483647"), c: NumberInt("2"), d:0.876544, e: NumberLong("-9223372036854775807"), f: NumberLong("2"), g: NumberDecimal("-0.12345678912345592"), h: NumberDecimal("-1234567889.1234567891"), i: NumberDecimal("-9876543209.0987654321") } } );
48 | use mycluster_mydb_regression_slot;
49 | db.tbl_int.deleteOne( { id: NumberInt("1") } );
50 | use mycluster_mydb_regression_slot;
51 | db.tbl_int.insertOne( { id: NumberInt("1"), a: NumberInt("32767"), b: NumberInt("2147483647"), c: NumberInt("2147483647"), d:0.123456, e: NumberLong("9223372036854775807"), f: NumberLong("9223372036854775807"), g: NumberDecimal("1.123456789123456"), h: NumberDecimal("1234567890.1234567891"), i: NumberDecimal("9876543210.0987654321") } );
52 | use mycluster_mydb_regression_slot;
53 | db.tbl_int.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a: NumberInt("32766"), b: NumberInt("2147483646"), c: NumberInt("2147483646"), d:-0.876544, e: NumberLong("9223372036854775806"), f: NumberLong("9223372036854775806"), g: NumberDecimal("0.12345678912345592"), h: NumberDecimal("1234567889.1234567891"), i: NumberDecimal("9876543209.0987654321") } } );
54 | use mycluster_mydb_regression_slot;
55 | db.tbl_int.deleteOne( { id: NumberInt("1") } );
56 | (12 rows)
57 |
58 | -- drop table
59 | DROP TABLE tbl_int;
60 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
61 | ?column?
62 | ----------
63 | end
64 | (1 row)
65 |
66 |
--------------------------------------------------------------------------------
/expected/pkey.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | CREATE TABLE testing_one_pkey (
11 | a smallserial PRIMARY KEY,
12 | b smallint,
13 | c int,
14 | d bigint,
15 | e int,
16 | f real,
17 | g double precision,
18 | h char(20),
19 | i varchar(30),
20 | j text
21 | );
22 | CREATE TABLE testing_multi_pkey (
23 | a smallserial,
24 | b smallint,
25 | c int,
26 | d bigint,
27 | e int,
28 | f real,
29 | g double precision,
30 | h char(20),
31 | i varchar(30),
32 | j text,
33 | PRIMARY KEY(a, b, c)
34 | );
35 | CREATE TABLE testing_no_pkey (
36 | a smallserial,
37 | b smallint,
38 | c int,
39 | d bigint,
40 | e int,
41 | f real,
42 | g double precision,
43 | h char(20),
44 | i varchar(30),
45 | j text
46 | );
47 | CREATE TABLE testing_unique (
48 | a smallserial,
49 | b smallint,
50 | c int,
51 | d bigint,
52 | e int,
53 | f real,
54 | g double precision,
55 | h char(20),
56 | i varchar(30),
57 | j text,
58 | UNIQUE(f, g)
59 | );
60 | INSERT INTO testing_one_pkey (b, c, d, e, f, g, h, i, j) VALUES (1, 2, 33, 555, 666.777777777, 3.33, 'testval1', 'testval2', 'testval3');
61 | INSERT INTO testing_multi_pkey (b, c, d, e, f, g, h, i, j) VALUES (1, 2, 33, 555, 666.777777777, 3.33, 'testval1', 'testval2', 'testval3');
62 | INSERT INTO testing_no_pkey (b, c, d, e, f, g, h, i, j) VALUES (1, 2, 33, 555, 666.777777777, 3.33, 'testval1', 'testval2', 'testval3');
63 | INSERT INTO testing_unique (b, c, d, e, f, g, h, i, j) VALUES (1, 2, 33, 555, 666.777777777, 3.33, 'testval1', 'testval2', 'testval3');
64 | -- non pkey change
65 | UPDATE testing_one_pkey SET f = 777.888888888 WHERE b = 1;
66 | -- pkey change
67 | UPDATE testing_one_pkey SET a = 14 WHERE b = 1;
68 | -- non pkey change
69 | UPDATE testing_multi_pkey SET f = 777.888888888 WHERE b = 1;
70 | -- some pkeys change
71 | UPDATE testing_multi_pkey SET a = 14, c = 14 WHERE b = 1;
72 | -- all pkeys change
73 | UPDATE testing_multi_pkey SET a = 15, b = 15, c = 15 WHERE b = 1;
74 | -- no pkey
75 | UPDATE testing_no_pkey SET h = 'changed' WHERE b = 1;
76 | -- non unique change
77 | UPDATE testing_unique SET h = 'changed' WHERE b = 1;
78 | -- one unique val change
79 | UPDATE testing_unique SET f = 888.888888888 WHERE b = 1;
80 | -- all unique vals change
81 | UPDATE testing_unique SET f = 888.888888888, g = 6.66 WHERE b = 1;
82 | -- get changes
83 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true', 'skip_empty_xacts', 'true');
84 | data
85 | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
86 | use mycluster_mydb_regression_slot;
87 | db.testing_one_pkey.insertOne( { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:666.7778, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } );
88 | use mycluster_mydb_regression_slot;
89 | db.testing_multi_pkey.insertOne( { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:666.7778, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } );
90 | use mycluster_mydb_regression_slot;
91 | db.testing_no_pkey.insertOne( { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:666.7778, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } );
92 | use mycluster_mydb_regression_slot;
93 | db.testing_unique.insertOne( { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:666.7778, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } );
94 | use mycluster_mydb_regression_slot;
95 | db.testing_one_pkey.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:777.8889, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } } );
96 | use mycluster_mydb_regression_slot;
97 | db.testing_one_pkey.updateOne( { a: NumberInt("1") }, { $set: { a: NumberInt("14"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:777.8889, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } } );
98 | use mycluster_mydb_regression_slot;
99 | db.testing_multi_pkey.updateOne( { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2") }, { $set: { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:777.8889, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } } );
100 | use mycluster_mydb_regression_slot;
101 | db.testing_multi_pkey.updateOne( { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2") }, { $set: { a: NumberInt("14"), b: NumberInt("1"), c: NumberInt("14"), d: NumberLong("33"), e: NumberInt("555"), f:777.8889, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } } );
102 | use mycluster_mydb_regression_slot;
103 | db.testing_multi_pkey.updateOne( { a: NumberInt("14"), b: NumberInt("1"), c: NumberInt("14") }, { $set: { a: NumberInt("15"), b: NumberInt("15"), c: NumberInt("15"), d: NumberLong("33"), e: NumberInt("555"), f:777.8889, g: NumberDecimal("3.33"), h:"testval1 ", i:"testval2", j:"testval3" } } );
104 | use mycluster_mydb_regression_slot;
105 | db.testing_no_pkey.updateOne({ selector: "null" }, { $set: { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:666.7778, g: NumberDecimal("3.33"), h:"changed ", i:"testval2", j:"testval3" } } );
106 | use mycluster_mydb_regression_slot;
107 | db.testing_unique.updateOne({ selector: "null" }, { $set: { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:666.7778, g: NumberDecimal("3.33"), h:"changed ", i:"testval2", j:"testval3" } } );
108 | use mycluster_mydb_regression_slot;
109 | db.testing_unique.updateOne({ selector: "null" }, { $set: { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:888.8889, g: NumberDecimal("3.33"), h:"changed ", i:"testval2", j:"testval3" } } );
110 | use mycluster_mydb_regression_slot;
111 | db.testing_unique.updateOne({ selector: "null" }, { $set: { a: NumberInt("1"), b: NumberInt("1"), c: NumberInt("2"), d: NumberLong("33"), e: NumberInt("555"), f:888.8889, g: NumberDecimal("6.66"), h:"changed ", i:"testval2", j:"testval3" } } );
112 | (26 rows)
113 |
114 | DROP TABLE testing_unique;
115 | DROP TABLE testing_no_pkey;
116 | DROP TABLE testing_multi_pkey;
117 | DROP TABLE testing_one_pkey;
118 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
119 | ?column?
120 | ----------
121 | end
122 | (1 row)
123 |
124 |
--------------------------------------------------------------------------------
/expected/specval.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | CREATE TABLE testing (
11 | a serial PRIMARY KEY,
12 | b varchar(80),
13 | c bool,
14 | d real
15 | );
16 | BEGIN;
17 | INSERT INTO testing (b, c, d) VALUES( E'valid: '' " \\ / \d \f \r \t \u447F \u967F invalid: \\g \\k end', FALSE, 123.456);
18 | INSERT INTO testing (b, c, d) VALUES('aaa', 't', '+inf');
19 | INSERT INTO testing (b, c, d) VALUES('aaa', 'f', 'nan');
20 | INSERT INTO testing (b, c, d) VALUES('null', NULL, '-inf');
21 | COMMIT;
22 | -- get changes
23 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true', 'skip_empty_xacts', 'true');
24 | data
25 | -------------------------------------------------------------------------------------------------------------------------------------
26 | use mycluster_mydb_regression_slot;
27 | db.testing.insertOne( { a: NumberInt("1"), b:"valid: '' " \ / d \x0C \r 䑿 陿 invalid: \g \k end", c:false, d:123.456 } );
28 | use mycluster_mydb_regression_slot;
29 | db.testing.insertOne( { a: NumberInt("2"), b:"aaa", c:true, d:Infinity } );
30 | use mycluster_mydb_regression_slot;
31 | db.testing.insertOne( { a: NumberInt("3"), b:"aaa", c:false, d:NaN } );
32 | use mycluster_mydb_regression_slot;
33 | db.testing.insertOne( { a: NumberInt("4"), b:"null", d:-Infinity } );
34 | (8 rows)
35 |
36 | DROP TABLE testing;
37 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
38 | ?column?
39 | ----------
40 | end
41 | (1 row)
42 |
43 |
--------------------------------------------------------------------------------
/expected/specval_1.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | CREATE TABLE testing (
11 | a serial PRIMARY KEY,
12 | b varchar(80),
13 | c bool,
14 | d real
15 | );
16 | BEGIN;
17 | INSERT INTO testing (b, c, d) VALUES( E'valid: '' " \\ / \d \f \r \t \u447F \u967F invalid: \\g \\k end', FALSE, 123.456);
18 | ERROR: Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8 at or near "E'valid: '' " \\ / \d \f \r \t \u447F" at character 39
19 | INSERT INTO testing (b, c, d) VALUES('aaa', 't', '+inf');
20 | ERROR: current transaction is aborted, commands ignored until end of transaction block
21 | INSERT INTO testing (b, c, d) VALUES('aaa', 'f', 'nan');
22 | ERROR: current transaction is aborted, commands ignored until end of transaction block
23 | INSERT INTO testing (b, c, d) VALUES('null', NULL, '-inf');
24 | ERROR: current transaction is aborted, commands ignored until end of transaction block
25 | COMMIT;
26 | -- get changes
27 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true', 'skip_empty_xacts', 'true');
28 | data
29 | ------
30 | (0 rows)
31 |
32 | DROP TABLE testing;
33 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
34 | ?column?
35 | ----------
36 | end
37 | (1 row)
38 |
39 |
--------------------------------------------------------------------------------
/expected/string.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- network address
11 | CREATE TABLE tbl_net (id serial PRIMARY KEY, a inet, b cidr, c macaddr, d macaddr8);
12 | -- ipv4 in different format
13 | INSERT INTO tbl_net(a,b,c,d) VALUES('192.168.100.128/25'::inet, '192.168.100.128/25'::cidr, '08:00:2b:01:02:03'::macaddr, '08:00:2b:01:02:03:04:05'::macaddr8);
14 | INSERT INTO tbl_net(a,b,c,d) VALUES('192.168.0.1/24'::inet, '192.168/24'::cidr, '08-00-2b-01-02-03'::macaddr, '08-00-2b-01-02-03-04-05'::macaddr8);
15 | INSERT INTO tbl_net(a,b,c,d) VALUES('192.168.0.0/16'::inet, '192.168'::cidr, '08002b:010203'::macaddr, '08002b:0102030405'::macaddr8);
16 | INSERT INTO tbl_net(a,b,c,d) VALUES('10.0.0.1/8'::inet, '10'::cidr, '08002b-010203'::macaddr, '08002b-0102030405'::macaddr8);
17 | INSERT INTO tbl_net(a,b,c,d) VALUES('10.1.2.3/32'::inet, '10.1.2.3/32'::cidr, '0800.2b01.0203'::macaddr, '0800.2b01.0203.0405'::macaddr8);
18 | INSERT INTO tbl_net(a,b,c,d) VALUES('2001:4f8:3:ba::/64'::inet, '2001:4f8:3:ba::/64'::cidr, '0800-2b01-0203'::macaddr, '0800-2b01-0203-0405'::macaddr8);
19 | -- ipv6 and mac in lower case
20 | INSERT INTO tbl_net(a,b,c,d) VALUES('2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128'::inet, '2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128'::cidr, '08002b010203'::macaddr, '08002b01:02030405'::macaddr8);
21 | -- ipv6 and mac in upper case
22 | INSERT INTO tbl_net(a,b,c,d) VALUES('2001:4F8:3:BA:2E0:81FF:FE22:D1F1/128'::inet, '2001:4F8:3:BA:2E0:81FF:FE22:D1F1/128'::cidr, '08002B010203'::macaddr, '08002B0102030405'::macaddr8);
23 | UPDATE tbl_net SET a='10.1.2.3/32'::inet, b='10.1.2.3/32'::cidr, c='0800-2b01-0203'::macaddr, d='0800-2b01-0203-0405'::macaddr8 WHERE id=1;
24 | DELETE FROM tbl_net WHERE id = 1;
25 | TRUNCATE TABLE tbl_net;
26 | -- peek changes
27 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
28 | data
29 | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
30 | use mycluster_mydb_regression_slot;
31 | db.tbl_net.insertOne( { id: NumberInt("1"), a:"192.168.100.128/25", b:"192.168.100.128/25", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
32 | use mycluster_mydb_regression_slot;
33 | db.tbl_net.insertOne( { id: NumberInt("2"), a:"192.168.0.1/24", b:"192.168.0.0/24", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
34 | use mycluster_mydb_regression_slot;
35 | db.tbl_net.insertOne( { id: NumberInt("3"), a:"192.168.0.0/16", b:"192.168.0.0/24", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
36 | use mycluster_mydb_regression_slot;
37 | db.tbl_net.insertOne( { id: NumberInt("4"), a:"10.0.0.1/8", b:"10.0.0.0/8", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
38 | use mycluster_mydb_regression_slot;
39 | db.tbl_net.insertOne( { id: NumberInt("5"), a:"10.1.2.3", b:"10.1.2.3/32", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
40 | use mycluster_mydb_regression_slot;
41 | db.tbl_net.insertOne( { id: NumberInt("6"), a:"2001:4f8:3:ba::/64", b:"2001:4f8:3:ba::/64", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
42 | use mycluster_mydb_regression_slot;
43 | db.tbl_net.insertOne( { id: NumberInt("7"), a:"2001:4f8:3:ba:2e0:81ff:fe22:d1f1", b:"2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
44 | use mycluster_mydb_regression_slot;
45 | db.tbl_net.insertOne( { id: NumberInt("8"), a:"2001:4f8:3:ba:2e0:81ff:fe22:d1f1", b:"2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
46 | use mycluster_mydb_regression_slot;
47 | db.tbl_net.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a:"10.1.2.3", b:"10.1.2.3/32", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } } );
48 | use mycluster_mydb_regression_slot;
49 | db.tbl_net.deleteOne( { id: NumberInt("1") } );
50 | (20 rows)
51 |
52 | -- get changes
53 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
54 | data
55 | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
56 | use mycluster_mydb_regression_slot;
57 | db.tbl_net.insertOne( { id: NumberInt("1"), a:"192.168.100.128/25", b:"192.168.100.128/25", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
58 | use mycluster_mydb_regression_slot;
59 | db.tbl_net.insertOne( { id: NumberInt("2"), a:"192.168.0.1/24", b:"192.168.0.0/24", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
60 | use mycluster_mydb_regression_slot;
61 | db.tbl_net.insertOne( { id: NumberInt("3"), a:"192.168.0.0/16", b:"192.168.0.0/24", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
62 | use mycluster_mydb_regression_slot;
63 | db.tbl_net.insertOne( { id: NumberInt("4"), a:"10.0.0.1/8", b:"10.0.0.0/8", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
64 | use mycluster_mydb_regression_slot;
65 | db.tbl_net.insertOne( { id: NumberInt("5"), a:"10.1.2.3", b:"10.1.2.3/32", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
66 | use mycluster_mydb_regression_slot;
67 | db.tbl_net.insertOne( { id: NumberInt("6"), a:"2001:4f8:3:ba::/64", b:"2001:4f8:3:ba::/64", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
68 | use mycluster_mydb_regression_slot;
69 | db.tbl_net.insertOne( { id: NumberInt("7"), a:"2001:4f8:3:ba:2e0:81ff:fe22:d1f1", b:"2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
70 | use mycluster_mydb_regression_slot;
71 | db.tbl_net.insertOne( { id: NumberInt("8"), a:"2001:4f8:3:ba:2e0:81ff:fe22:d1f1", b:"2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } );
72 | use mycluster_mydb_regression_slot;
73 | db.tbl_net.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a:"10.1.2.3", b:"10.1.2.3/32", c:"08:00:2b:01:02:03", d:"08:00:2b:01:02:03:04:05" } } );
74 | use mycluster_mydb_regression_slot;
75 | db.tbl_net.deleteOne( { id: NumberInt("1") } );
76 | (20 rows)
77 |
78 | -- drop table
79 | DROP TABLE tbl_net;
80 | -- geo data
81 | CREATE TABLE tbl_geo (id serial PRIMARY KEY, a point, b line, c lseg, d box, e path, f path, g polygon, h circle);
82 | ALTER TABLE tbl_geo REPLICA IDENTITY FULL;
83 | -- geo different data types
84 | INSERT INTO tbl_geo(a,b,c,d,e,f,g,h) VALUES('(1,1)'::point, '[(1,1),(2,2)]'::line, '[(3,3), (4,4)]'::lseg, '((1,1),(2,2))'::box, '[(1,1),(2,2),(3,3)]'::path, '((1,1),(2,2),(3,3))'::path, '((1,1),(2,2),(3,3),(4,4))'::polygon, '<(1,1),5>'::circle);
85 | UPDATE tbl_geo SET a='11,11'::point, b='((11,11),(12,12))'::line, c='((13,13), (14,14))'::lseg, d='(11,11),(12,12)'::box, e='(11,11),(12,12),(13,13)'::path, f='(11,11,12,12,13,13)'::path, g='(11,11),(12,12),(13,13),(14,14)'::polygon, h='((11,11),15)'::circle WHERE id=1;
86 | INSERT INTO tbl_geo(a,b,c,d,e,f,g,h) VALUES('(1,1)'::point, '(1,1),(2,2)'::line, '(3,3), (4,4)'::lseg, '1,1,2,2'::box, '1,1,2,2,3,3'::path, '1,1,2,2,3,3'::path, '(1,1,2,2,3,3,4,4)'::polygon, '1,1,5'::circle);
87 | UPDATE tbl_geo SET a='21,21'::point, b='21,21,22,22'::line, c='23,23, 24,24'::lseg, d='21,21,22,22'::box, e='(21,21,22,22,23,23)'::path, f='21,21,22,22,23,23'::path, g='21,21,22,22,23,23,24,24'::polygon, h='21,21,25'::circle WHERE id=2;
88 | DELETE FROM tbl_geo WHERE id = 1;
89 | DELETE FROM tbl_geo WHERE id = 2;
90 | TRUNCATE TABLE tbl_geo;
91 | -- peek changes
92 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
93 | data
94 | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
95 | use mycluster_mydb_regression_slot;
96 | db.tbl_geo.insertOne( { id: NumberInt("1"), a:"(1,1)", b:"{1,-1,0}", c:"[(3,3),(4,4)]", d:"(2,2),(1,1)", e:"[(1,1),(2,2),(3,3)]", f:"((1,1),(2,2),(3,3))", g:"((1,1),(2,2),(3,3),(4,4))", h:"<(1,1),5>" } );
97 | use mycluster_mydb_regression_slot;
98 | db.tbl_geo.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a:"(11,11)", b:"{1,-1,0}", c:"[(13,13),(14,14)]", d:"(12,12),(11,11)", e:"((11,11),(12,12),(13,13))", f:"((11,11),(12,12),(13,13))", g:"((11,11),(12,12),(13,13),(14,14))", h:"<(11,11),15>" } } );
99 | use mycluster_mydb_regression_slot;
100 | db.tbl_geo.insertOne( { id: NumberInt("2"), a:"(1,1)", b:"{1,-1,0}", c:"[(3,3),(4,4)]", d:"(2,2),(1,1)", e:"((1,1),(2,2),(3,3))", f:"((1,1),(2,2),(3,3))", g:"((1,1),(2,2),(3,3),(4,4))", h:"<(1,1),5>" } );
101 | use mycluster_mydb_regression_slot;
102 | db.tbl_geo.updateOne( { id: NumberInt("2") }, { $set: { id: NumberInt("2"), a:"(21,21)", b:"{1,-1,0}", c:"[(23,23),(24,24)]", d:"(22,22),(21,21)", e:"((21,21),(22,22),(23,23))", f:"((21,21),(22,22),(23,23))", g:"((21,21),(22,22),(23,23),(24,24))", h:"<(21,21),25>" } } );
103 | use mycluster_mydb_regression_slot;
104 | db.tbl_geo.deleteOne( { id: NumberInt("1") } );
105 | use mycluster_mydb_regression_slot;
106 | db.tbl_geo.deleteOne( { id: NumberInt("2") } );
107 | (12 rows)
108 |
109 | -- get changes
110 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
111 | data
112 | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
113 | use mycluster_mydb_regression_slot;
114 | db.tbl_geo.insertOne( { id: NumberInt("1"), a:"(1,1)", b:"{1,-1,0}", c:"[(3,3),(4,4)]", d:"(2,2),(1,1)", e:"[(1,1),(2,2),(3,3)]", f:"((1,1),(2,2),(3,3))", g:"((1,1),(2,2),(3,3),(4,4))", h:"<(1,1),5>" } );
115 | use mycluster_mydb_regression_slot;
116 | db.tbl_geo.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a:"(11,11)", b:"{1,-1,0}", c:"[(13,13),(14,14)]", d:"(12,12),(11,11)", e:"((11,11),(12,12),(13,13))", f:"((11,11),(12,12),(13,13))", g:"((11,11),(12,12),(13,13),(14,14))", h:"<(11,11),15>" } } );
117 | use mycluster_mydb_regression_slot;
118 | db.tbl_geo.insertOne( { id: NumberInt("2"), a:"(1,1)", b:"{1,-1,0}", c:"[(3,3),(4,4)]", d:"(2,2),(1,1)", e:"((1,1),(2,2),(3,3))", f:"((1,1),(2,2),(3,3))", g:"((1,1),(2,2),(3,3),(4,4))", h:"<(1,1),5>" } );
119 | use mycluster_mydb_regression_slot;
120 | db.tbl_geo.updateOne( { id: NumberInt("2") }, { $set: { id: NumberInt("2"), a:"(21,21)", b:"{1,-1,0}", c:"[(23,23),(24,24)]", d:"(22,22),(21,21)", e:"((21,21),(22,22),(23,23))", f:"((21,21),(22,22),(23,23))", g:"((21,21),(22,22),(23,23),(24,24))", h:"<(21,21),25>" } } );
121 | use mycluster_mydb_regression_slot;
122 | db.tbl_geo.deleteOne( { id: NumberInt("1") } );
123 | use mycluster_mydb_regression_slot;
124 | db.tbl_geo.deleteOne( { id: NumberInt("2") } );
125 | (12 rows)
126 |
127 | -- drop table
128 | DROP TABLE tbl_geo;
129 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
130 | ?column?
131 | ----------
132 | end
133 | (1 row)
134 |
135 |
--------------------------------------------------------------------------------
/expected/timestamptz.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- timestamptz
11 | CREATE TABLE tbl_tstz ( id serial PRIMARY KEY, tstz TIMESTAMPTZ NOT NULL );
12 | INSERT INTO tbl_tstz (tstz) VALUES('2020-03-24 16:00:30-07') ;
13 | UPDATE tbl_tstz SET tstz = '2020-03-24 16:30:00-07' WHERE id=1;
14 | DELETE FROM tbl_tstz WHERE id = 1;
15 | TRUNCATE TABLE tbl_tstz;
16 | -- peek changes according to action configuration
17 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
18 | data
19 | ---------------------------------------------------------------------------------------------------------------------------------
20 | use mycluster_mydb_regression_slot;
21 | db.tbl_tstz.insertOne( { id: NumberInt("1"), tstz: ISODate("2020-03-24T16:00:30-07:00") } );
22 | use mycluster_mydb_regression_slot;
23 | db.tbl_tstz.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), tstz: ISODate("2020-03-24T16:30:00-07:00") } } );
24 | use mycluster_mydb_regression_slot;
25 | db.tbl_tstz.deleteOne( { id: NumberInt("1") } );
26 | (6 rows)
27 |
28 | -- get changes according to action configuration
29 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
30 | data
31 | ---------------------------------------------------------------------------------------------------------------------------------
32 | use mycluster_mydb_regression_slot;
33 | db.tbl_tstz.insertOne( { id: NumberInt("1"), tstz: ISODate("2020-03-24T16:00:30-07:00") } );
34 | use mycluster_mydb_regression_slot;
35 | db.tbl_tstz.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), tstz: ISODate("2020-03-24T16:30:00-07:00") } } );
36 | use mycluster_mydb_regression_slot;
37 | db.tbl_tstz.deleteOne( { id: NumberInt("1") } );
38 | (6 rows)
39 |
40 | -- drop tables
41 | DROP TABLE tbl_tstz;
42 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
43 | ?column?
44 | ----------
45 | end
46 | (1 row)
47 |
48 |
--------------------------------------------------------------------------------
/expected/transaction.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- actions
11 | CREATE TABLE testing (a varchar(30) primary key);
12 | INSERT INTO testing (a) VALUES('cary');
13 | INSERT INTO testing (a) VALUES('john');
14 | INSERT INTO testing (a) VALUES('peter');
15 | -- define a transaction containing sub transactions
16 | BEGIN;
17 | INSERT INTO testing (a) VALUES('david');
18 | SAVEPOINT p1;
19 | INSERT INTO testing (a) VALUES('grant');
20 | SAVEPOINT p2;
21 | INSERT INTO testing (a) VALUES('mike');
22 | SAVEPOINT p3;
23 | INSERT INTO testing (a) VALUES('allen');
24 | SAVEPOINT p4;
25 | INSERT INTO testing (a) VALUES('dan');
26 | ROLLBACK TO SAVEPOINT p3;
27 | RELEASE SAVEPOINT p1;
28 | INSERT INTO testing (a) VALUES('cheese');
29 | COMMIT;
30 | -- peek and get changes with and without transaction mode
31 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'use_transaction', 'true', 'regress', 'true');
32 | data
33 | ---------------------------------------------------------
34 | session0regression_slot = db.getMongo().startSession();
35 | session0regression_slot.startTransaction();
36 | session0regression_slot.commitTransaction();
37 | session0regression_slot.endSession();
38 | session0regression_slot = db.getMongo().startSession();
39 | session0regression_slot.startTransaction();
40 | use mycluster_mydb_regression_slot;
41 | db.testing.insertOne( { a:"cary" } );
42 | session0regression_slot.commitTransaction();
43 | session0regression_slot.endSession();
44 | session0regression_slot = db.getMongo().startSession();
45 | session0regression_slot.startTransaction();
46 | use mycluster_mydb_regression_slot;
47 | db.testing.insertOne( { a:"john" } );
48 | session0regression_slot.commitTransaction();
49 | session0regression_slot.endSession();
50 | session0regression_slot = db.getMongo().startSession();
51 | session0regression_slot.startTransaction();
52 | use mycluster_mydb_regression_slot;
53 | db.testing.insertOne( { a:"peter" } );
54 | session0regression_slot.commitTransaction();
55 | session0regression_slot.endSession();
56 | session0regression_slot = db.getMongo().startSession();
57 | session0regression_slot.startTransaction();
58 | use mycluster_mydb_regression_slot;
59 | db.testing.insertOne( { a:"david" } );
60 | use mycluster_mydb_regression_slot;
61 | db.testing.insertOne( { a:"grant" } );
62 | use mycluster_mydb_regression_slot;
63 | db.testing.insertOne( { a:"mike" } );
64 | use mycluster_mydb_regression_slot;
65 | db.testing.insertOne( { a:"cheese" } );
66 | session0regression_slot.commitTransaction();
67 | session0regression_slot.endSession();
68 | (34 rows)
69 |
70 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'use_transaction', 'false', 'regress', 'true', 'skip_empty_xacts', 'false');
71 | data
72 | -----------------------------------------
73 | use mycluster_mydb_regression_slot;
74 | db.testing.insertOne( { a:"cary" } );
75 | use mycluster_mydb_regression_slot;
76 | db.testing.insertOne( { a:"john" } );
77 | use mycluster_mydb_regression_slot;
78 | db.testing.insertOne( { a:"peter" } );
79 | use mycluster_mydb_regression_slot;
80 | db.testing.insertOne( { a:"david" } );
81 | use mycluster_mydb_regression_slot;
82 | db.testing.insertOne( { a:"grant" } );
83 | use mycluster_mydb_regression_slot;
84 | db.testing.insertOne( { a:"mike" } );
85 | use mycluster_mydb_regression_slot;
86 | db.testing.insertOne( { a:"cheese" } );
87 | (14 rows)
88 |
89 | DROP TABLE testing;
90 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
91 | ?column?
92 | ----------
93 | end
94 | (1 row)
95 |
96 |
--------------------------------------------------------------------------------
/expected/xml.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- xml data
11 | CREATE TABLE tbl_xml (id serial primary key, a xml);
12 | -- insert data simple SQL way
13 | INSERT INTO tbl_xml(a) VALUES(' xml-data '::xml);
14 | -- using XMLPARSE and DOCUMENT syntaxes
15 | INSERT INTO tbl_xml(a) VALUES(XMLPARSE (DOCUMENT' xml-data '));
16 | -- xpath example simple SQL way
17 | INSERT INTO tbl_xml(a) VALUES('
18 |
20 | IT
21 |
22 |
23 | John Smith
24 | 24
25 |
26 |
27 | Michael Black
28 | 28
29 |
30 |
31 | '::xml);
32 | -- xpath example in minify way
33 | INSERT INTO tbl_xml(a) VALUES('ITJohn Smith24Michael Black28'::xml);
34 | -- xpath example using XMLPARSE and DOCUMENT
35 | INSERT INTO tbl_xml(a) VALUES(XMLPARSE (DOCUMENT'
36 |
38 | IT
39 |
40 |
41 | John Smith
42 | 24
43 |
44 |
45 | Michael Black
46 | 28
47 |
48 |
49 | '));
50 | -- update using simple SQL way
51 | UPDATE tbl_xml SET a = '
52 |
54 | IT
55 |
56 |
57 | John Smith
58 | 24
59 |
60 |
61 | Michael Black
62 | 28
63 |
64 |
65 | '::xml WHERE id=1;
66 | -- update using XMLPARSE and DOCUMENT syntaxes
67 | UPDATE tbl_xml SET a =
68 | XMLPARSE (DOCUMENT'
69 |
71 | IT
72 |
73 |
74 | John Smith
75 | 24
76 |
77 |
78 | Michael Black
79 | 28
80 |
81 |
82 | ') WHERE id=2;
83 | -- delete
84 | DELETE FROM tbl_xml WHERE id = 1;
85 | TRUNCATE TABLE tbl_xml;
86 | -- peek changes
87 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
88 | data
89 | -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
90 | use mycluster_mydb_regression_slot;
91 | db.tbl_xml.insertOne( { id: NumberInt("1"), a:" xml-data " } );
92 | use mycluster_mydb_regression_slot;
93 | db.tbl_xml.insertOne( { id: NumberInt("2"), a:" xml-data " } );
94 | use mycluster_mydb_regression_slot;
95 | db.tbl_xml.insertOne( { id: NumberInt("3"), a:" IT John Smith 24 Michael Black 28 " } );
96 | use mycluster_mydb_regression_slot;
97 | db.tbl_xml.insertOne( { id: NumberInt("4"), a:"ITJohn Smith24Michael Black28" } );
98 | use mycluster_mydb_regression_slot;
99 | db.tbl_xml.insertOne( { id: NumberInt("5"), a:" IT John Smith 24 Michael Black 28 " } );
100 | use mycluster_mydb_regression_slot;
101 | db.tbl_xml.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a:" IT John Smith 24 Michael Black 28 " } } );
102 | use mycluster_mydb_regression_slot;
103 | db.tbl_xml.updateOne( { id: NumberInt("2") }, { $set: { id: NumberInt("2"), a:" IT John Smith 24 Michael Black 28 " } } );
104 | use mycluster_mydb_regression_slot;
105 | db.tbl_xml.deleteOne( { id: NumberInt("1") } );
106 | (16 rows)
107 |
108 | -- get changes
109 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
110 | data
111 | -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
112 | use mycluster_mydb_regression_slot;
113 | db.tbl_xml.insertOne( { id: NumberInt("1"), a:" xml-data " } );
114 | use mycluster_mydb_regression_slot;
115 | db.tbl_xml.insertOne( { id: NumberInt("2"), a:" xml-data " } );
116 | use mycluster_mydb_regression_slot;
117 | db.tbl_xml.insertOne( { id: NumberInt("3"), a:" IT John Smith 24 Michael Black 28 " } );
118 | use mycluster_mydb_regression_slot;
119 | db.tbl_xml.insertOne( { id: NumberInt("4"), a:"ITJohn Smith24Michael Black28" } );
120 | use mycluster_mydb_regression_slot;
121 | db.tbl_xml.insertOne( { id: NumberInt("5"), a:" IT John Smith 24 Michael Black 28 " } );
122 | use mycluster_mydb_regression_slot;
123 | db.tbl_xml.updateOne( { id: NumberInt("1") }, { $set: { id: NumberInt("1"), a:" IT John Smith 24 Michael Black 28 " } } );
124 | use mycluster_mydb_regression_slot;
125 | db.tbl_xml.updateOne( { id: NumberInt("2") }, { $set: { id: NumberInt("2"), a:" IT John Smith 24 Michael Black 28 " } } );
126 | use mycluster_mydb_regression_slot;
127 | db.tbl_xml.deleteOne( { id: NumberInt("1") } );
128 | (16 rows)
129 |
130 | -- drop table
131 | DROP TABLE tbl_xml;
132 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
133 | ?column?
134 | ----------
135 | end
136 | (1 row)
137 |
138 |
--------------------------------------------------------------------------------
/expected/xml_1.out:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 | -- predictability
3 | SET synchronous_commit = on;
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | ?column?
6 | ----------
7 | init
8 | (1 row)
9 |
10 | -- xml data
11 | CREATE TABLE tbl_xml (id serial primary key, a xml);
12 | -- insert data simple SQL way
13 | INSERT INTO tbl_xml(a) VALUES(' xml-data '::xml);
14 | ERROR: unsupported XML feature at character 31
15 | -- using XMLPARSE and DOCUMENT syntaxes
16 | INSERT INTO tbl_xml(a) VALUES(XMLPARSE (DOCUMENT' xml-data '));
17 | ERROR: unsupported XML feature
18 | -- xpath example simple SQL way
19 | INSERT INTO tbl_xml(a) VALUES('
20 |
22 | IT
23 |
24 |
25 | John Smith
26 | 24
27 |
28 |
29 | Michael Black
30 | 28
31 |
32 |
33 | '::xml);
34 | ERROR: unsupported XML feature at character 31
35 | -- xpath example in minify way
36 | INSERT INTO tbl_xml(a) VALUES('ITJohn Smith24Michael Black28'::xml);
37 | ERROR: unsupported XML feature at character 31
38 | -- xpath example using XMLPARSE and DOCUMENT
39 | INSERT INTO tbl_xml(a) VALUES(XMLPARSE (DOCUMENT'
40 |
42 | IT
43 |
44 |
45 | John Smith
46 | 24
47 |
48 |
49 | Michael Black
50 | 28
51 |
52 |
53 | '));
54 | ERROR: unsupported XML feature
55 | -- update using simple SQL way
56 | UPDATE tbl_xml SET a = '
57 |
59 | IT
60 |
61 |
62 | John Smith
63 | 24
64 |
65 |
66 | Michael Black
67 | 28
68 |
69 |
70 | '::xml WHERE id=1;
71 | ERROR: unsupported XML feature at character 24
72 | -- update using XMLPARSE and DOCUMENT syntaxes
73 | UPDATE tbl_xml SET a =
74 | XMLPARSE (DOCUMENT'
75 |
77 | IT
78 |
79 |
80 | John Smith
81 | 24
82 |
83 |
84 | Michael Black
85 | 28
86 |
87 |
88 | ') WHERE id=2;
89 | -- delete
90 | DELETE FROM tbl_xml WHERE id = 1;
91 | TRUNCATE TABLE tbl_xml;
92 | -- peek changes
93 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
94 | data
95 | ------
96 | (0 rows)
97 |
98 | -- get changes
99 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
100 | data
101 | ------
102 | (0 rows)
103 |
104 | -- drop table
105 | DROP TABLE tbl_xml;
106 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
107 | ?column?
108 | ----------
109 | end
110 | (1 row)
111 |
112 |
--------------------------------------------------------------------------------
/logical.conf:
--------------------------------------------------------------------------------
1 | wal_level = logical
2 | max_replication_slots = 4
3 |
--------------------------------------------------------------------------------
/sql/actions.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- actions
9 | CREATE TABLE testing (a integer primary key);
10 | INSERT INTO testing (a) VALUES(200);
11 | UPDATE testing SET a = 500 WHERE a = 200;
12 | DELETE FROM testing WHERE a = 500;
13 | TRUNCATE TABLE testing;
14 |
15 | -- peek changes according to action configuration
16 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'insert', 'regress', 'true');
17 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'update', 'regress', 'true');
18 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'delete', 'regress', 'true');
19 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'truncate', 'regress', 'true');
20 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'insert, update, delete, truncate', 'regress', 'true');
21 |
22 | -- peek changes with default action configuraiton
23 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
24 |
25 | -- peek changes with several configuration parameter combinations
26 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include_cluster_name', 'true', 'regress', 'true');
27 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'skip_empty_xacts', 'true', 'regress', 'true');
28 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'only_local', 'true', 'regress', 'true');
29 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'use_transaction', 'true', 'regress', 'true');
30 |
31 | -- peek changes with invalid actions
32 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'actions', 'insert, xxx, delete, xxx', 'regress', 'true');
33 |
34 | DROP TABLE testing;
35 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
36 |
--------------------------------------------------------------------------------
/sql/array.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- create table with different numeric type
9 | CREATE TABLE tbl_array(id serial primary key, a bool[], c char[], d name[], e int2[], f int4[], g text[], h varchar[], i int8[], j float4[], k float8[], l timestamptz[], m numeric[], n uuid[] );
10 |
11 | -- different data types for array
12 | INSERT INTO tbl_array (a, c, d, e, f, g, h, i, j, k, l, m, n) VALUES(
13 | ARRAY[true, false],
14 | ARRAY['c'::char,'h'::char],
15 | ARRAY['student'::name, 'teacher'::name],
16 | ARRAY['123'::int2, '456'::int2],
17 | ARRAY['123456789'::int4,'987654321'::int4],
18 | ARRAY['abc'::text, '123'::text],
19 | ARRAY['ABCD'::varchar, '1234'::varchar],
20 | ARRAY['112233445566778899'::int8, '998877665544332211'::int8],
21 | ARRAY['123.456'::float4, '2222.3333'::float4],
22 | ARRAY['123456.123'::float8, '654321.123'::float8],
23 | ARRAY['2020-03-30 10:18:40.12-07'::timestamptz, '2020-03-30 20:28:40.12-07'::timestamptz],
24 | ARRAY['123456789'::numeric, '987654321'::numeric],
25 | ARRAY['40e6215d-b5c6-4896-987c-f30f3678f608'::uuid, '3f333df6-90a4-4fda-8dd3-9485d27cee36'::uuid]
26 | );
27 | UPDATE tbl_array SET
28 | a=ARRAY[false, true],
29 | c=ARRAY['h'::char, 'c'::char],
30 | d=ARRAY['teacher'::name, 'student'::name],
31 | e=ARRAY['456'::int2, '123'::int2],
32 | f=ARRAY['987654321'::int4, '123456789'::int4],
33 | g=ARRAY['123'::text, 'abc'::text],
34 | h=ARRAY['1234'::varchar, 'ABCD'::varchar],
35 | i=ARRAY['998877665544332211'::int8, '112233445566778899'::int8],
36 | j=ARRAY['2222.3333'::float4, '123.456'::float4],
37 | k=ARRAY['654321.123'::float8, '123456.123'::float8],
38 | l=ARRAY['2020-03-30 20:28:40.12-07'::timestamptz, '2020-03-30 10:18:40.12-07'::timestamptz],
39 | m=ARRAY['987654321'::numeric, '123456789'::numeric],
40 | n=ARRAY['3f333df6-90a4-4fda-8dd3-9485d27cee36'::uuid, '40e6215d-b5c6-4896-987c-f30f3678f608'::uuid]
41 | WHERE id=1;
42 | DELETE FROM tbl_array WHERE id = 1;
43 | TRUNCATE TABLE tbl_array;
44 |
45 |
46 | -- peek changes according to action configuration
47 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
48 |
49 | -- get changes according to action configuration
50 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
51 |
52 | -- drop table
53 | DROP TABLE tbl_array;
54 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
55 |
--------------------------------------------------------------------------------
/sql/binData.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- UUID
9 | CREATE TABLE tbl_uuid ( id serial PRIMARY KEY, a_uuid UUID NOT NULL );
10 | INSERT INTO tbl_uuid (a_uuid) VALUES('47deacb1-3ad0-4a0e-8254-9ad3f589c9f3') ;
11 | UPDATE tbl_uuid SET a_uuid = 'e7d8e462-12cc-49dc-aac2-2b5dccdabeda' WHERE id=1;
12 | DELETE FROM tbl_uuid WHERE id = 1;
13 | TRUNCATE TABLE tbl_uuid;
14 |
15 | -- BYTEA
16 | CREATE TABLE tbl_bytea ( id serial PRIMARY KEY, a_bytea bytea NOT NULL );
17 | ALTER TABLE tbl_bytea REPLICA IDENTITY FULL;
18 | INSERT INTO tbl_bytea(a_bytea) SELECT 'abc \153\154\155 \052\251\124'::bytea RETURNING a_bytea;
19 | UPDATE tbl_bytea SET a_bytea = SELECT '\134'::bytea RETURNING a_bytea;
20 | DELETE FROM tbl_bytea WHERE id = 1;
21 | TRUNCATE TABLE tbl_bytea;
22 |
23 | -- peek changes according to action configuration
24 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
25 |
26 | -- get changes according to action configuration
27 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
28 |
29 |
30 | -- drop tables
31 | DROP TABLE tbl_uuid;
32 | DROP TABLE tbl_bytea;
33 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
34 |
--------------------------------------------------------------------------------
/sql/binary.sql:
--------------------------------------------------------------------------------
1 | -- predictability
2 | SET synchronous_commit = on;
3 |
4 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
5 | -- succeeds, textual plugin, textual consumer
6 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force_binary', '0');
7 | -- fails, binary plugin, textual consumer
8 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force_binary', '1');
9 | -- succeeds, textual plugin, binary consumer
10 | SELECT data FROM pg_logical_slot_get_binary_changes('regression_slot', NULL, NULL, 'force_binary', '0');
11 | -- succeeds, binary plugin, binary consumer
12 | SELECT data FROM pg_logical_slot_get_binary_changes('regression_slot', NULL, NULL, 'force_binary', '1');
13 |
14 | SELECT 'init' FROM pg_drop_replication_slot('regression_slot');
15 |
--------------------------------------------------------------------------------
/sql/boolean.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- create table with boolean type
9 | CREATE TABLE tbl_boolean (a integer primary key, b BOOLEAN);
10 |
11 | -- true
12 | INSERT INTO tbl_boolean (a, b) VALUES(1, true);
13 | UPDATE tbl_boolean SET b = false WHERE a = 1;
14 | DELETE FROM tbl_boolean WHERE a = 1;
15 | TRUNCATE TABLE tbl_boolean;
16 |
17 | -- 'true'
18 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'true');
19 | UPDATE tbl_boolean SET b = 'false' WHERE a = 1;
20 | DELETE FROM tbl_boolean WHERE a = 1;
21 | TRUNCATE TABLE tbl_boolean;
22 |
23 | -- 't'
24 | INSERT INTO tbl_boolean (a, b) VALUES(1, 't');
25 | UPDATE tbl_boolean SET b = 'f' WHERE a = 1;
26 | DELETE FROM tbl_boolean WHERE a = 1;
27 | TRUNCATE TABLE tbl_boolean;
28 |
29 | -- TRUE
30 | INSERT INTO tbl_boolean (a, b) VALUES(1, TRUE);
31 | UPDATE tbl_boolean SET b = TRUE WHERE a = 1;
32 | DELETE FROM tbl_boolean WHERE a = 1;
33 | TRUNCATE TABLE tbl_boolean;
34 |
35 | -- 'TRUE'
36 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'TRUE');
37 | UPDATE tbl_boolean SET b = 'TRUE' WHERE a = 1;
38 | DELETE FROM tbl_boolean WHERE a = 1;
39 | TRUNCATE TABLE tbl_boolean;
40 |
41 | -- 'T'
42 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'T');
43 | UPDATE tbl_boolean SET b = 'T' WHERE a = 1;
44 | DELETE FROM tbl_boolean WHERE a = 1;
45 | TRUNCATE TABLE tbl_boolean;
46 |
47 | -- 'on'
48 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'on');
49 | UPDATE tbl_boolean SET b = 'off' WHERE a = 1;
50 | DELETE FROM tbl_boolean WHERE a = 1;
51 | TRUNCATE TABLE tbl_boolean;
52 |
53 | -- '1'
54 | INSERT INTO tbl_boolean (a, b) VALUES(1, '1');
55 | UPDATE tbl_boolean SET b = '0' WHERE a = 1;
56 | DELETE FROM tbl_boolean WHERE a = 1;
57 | TRUNCATE TABLE tbl_boolean;
58 |
59 | -- false
60 | INSERT INTO tbl_boolean (a, b) VALUES(1, false);
61 | UPDATE tbl_boolean SET b = true WHERE a = 1;
62 | DELETE FROM tbl_boolean WHERE a = 1;
63 | TRUNCATE TABLE tbl_boolean;
64 |
65 | -- 'false'
66 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'false');
67 | UPDATE tbl_boolean SET b = 'true' WHERE a = 1;
68 | DELETE FROM tbl_boolean WHERE a = 1;
69 | TRUNCATE TABLE tbl_boolean;
70 |
71 | -- 'f'
72 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'f');
73 | UPDATE tbl_boolean SET b = 't' WHERE a = 1;
74 | DELETE FROM tbl_boolean WHERE a = 1;
75 | TRUNCATE TABLE tbl_boolean;
76 |
77 | -- FALSE
78 | INSERT INTO tbl_boolean (a, b) VALUES(1, FALSE);
79 | UPDATE tbl_boolean SET b = TRUE WHERE a = 1;
80 | DELETE FROM tbl_boolean WHERE a = 1;
81 | TRUNCATE TABLE tbl_boolean;
82 |
83 | -- 'FALSE'
84 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'FALSE');
85 | UPDATE tbl_boolean SET b = 'TRUE' WHERE a = 1;
86 | DELETE FROM tbl_boolean WHERE a = 1;
87 | TRUNCATE TABLE tbl_boolean;
88 |
89 | -- 'F'
90 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'F');
91 | UPDATE tbl_boolean SET b = 'T' WHERE a = 1;
92 | DELETE FROM tbl_boolean WHERE a = 1;
93 | TRUNCATE TABLE tbl_boolean;
94 |
95 | -- 'off'
96 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'off');
97 | UPDATE tbl_boolean SET b = 'on' WHERE a = 1;
98 | DELETE FROM tbl_boolean WHERE a = 1;
99 | TRUNCATE TABLE tbl_boolean;
100 |
101 | -- '0'
102 | INSERT INTO tbl_boolean (a, b) VALUES(1, '0');
103 | UPDATE tbl_boolean SET b = '1' WHERE a = 1;
104 | DELETE FROM tbl_boolean WHERE a = 1;
105 | TRUNCATE TABLE tbl_boolean;
106 |
107 |
108 | -- 'on' exception
109 | INSERT INTO tbl_boolean (a, b) VALUES(1, on);
110 | UPDATE tbl_boolean SET b = off WHERE a = 1;
111 | DELETE FROM tbl_boolean WHERE a = 1;
112 | TRUNCATE TABLE tbl_boolean;
113 |
114 | -- 'enable' exception
115 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'enable');
116 | UPDATE tbl_boolean SET b = 'disable' WHERE a = 1;
117 | DELETE FROM tbl_boolean WHERE a = 1;
118 | TRUNCATE TABLE tbl_boolean;
119 |
120 | -- 'disable' exception
121 | INSERT INTO tbl_boolean (a, b) VALUES(1, 'disable');
122 | UPDATE tbl_boolean SET b = 'enable' WHERE a = 1;
123 | DELETE FROM tbl_boolean WHERE a = 1;
124 | TRUNCATE TABLE tbl_boolean;
125 |
126 |
127 | -- peek changes according to action configuration
128 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
129 |
130 | -- get changes according to action configuration
131 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
132 |
133 | -- peek changes with invalid actions
134 |
135 | DROP TABLE tbl_boolean;
136 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
137 |
--------------------------------------------------------------------------------
/sql/identity.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- Replica identity nothing
9 | CREATE TABLE testing (a varchar(30) primary key, b INT, c INT);
10 | ALTER TABLE testing REPLICA IDENTITY NOTHING;
11 | BEGIN;
12 | INSERT INTO testing VALUES('aaa', 123, 456);
13 | UPDATE testing set b = 789 where a = 'aaa';
14 | UPDATE testing set a = 'bbb';
15 | DELETE from testing where a = 'bbb';
16 | COMMIT;
17 |
18 | -- Replica identity default
19 | BEGIN;
20 | ALTER TABLE testing REPLICA IDENTITY DEFAULT;
21 | INSERT INTO testing VALUES('aaa', 123, 456);
22 | UPDATE testing set b = 789 where a = 'aaa';
23 | UPDATE testing set a = 'bbb';
24 | DELETE from testing where a = 'bbb';
25 | COMMIT;
26 |
27 | -- Replica identity full
28 | BEGIN;
29 | ALTER TABLE testing REPLICA IDENTITY FULL;
30 | INSERT INTO testing VALUES('aaa', 123, 456);
31 | UPDATE testing set b = 789 where a = 'aaa';
32 | UPDATE testing set a = 'bbb';
33 | DELETE from testing where a = 'bbb';
34 | COMMIT;
35 |
36 | -- Replica identity index
37 | CREATE INDEX idx_test ON testing(b);
38 | ALTER TABLE testing REPLICA IDENTITY INDEX;
39 | INSERT INTO testing VALUES('aaa', 123, 456);
40 | UPDATE testing set b = 789 where a = 'aaa';
41 | UPDATE testing set a = 'bbb';
42 | DELETE from testing where a = 'bbb';
43 | COMMIT;
44 |
45 | -- get changes
46 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true', 'skip_empty_xacts', 'true');
47 |
48 | DROP TABLE testing;
49 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
50 |
--------------------------------------------------------------------------------
/sql/json.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | CREATE TABLE testing (a integer primary key,
9 | j1 json,
10 | j2 jsonb,
11 | j3 jsonpath,
12 | j4 json[],
13 | j5 jsonb[]);
14 |
15 | -- JSON data type
16 | INSERT INTO testing (a, j1) VALUES(1, '{"customer": "John", "items":{"product":"beer","qty":20}}');
17 | INSERT INTO testing (a, j1) VALUES(2, '{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10},{"product":"cooler","qty":10}]}');
18 | INSERT INTO testing (a, j1) VALUES(3, '{"customer": "Joe", "items":{"product":"wine","qty":20}}');
19 | UPDATE testing set j1='{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10}]}' where a = 3;
20 | DELETE FROM testing where a = 1;
21 |
22 | -- JSONB data type
23 | INSERT INTO testing (a, j2) VALUES(4, '{"customer": "John", "items":{"product":"beer","qty":20}}');
24 | INSERT INTO testing (a, j2) VALUES(5, '{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10},{"product":"cooler","qty":10}]}');
25 | INSERT INTO testing (a, j2) VALUES(6, '{"customer": "Joe", "items":{"product":"wine","qty":20}}');
26 | UPDATE testing set j2='{"customer": "Michael", "items":[{"product":"vodka","qty":5},{"product":"whiskey","qty":10}]}' where a = 3;
27 | DELETE FROM testing where a = 1;
28 |
29 | -- JSONPATH data type
30 | INSERT INTO testing (a, j3) VALUES(7, '$.equipment.rings[*] ? (@.track.segments > 1)');
31 | UPDATE testing set j3 = '$.equipment.rings[*]' where a = 7;
32 | DELETE from testing where a = 7;
33 |
34 | -- JSON[] data type
35 | INSERT into testing (a, j4) VALUES
36 | (
37 | 8,
38 | array[
39 | '{"customer": "Cary", "items":{"product":"beer","qty":1000}}',
40 | '{"customer": "John", "items":{"product":"beer","qty":2000}}',
41 | '{"customer": "David", "items":{"product":"beer","qty":3000}}'
42 | ]::json[]
43 | );
44 | UPDATE testing set j4 =
45 | array[
46 | '{"customer": "Cary"}',
47 | '{"customer": "John"}',
48 | '{"customer": "David"}'
49 | ]::json[] where a = 8;
50 | DELETE FROM testing where a = 8;
51 |
52 | -- JSONB[] data type
53 | INSERT into testing (a, j5) VALUES
54 | (
55 | 9,
56 | array[
57 | '{"customer": "Cary", "items":{"product":"beer","qty":1000}}',
58 | '{"customer": "John", "items":{"product":"beer","qty":2000}}',
59 | '{"customer": "David", "items":{"product":"beer","qty":3000}}'
60 | ]::json[]
61 | );
62 | UPDATE testing set j5 =
63 | array[
64 | '{"customer": "Cary"}',
65 | '{"customer": "John"}',
66 | '{"customer": "David"}'
67 | ]::json[] where a = 9;
68 | DELETE FROM testing where a = 9;
69 |
70 | -- get the changes
71 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'use_transaction', 'false', 'regress', 'true');
72 |
73 | DROP TABLE testing;
74 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
75 |
--------------------------------------------------------------------------------
/sql/numeric.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- create table with different numeric type
9 | CREATE TABLE tbl_int(id serial primary key, a smallint, b integer, c serial, d real, e bigint, f bigserial, g double precision, h decimal, i numeric);
10 |
11 | -- minumum values
12 | INSERT INTO tbl_int values(1, -32768, -2147483648, 1, -0.123456, -9223372036854775808, 1, -1.123456789123456, -1234567890.1234567891, -9876543210.0987654321);
13 | UPDATE tbl_int SET a=a+1, b=b+1, c=c+1, d=d+1, e=e+1, f=f+1, g=g+1, h=h+1, i=i+1 WHERE id=1;
14 | DELETE FROM tbl_int WHERE id = 1;
15 | TRUNCATE TABLE tbl_int;
16 |
17 | -- maximum values
18 | INSERT INTO tbl_int values(1, 32767, 2147483647, 2147483647, 0.123456, 9223372036854775807, 9223372036854775807, 1.123456789123456, 1234567890.1234567891, 9876543210.0987654321);
19 | UPDATE tbl_int SET a=a-1, b=b-1, c=c-1, d=d-1, e=e-1, f=f-1, g=g-1, h=h-1, i=i-1 WHERE id=1;
20 | DELETE FROM tbl_int WHERE id = 1;
21 | TRUNCATE TABLE tbl_int;
22 |
23 |
24 | -- peek changes according to action configuration
25 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
26 |
27 | -- get changes according to action configuration
28 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
29 |
30 | -- drop table
31 | DROP TABLE tbl_int;
32 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
33 |
--------------------------------------------------------------------------------
/sql/pkey.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 |
9 | CREATE TABLE testing_one_pkey (
10 | a smallserial PRIMARY KEY,
11 | b smallint,
12 | c int,
13 | d bigint,
14 | e int,
15 | f real,
16 | g double precision,
17 | h char(20),
18 | i varchar(30),
19 | j text
20 | );
21 |
22 | CREATE TABLE testing_multi_pkey (
23 | a smallserial,
24 | b smallint,
25 | c int,
26 | d bigint,
27 | e int,
28 | f real,
29 | g double precision,
30 | h char(20),
31 | i varchar(30),
32 | j text,
33 | PRIMARY KEY(a, b, c)
34 | );
35 |
36 | CREATE TABLE testing_no_pkey (
37 | a smallserial,
38 | b smallint,
39 | c int,
40 | d bigint,
41 | e int,
42 | f real,
43 | g double precision,
44 | h char(20),
45 | i varchar(30),
46 | j text
47 | );
48 |
49 | CREATE TABLE testing_unique (
50 | a smallserial,
51 | b smallint,
52 | c int,
53 | d bigint,
54 | e int,
55 | f real,
56 | g double precision,
57 | h char(20),
58 | i varchar(30),
59 | j text,
60 | UNIQUE(f, g)
61 | );
62 |
63 | INSERT INTO testing_one_pkey (b, c, d, e, f, g, h, i, j) VALUES (1, 2, 33, 555, 666.777777777, 3.33, 'testval1', 'testval2', 'testval3');
64 | INSERT INTO testing_multi_pkey (b, c, d, e, f, g, h, i, j) VALUES (1, 2, 33, 555, 666.777777777, 3.33, 'testval1', 'testval2', 'testval3');
65 | INSERT INTO testing_no_pkey (b, c, d, e, f, g, h, i, j) VALUES (1, 2, 33, 555, 666.777777777, 3.33, 'testval1', 'testval2', 'testval3');
66 | INSERT INTO testing_unique (b, c, d, e, f, g, h, i, j) VALUES (1, 2, 33, 555, 666.777777777, 3.33, 'testval1', 'testval2', 'testval3');
67 |
68 | -- non pkey change
69 | UPDATE testing_one_pkey SET f = 777.888888888 WHERE b = 1;
70 |
71 | -- pkey change
72 | UPDATE testing_one_pkey SET a = 14 WHERE b = 1;
73 |
74 | -- non pkey change
75 | UPDATE testing_multi_pkey SET f = 777.888888888 WHERE b = 1;
76 |
77 | -- some pkeys change
78 | UPDATE testing_multi_pkey SET a = 14, c = 14 WHERE b = 1;
79 |
80 | -- all pkeys change
81 | UPDATE testing_multi_pkey SET a = 15, b = 15, c = 15 WHERE b = 1;
82 |
83 | -- no pkey
84 | UPDATE testing_no_pkey SET h = 'changed' WHERE b = 1;
85 |
86 | -- non unique change
87 | UPDATE testing_unique SET h = 'changed' WHERE b = 1;
88 |
89 | -- one unique val change
90 | UPDATE testing_unique SET f = 888.888888888 WHERE b = 1;
91 |
92 | -- all unique vals change
93 | UPDATE testing_unique SET f = 888.888888888, g = 6.66 WHERE b = 1;
94 |
95 | -- get changes
96 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true', 'skip_empty_xacts', 'true');
97 |
98 | DROP TABLE testing_unique;
99 | DROP TABLE testing_no_pkey;
100 | DROP TABLE testing_multi_pkey;
101 | DROP TABLE testing_one_pkey;
102 |
103 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
104 |
--------------------------------------------------------------------------------
/sql/specval.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | CREATE TABLE testing (
9 | a serial PRIMARY KEY,
10 | b varchar(80),
11 | c bool,
12 | d real
13 | );
14 |
15 | BEGIN;
16 | INSERT INTO testing (b, c, d) VALUES( E'valid: '' " \\ / \d \f \r \t \u447F \u967F invalid: \\g \\k end', FALSE, 123.456);
17 | INSERT INTO testing (b, c, d) VALUES('aaa', 't', '+inf');
18 | INSERT INTO testing (b, c, d) VALUES('aaa', 'f', 'nan');
19 | INSERT INTO testing (b, c, d) VALUES('null', NULL, '-inf');
20 | COMMIT;
21 |
22 | -- get changes
23 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true', 'skip_empty_xacts', 'true');
24 |
25 | DROP TABLE testing;
26 |
27 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
28 |
--------------------------------------------------------------------------------
/sql/string.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- network address
9 | CREATE TABLE tbl_net (id serial PRIMARY KEY, a inet, b cidr, c macaddr, d macaddr8);
10 |
11 | -- ipv4 in different format
12 | INSERT INTO tbl_net(a,b,c,d) VALUES('192.168.100.128/25'::inet, '192.168.100.128/25'::cidr, '08:00:2b:01:02:03'::macaddr, '08:00:2b:01:02:03:04:05'::macaddr8);
13 | INSERT INTO tbl_net(a,b,c,d) VALUES('192.168.0.1/24'::inet, '192.168/24'::cidr, '08-00-2b-01-02-03'::macaddr, '08-00-2b-01-02-03-04-05'::macaddr8);
14 | INSERT INTO tbl_net(a,b,c,d) VALUES('192.168.0.0/16'::inet, '192.168'::cidr, '08002b:010203'::macaddr, '08002b:0102030405'::macaddr8);
15 | INSERT INTO tbl_net(a,b,c,d) VALUES('10.0.0.1/8'::inet, '10'::cidr, '08002b-010203'::macaddr, '08002b-0102030405'::macaddr8);
16 | INSERT INTO tbl_net(a,b,c,d) VALUES('10.1.2.3/32'::inet, '10.1.2.3/32'::cidr, '0800.2b01.0203'::macaddr, '0800.2b01.0203.0405'::macaddr8);
17 | INSERT INTO tbl_net(a,b,c,d) VALUES('2001:4f8:3:ba::/64'::inet, '2001:4f8:3:ba::/64'::cidr, '0800-2b01-0203'::macaddr, '0800-2b01-0203-0405'::macaddr8);
18 |
19 | -- ipv6 and mac in lower case
20 | INSERT INTO tbl_net(a,b,c,d) VALUES('2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128'::inet, '2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128'::cidr, '08002b010203'::macaddr, '08002b01:02030405'::macaddr8);
21 | -- ipv6 and mac in upper case
22 | INSERT INTO tbl_net(a,b,c,d) VALUES('2001:4F8:3:BA:2E0:81FF:FE22:D1F1/128'::inet, '2001:4F8:3:BA:2E0:81FF:FE22:D1F1/128'::cidr, '08002B010203'::macaddr, '08002B0102030405'::macaddr8);
23 |
24 | UPDATE tbl_net SET a='10.1.2.3/32'::inet, b='10.1.2.3/32'::cidr, c='0800-2b01-0203'::macaddr, d='0800-2b01-0203-0405'::macaddr8 WHERE id=1;
25 | DELETE FROM tbl_net WHERE id = 1;
26 | TRUNCATE TABLE tbl_net;
27 |
28 | -- peek changes
29 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
30 |
31 | -- get changes
32 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
33 |
34 | -- drop table
35 | DROP TABLE tbl_net;
36 |
37 |
38 | -- geo data
39 | CREATE TABLE tbl_geo (id serial PRIMARY KEY, a point, b line, c lseg, d box, e path, f path, g polygon, h circle);
40 | ALTER TABLE tbl_geo REPLICA IDENTITY FULL;
41 |
42 | -- geo different data types
43 | INSERT INTO tbl_geo(a,b,c,d,e,f,g,h) VALUES('(1,1)'::point, '[(1,1),(2,2)]'::line, '[(3,3), (4,4)]'::lseg, '((1,1),(2,2))'::box, '[(1,1),(2,2),(3,3)]'::path, '((1,1),(2,2),(3,3))'::path, '((1,1),(2,2),(3,3),(4,4))'::polygon, '<(1,1),5>'::circle);
44 |
45 | UPDATE tbl_geo SET a='11,11'::point, b='((11,11),(12,12))'::line, c='((13,13), (14,14))'::lseg, d='(11,11),(12,12)'::box, e='(11,11),(12,12),(13,13)'::path, f='(11,11,12,12,13,13)'::path, g='(11,11),(12,12),(13,13),(14,14)'::polygon, h='((11,11),15)'::circle WHERE id=1;
46 |
47 | INSERT INTO tbl_geo(a,b,c,d,e,f,g,h) VALUES('(1,1)'::point, '(1,1),(2,2)'::line, '(3,3), (4,4)'::lseg, '1,1,2,2'::box, '1,1,2,2,3,3'::path, '1,1,2,2,3,3'::path, '(1,1,2,2,3,3,4,4)'::polygon, '1,1,5'::circle);
48 |
49 | UPDATE tbl_geo SET a='21,21'::point, b='21,21,22,22'::line, c='23,23, 24,24'::lseg, d='21,21,22,22'::box, e='(21,21,22,22,23,23)'::path, f='21,21,22,22,23,23'::path, g='21,21,22,22,23,23,24,24'::polygon, h='21,21,25'::circle WHERE id=2;
50 |
51 | DELETE FROM tbl_geo WHERE id = 1;
52 | DELETE FROM tbl_geo WHERE id = 2;
53 | TRUNCATE TABLE tbl_geo;
54 |
55 | -- peek changes
56 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
57 |
58 | -- get changes
59 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
60 |
61 | -- drop table
62 | DROP TABLE tbl_geo;
63 |
64 |
65 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
66 |
--------------------------------------------------------------------------------
/sql/timestamptz.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- timestamptz
9 | CREATE TABLE tbl_tstz ( id serial PRIMARY KEY, tstz TIMESTAMPTZ NOT NULL );
10 | INSERT INTO tbl_tstz (tstz) VALUES('2020-03-24 16:00:30-07') ;
11 | UPDATE tbl_tstz SET tstz = '2020-03-24 16:30:00-07' WHERE id=1;
12 | DELETE FROM tbl_tstz WHERE id = 1;
13 | TRUNCATE TABLE tbl_tstz;
14 |
15 | -- peek changes according to action configuration
16 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
17 |
18 | -- get changes according to action configuration
19 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
20 |
21 |
22 | -- drop tables
23 | DROP TABLE tbl_tstz;
24 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
25 |
--------------------------------------------------------------------------------
/sql/transaction.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- actions
9 | CREATE TABLE testing (a varchar(30) primary key);
10 | INSERT INTO testing (a) VALUES('cary');
11 | INSERT INTO testing (a) VALUES('john');
12 | INSERT INTO testing (a) VALUES('peter');
13 |
14 | -- define a transaction containing sub transactions
15 | BEGIN;
16 | INSERT INTO testing (a) VALUES('david');
17 | SAVEPOINT p1;
18 | INSERT INTO testing (a) VALUES('grant');
19 | SAVEPOINT p2;
20 | INSERT INTO testing (a) VALUES('mike');
21 | SAVEPOINT p3;
22 | INSERT INTO testing (a) VALUES('allen');
23 | SAVEPOINT p4;
24 | INSERT INTO testing (a) VALUES('dan');
25 | ROLLBACK TO SAVEPOINT p3;
26 | RELEASE SAVEPOINT p1;
27 | INSERT INTO testing (a) VALUES('cheese');
28 | COMMIT;
29 |
30 |
31 | -- peek and get changes with and without transaction mode
32 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'use_transaction', 'true', 'regress', 'true');
33 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'use_transaction', 'false', 'regress', 'true', 'skip_empty_xacts', 'false');
34 |
35 | DROP TABLE testing;
36 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
37 |
--------------------------------------------------------------------------------
/sql/xml.sql:
--------------------------------------------------------------------------------
1 | \set VERBOSITY terse
2 |
3 | -- predictability
4 | SET synchronous_commit = on;
5 |
6 | SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'wal2mongo');
7 |
8 | -- xml data
9 | CREATE TABLE tbl_xml (id serial primary key, a xml);
10 |
11 | -- insert data simple SQL way
12 | INSERT INTO tbl_xml(a) VALUES(' xml-data '::xml);
13 | -- using XMLPARSE and DOCUMENT syntaxes
14 | INSERT INTO tbl_xml(a) VALUES(XMLPARSE (DOCUMENT' xml-data '));
15 | -- xpath example simple SQL way
16 | INSERT INTO tbl_xml(a) VALUES('
17 |
19 | IT
20 |
21 |
22 | John Smith
23 | 24
24 |
25 |
26 | Michael Black
27 | 28
28 |
29 |
30 | '::xml);
31 | -- xpath example in minify way
32 | INSERT INTO tbl_xml(a) VALUES('ITJohn Smith24Michael Black28'::xml);
33 | -- xpath example using XMLPARSE and DOCUMENT
34 | INSERT INTO tbl_xml(a) VALUES(XMLPARSE (DOCUMENT'
35 |
37 | IT
38 |
39 |
40 | John Smith
41 | 24
42 |
43 |
44 | Michael Black
45 | 28
46 |
47 |
48 | '));
49 |
50 | -- update using simple SQL way
51 | UPDATE tbl_xml SET a = '
52 |
54 | IT
55 |
56 |
57 | John Smith
58 | 24
59 |
60 |
61 | Michael Black
62 | 28
63 |
64 |
65 | '::xml WHERE id=1;
66 |
67 | -- update using XMLPARSE and DOCUMENT syntaxes
68 | UPDATE tbl_xml SET a =
69 | XMLPARSE (DOCUMENT'
70 |
72 | IT
73 |
74 |
75 | John Smith
76 | 24
77 |
78 |
79 | Michael Black
80 | 28
81 |
82 |
83 | ') WHERE id=2;
84 |
85 | -- delete
86 | DELETE FROM tbl_xml WHERE id = 1;
87 | TRUNCATE TABLE tbl_xml;
88 |
89 | -- peek changes
90 | SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'regress', 'true');
91 |
92 | -- get changes
93 | SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'regress', 'true');
94 |
95 | -- drop table
96 | DROP TABLE tbl_xml;
97 |
98 | SELECT 'end' FROM pg_drop_replication_slot('regression_slot');
99 |
--------------------------------------------------------------------------------
/wal2mongo.c:
--------------------------------------------------------------------------------
1 | /*-------------------------------------------------------------------------
2 | *
3 | * wal2mongo.c
4 | * logical decoding output plugin for MongoDB
5 | *
6 | * Copyright (c) 2019-2020, Highgo Global Development Center
7 | *
8 | * IDENTIFICATION
9 | * when build under PostgreSQL source code tree,
10 | * contrib/wal2mongo/wal2mongo.c
11 | *
12 | *-------------------------------------------------------------------------
13 | */
14 | #include "postgres.h"
15 |
16 | #include "catalog/pg_type.h"
17 |
18 | #include "replication/logical.h"
19 | #include "replication/origin.h"
20 | #include "commands/dbcommands.h"
21 | #include "utils/builtins.h"
22 | #include "utils/lsyscache.h"
23 | #include "utils/memutils.h"
24 | #include "utils/rel.h"
25 | #include "utils/guc.h"
26 | #include "common/jsonapi.h"
27 | #include "utils/datetime.h"
28 | #include "utils/json.h"
29 | #include "miscadmin.h"
30 |
31 | PG_MODULE_MAGIC;
32 |
33 | /* These must be available to pg_dlsym() */
34 | extern void _PG_init(void);
35 | extern void _PG_output_plugin_init(OutputPluginCallbacks *cb);
36 |
37 | typedef struct
38 | {
39 | bool insert;
40 | bool update;
41 | bool delete;
42 | bool truncate;
43 | } Wal2MongoAction;
44 |
45 | typedef struct
46 | {
47 | MemoryContext context;
48 | bool skip_empty_xacts;
49 | bool xact_wrote_changes;
50 | bool only_local;
51 | bool use_transaction;
52 | bool include_cluster_name;
53 | bool regress;
54 | Wal2MongoAction actions;
55 | } Wal2MongoData;
56 |
57 | static void pg_w2m_decode_startup(LogicalDecodingContext *ctx,
58 | OutputPluginOptions *opt,
59 | bool is_init);
60 |
61 | static void pg_w2m_decode_shutdown(LogicalDecodingContext *ctx);
62 |
63 | static void pg_w2m_decode_begin_txn(LogicalDecodingContext *ctx,
64 | ReorderBufferTXN *txn);
65 |
66 | static void pg_w2m_decode_begin(LogicalDecodingContext *ctx,
67 | Wal2MongoData *data,
68 | ReorderBufferTXN *txn);
69 |
70 | static void pg_w2m_decode_commit_txn(LogicalDecodingContext *ctx,
71 | ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
72 |
73 | static void pg_w2m_decode_change(LogicalDecodingContext *ctx,
74 | ReorderBufferTXN *txn, Relation rel,
75 | ReorderBufferChange *change);
76 |
77 | static void pg_w2m_decode_truncate(LogicalDecodingContext *ctx,
78 | ReorderBufferTXN *txn,
79 | int nrelations, Relation relations[],
80 | ReorderBufferChange *change);
81 |
82 | static bool pg_w2m_decode_filter(LogicalDecodingContext *ctx,
83 | RepOriginId origin_id);
84 |
85 | static void pg_w2m_decode_message(LogicalDecodingContext *ctx,
86 | ReorderBufferTXN *txn, XLogRecPtr message_lsn,
87 | bool transactional, const char *prefix,
88 | Size sz, const char *message);
89 |
90 | static bool split_string_to_list(char *rawstring, char separator, List **sl);
91 |
92 |
93 | /* Will be called immediately after loaded */
94 | void
95 | _PG_init(void)
96 | {
97 |
98 | }
99 |
100 | /* Initialize callback functions */
101 | void
102 | _PG_output_plugin_init(OutputPluginCallbacks *cb)
103 | {
104 | AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
105 |
106 | cb->startup_cb = pg_w2m_decode_startup;
107 | cb->begin_cb = pg_w2m_decode_begin_txn;
108 | cb->change_cb = pg_w2m_decode_change;
109 | cb->truncate_cb = pg_w2m_decode_truncate;
110 | cb->commit_cb = pg_w2m_decode_commit_txn;
111 | cb->filter_by_origin_cb = pg_w2m_decode_filter;
112 | cb->shutdown_cb = pg_w2m_decode_shutdown;
113 | cb->message_cb = pg_w2m_decode_message;
114 | }
115 |
116 |
117 | /* Initialize this plugin's resources */
118 | static void
119 | pg_w2m_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
120 | bool is_init)
121 | {
122 | ListCell *option;
123 | Wal2MongoData *data;
124 |
125 | data = palloc0(sizeof(Wal2MongoData));
126 | data->context = AllocSetContextCreate(ctx->context,
127 | "wal2mongo context",
128 | ALLOCSET_DEFAULT_SIZES);
129 | data->skip_empty_xacts = false;
130 | data->only_local = false;
131 | data->use_transaction = false;
132 | data->include_cluster_name = true;
133 | data->regress = false;
134 |
135 | data->actions.delete = true;
136 | data->actions.insert = true;
137 | data->actions.update = true;
138 | data->actions.truncate = true;
139 |
140 | ctx->output_plugin_private = data;
141 |
142 | opt->output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT;
143 | opt->receive_rewrites = false;
144 |
145 | foreach(option, ctx->output_plugin_options)
146 | {
147 | DefElem *elem = lfirst(option);
148 | Assert(elem->arg == NULL || IsA(elem->arg, String));
149 | if (strcmp(elem->defname, "skip_empty_xacts") == 0)
150 | {
151 | /* if option value is NULL then assume that value is false */
152 | if (elem->arg == NULL)
153 | data->skip_empty_xacts = false;
154 | else if (!parse_bool(strVal(elem->arg), &data->skip_empty_xacts))
155 | ereport(ERROR,
156 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
157 | errmsg("could not parse value \"%s\" for parameter \"%s\"",
158 | strVal(elem->arg), elem->defname)));
159 | }
160 | else if (strcmp(elem->defname, "only_local") == 0)
161 | {
162 | /* if option value is NULL then assume that value is false */
163 | if (elem->arg == NULL)
164 | data->only_local = false;
165 | else if (!parse_bool(strVal(elem->arg), &data->only_local))
166 | ereport(ERROR,
167 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
168 | errmsg("could not parse value \"%s\" for parameter \"%s\"",
169 | strVal(elem->arg), elem->defname)));
170 | }
171 | else if (strcmp(elem->defname, "use_transaction") == 0)
172 | {
173 | /* if option value is NULL then assume that value is false */
174 | if (elem->arg == NULL)
175 | data->use_transaction = false;
176 | else if (!parse_bool(strVal(elem->arg), &data->use_transaction))
177 | ereport(ERROR,
178 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
179 | errmsg("could not parse value \"%s\" for parameter \"%s\"",
180 | strVal(elem->arg), elem->defname)));
181 | }
182 | else if (strcmp(elem->defname, "force_binary") == 0)
183 | {
184 | bool force_binary;
185 |
186 | if (elem->arg == NULL)
187 | continue;
188 | else if (!parse_bool(strVal(elem->arg), &force_binary))
189 | ereport(ERROR,
190 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
191 | errmsg("could not parse value \"%s\" for parameter \"%s\"",
192 | strVal(elem->arg), elem->defname)));
193 |
194 | if (force_binary)
195 | opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
196 | }
197 | else if (strcmp(elem->defname, "include_cluster_name") == 0)
198 | {
199 | /* if option value is NULL then assume that value is false */
200 | if (elem->arg == NULL)
201 | data->include_cluster_name = false;
202 | else if (!parse_bool(strVal(elem->arg), &data->include_cluster_name))
203 | ereport(ERROR,
204 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
205 | errmsg("could not parse value \"%s\" for parameter \"%s\"",
206 | strVal(elem->arg), elem->defname)));
207 | }
208 | else if (strcmp(elem->defname, "regress") == 0)
209 | {
210 | /* if option value is NULL then assume that value is false */
211 | if (elem->arg == NULL)
212 | data->regress = false;
213 | else if (!parse_bool(strVal(elem->arg), &data->regress))
214 | ereport(ERROR,
215 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
216 | errmsg("could not parse value \"%s\" for parameter \"%s\"",
217 | strVal(elem->arg), elem->defname)));
218 | }
219 | else if (strcmp(elem->defname, "actions") == 0)
220 | {
221 | char *rawstr;
222 |
223 | if (elem->arg == NULL)
224 | {
225 | elog(DEBUG1, "actions argument is null");
226 | /* argument null means default; nothing to do here */
227 | }
228 | else
229 | {
230 | List *selected_actions = NIL;
231 | ListCell *lc;
232 |
233 | rawstr = pstrdup(strVal(elem->arg));
234 | if (!split_string_to_list(rawstr, ',', &selected_actions))
235 | {
236 | pfree(rawstr);
237 | ereport(ERROR,
238 | (errcode(ERRCODE_INVALID_NAME),
239 | errmsg("could not parse value \"%s\" for parameter \"%s\"",
240 | strVal(elem->arg), elem->defname)));
241 | }
242 |
243 | data->actions.insert = false;
244 | data->actions.update = false;
245 | data->actions.delete = false;
246 | data->actions.truncate = false;
247 |
248 | foreach(lc, selected_actions)
249 | {
250 | char *p = lfirst(lc);
251 |
252 | if (strcmp(p, "insert") == 0)
253 | data->actions.insert = true;
254 | else if (strcmp(p, "update") == 0)
255 | data->actions.update = true;
256 | else if (strcmp(p, "delete") == 0)
257 | data->actions.delete = true;
258 | else if (strcmp(p, "truncate") == 0)
259 | data->actions.truncate = true;
260 | else
261 | ereport(ERROR,
262 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
263 | errmsg("could not parse value \"%s\" for parameter \"%s\"",
264 | p, elem->defname)));
265 | }
266 |
267 | pfree(rawstr);
268 | list_free_deep(selected_actions);
269 | }
270 | }
271 | else
272 | {
273 | ereport(ERROR,
274 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
275 | errmsg("option \"%s\" = \"%s\" is unknown",
276 | elem->defname,
277 | elem->arg ? strVal(elem->arg) : "(null)")));
278 | }
279 | }
280 | }
281 |
282 | /* Cleanup this plugin's resources */
283 | static void
284 | pg_w2m_decode_shutdown(LogicalDecodingContext *ctx)
285 | {
286 | Wal2MongoData *data = ctx->output_plugin_private;
287 |
288 | /* cleanup our own resources via memory context reset */
289 | MemoryContextDelete(data->context);
290 | }
291 |
292 | /* BEGIN callback */
293 | static void
294 | pg_w2m_decode_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
295 | {
296 | Wal2MongoData *data = ctx->output_plugin_private;
297 |
298 | data->xact_wrote_changes = false;
299 | if (data->skip_empty_xacts)
300 | return;
301 |
302 | pg_w2m_decode_begin(ctx, data, txn);
303 | }
304 |
305 | static void pg_w2m_decode_begin(LogicalDecodingContext *ctx,
306 | Wal2MongoData *data,
307 | ReorderBufferTXN *txn)
308 | {
309 |
310 | /* Skip this callback if transaction mode is not enabled */
311 | if(!data->use_transaction)
312 | return;
313 |
314 | /* first write the session variable for Mongo */
315 | OutputPluginPrepareWrite(ctx, false);
316 | appendStringInfo(ctx->out, "session%u%s = db.getMongo().startSession();",
317 | data->regress == true? 0 : txn->xid, ctx->slot->data.name.data);
318 | OutputPluginWrite(ctx, false);
319 |
320 | /* then write transaction start */
321 | OutputPluginPrepareWrite(ctx, true);
322 | appendStringInfo(ctx->out, "session%u%s.startTransaction();",
323 | data->regress == true? 0 : txn->xid, ctx->slot->data.name.data);
324 | OutputPluginWrite(ctx, true);
325 | }
326 |
327 | /* COMMIT callback */
328 | static void
329 | pg_w2m_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
330 | XLogRecPtr commit_lsn)
331 | {
332 | Wal2MongoData *data = ctx->output_plugin_private;
333 |
334 | /* Skip this callback if it is an empty transaction */
335 | if (data->skip_empty_xacts && !data->xact_wrote_changes)
336 | return;
337 |
338 | /* Skip this callback if transaction mode is not enabled */
339 | if(!data->use_transaction)
340 | return;
341 |
342 | /* first write the commit transaction cmd */
343 | OutputPluginPrepareWrite(ctx, false);
344 | appendStringInfo(ctx->out, "session%u%s.commitTransaction();",
345 | data->regress == true? 0 : txn->xid, ctx->slot->data.name.data);
346 | OutputPluginWrite(ctx, false);
347 |
348 | /* then write session termination */
349 | OutputPluginPrepareWrite(ctx, true);
350 | appendStringInfo(ctx->out, "session%u%s.endSession();",
351 | data->regress == true? 0 : txn->xid, ctx->slot->data.name.data);
352 | OutputPluginWrite(ctx, true);
353 | }
354 |
355 | /* Generic message callback */
356 | static bool
357 | pg_w2m_decode_filter(LogicalDecodingContext *ctx,
358 | RepOriginId origin_id)
359 | {
360 | Wal2MongoData *data = ctx->output_plugin_private;
361 |
362 | if (data->only_local && origin_id != InvalidRepOriginId)
363 | return true;
364 | return false;
365 | }
366 |
367 | /* Postgres data types to MongoDB data types conversion
368 | * if input data has already been quoted, then use false to skip quotation
369 | * if input data hasn't been quoted yet, then use true to request a quotation
370 | */
371 | static void
372 | print_w2m_data_type(StringInfo s, char *outputstr, const char *type, bool quotation)
373 | {
374 | const char *valptr;
375 |
376 | for (valptr = outputstr; *valptr; valptr++)
377 | {
378 | char ch = *valptr;
379 | if(ch == '{')
380 | {
381 | appendStringInfo(s, "[%s(", type);
382 | if (quotation)
383 | appendStringInfo(s, "\"");
384 | }
385 | else if(ch == ',')
386 | {
387 | if (quotation)
388 | appendStringInfo(s, "\"");
389 | appendStringInfo(s, "),%s(", type);
390 | if (quotation)
391 | appendStringInfo(s, "\"");
392 | }
393 | else if(ch == '}')
394 | {
395 | if (quotation)
396 | appendStringInfo(s, "\"");
397 | appendStringInfo(s, ")]");
398 | }
399 | else
400 | appendStringInfoChar(s, ch);
401 | }
402 | }
403 |
404 | /* PG to MG data conversion */
405 | static void
406 | print_w2m_literal(StringInfo s, Oid typid, char *outputstr)
407 | {
408 | const char *valptr;
409 |
410 | switch (typid)
411 | {
412 | case INT2OID:
413 | case INT4OID:
414 | appendStringInfo(s, " NumberInt(\"%s\")", outputstr);
415 | break;
416 |
417 | case FLOAT4OID:
418 | appendStringInfoString(s, outputstr);
419 | break;
420 |
421 | case INT8OID:
422 | case OIDOID:
423 | appendStringInfo(s, " NumberLong(\"%s\")", outputstr);
424 | break;
425 |
426 | case FLOAT8OID:
427 | case NUMERICOID:
428 | appendStringInfo(s, " NumberDecimal(\"%s\")", outputstr);
429 | break;
430 |
431 | case BITOID:
432 | case VARBITOID:
433 | appendStringInfo(s, "B'%s'", outputstr);
434 | break;
435 |
436 | case BOOLOID:
437 | if (strcmp(outputstr, "t") == 0)
438 | appendStringInfoString(s, "true");
439 | else
440 | appendStringInfoString(s, "false");
441 | break;
442 |
443 | case TIMESTAMPTZOID:
444 | appendStringInfo(s, " ISODate(\"%s\")", outputstr);
445 | break;
446 |
447 | case UUIDOID:
448 | appendStringInfo(s, " UUID(\"%s\")", outputstr);
449 | break;
450 |
451 | case BYTEAOID:
452 | appendStringInfo(s, " HexData(0, \"");
453 | for (valptr = outputstr+2; *valptr; valptr++)
454 | {
455 | char ch = *valptr;
456 | appendStringInfoChar(s, ch);
457 | }
458 | appendStringInfo(s, "\")");
459 | break;
460 |
461 | /* Array data type */
462 | case BOOLARRAYOID:
463 | for (valptr = outputstr; *valptr; valptr++)
464 | {
465 | char ch = *valptr;
466 | if(ch == '{')
467 | appendStringInfoChar(s, '[');
468 | else if(ch == ',')
469 | appendStringInfoChar(s, ',');
470 | else if(ch == '}')
471 | appendStringInfoChar(s, ']');
472 | else
473 | {
474 | if (ch == 't')
475 | appendStringInfoString(s, "true");
476 | else
477 | appendStringInfoString(s, "false");
478 | }
479 | }
480 | break;
481 |
482 | case INT2ARRAYOID:
483 | case INT4ARRAYOID:
484 | print_w2m_data_type(s, outputstr, "NumberInt", false);
485 | break;
486 |
487 | case FLOAT4ARRAYOID:
488 | for (valptr = outputstr; *valptr; valptr++)
489 | {
490 | char ch = *valptr;
491 | if(ch == '{')
492 | appendStringInfoChar(s, '[');
493 | else if(ch == ',')
494 | appendStringInfoChar(s, ',');
495 | else if(ch == '}')
496 | appendStringInfoChar(s, ']');
497 | else
498 | {
499 | if (SQL_STR_DOUBLE(ch, false))
500 | appendStringInfoChar(s, ch);
501 | appendStringInfoChar(s, ch);
502 | }
503 | }
504 | break;
505 |
506 | case INT8ARRAYOID:
507 | print_w2m_data_type(s, outputstr, "NumberLong", false);
508 | break;
509 |
510 | case TIMESTAMPTZARRAYOID:
511 | print_w2m_data_type(s, outputstr, "ISODate", false);
512 | break;
513 |
514 | case FLOAT8ARRAYOID:
515 | case NUMERICARRAYOID:
516 | print_w2m_data_type(s, outputstr, "NumberDecimal", true);
517 | break;
518 |
519 | case UUIDARRAYOID:
520 | print_w2m_data_type(s, outputstr, "UUID", true);
521 | break;
522 |
523 | case CHARARRAYOID:
524 | case NAMEARRAYOID:
525 | case TEXTARRAYOID:
526 | case BPCHARARRAYOID:
527 | case VARCHARARRAYOID:
528 | for (valptr = outputstr; *valptr; valptr++)
529 | {
530 | char ch = *valptr;
531 | if(ch == '{')
532 | if(*(valptr+1) == '{' || *(valptr+1) == '"')
533 | appendStringInfoChar(s, '[');
534 | else
535 | appendStringInfo(s, "[\"");
536 | else if(ch == ',')
537 | if(*(valptr+1) == '{' || *(valptr+1) == '"')
538 | appendStringInfoChar(s, ',');
539 | else
540 | appendStringInfo(s, "\",\"");
541 | else if(ch == '}')
542 | if(*(valptr-1) != '}' && *(valptr-1) != '"')
543 | appendStringInfo(s, "\"]");
544 | else
545 | appendStringInfoChar(s, ']');
546 | else
547 | {
548 | if (SQL_STR_DOUBLE(ch, false))
549 | appendStringInfoChar(s, ch);
550 | appendStringInfoChar(s, ch);
551 | }
552 | }
553 | break;
554 | /* Array data type */
555 | case JSONOID:
556 | case JSONBOID:
557 | appendStringInfo(s, "%s", outputstr);
558 | break;
559 | case JSONPATHOID:
560 | appendStringInfoChar(s, '\"');
561 | for (valptr = outputstr; *valptr; valptr++)
562 | {
563 | char ch = *valptr;
564 |
565 | if(ch != '"')
566 | appendStringInfoChar(s, ch);
567 | }
568 | appendStringInfoChar(s, '\"');
569 | break;
570 | case JSONARRAYOID:
571 | case JSONBARRAYOID:
572 | /* For JSON arrays, we need to strip first and last curly brackets and
573 | * replace them with square brackets in order to be accepted by Mongodb
574 | */
575 | outputstr = outputstr + 1;
576 | outputstr[strlen(outputstr) - 1] = '\0';
577 | appendStringInfoChar(s, '[');
578 | for (valptr = outputstr; *valptr; valptr++)
579 | {
580 | char ch = *valptr;
581 | if(ch == '\\' && (valptr+1) != NULL && *(valptr+1) == '"' )
582 | {
583 | /* replace all \" with " */
584 | appendStringInfoChar(s, '"');
585 | }
586 | else if(ch == '"')
587 | {
588 | /* ignore all the double quote without backslash escape */
589 | }
590 | else
591 | {
592 | appendStringInfoChar(s, ch);
593 | }
594 | }
595 | appendStringInfoChar(s, ']');
596 | break;
597 |
598 | case XMLOID:
599 | appendStringInfoChar(s, '\"');
600 | for (valptr = outputstr; *valptr; valptr++)
601 | {
602 | char ch = *valptr;
603 |
604 | if (ch == '\n')
605 | continue;
606 | if ((ch == '"') && (*(valptr+1) != '\0'))
607 | appendStringInfoChar(s, '\\');
608 |
609 | if (SQL_STR_DOUBLE(ch, false))
610 | appendStringInfoChar(s, ch);
611 | appendStringInfoChar(s, ch);
612 | }
613 | appendStringInfoChar(s, '\"');
614 | break;
615 |
616 | default:
617 | appendStringInfoChar(s, '\"');
618 | for (valptr = outputstr; *valptr; valptr++)
619 | {
620 | char ch = *valptr;
621 |
622 | if (SQL_STR_DOUBLE(ch, false))
623 | appendStringInfoChar(s, ch);
624 | appendStringInfoChar(s, ch);
625 | }
626 | appendStringInfoChar(s, '\"');
627 | break;
628 | }
629 | }
630 |
631 | /* print the tuple 'tuple' into the StringInfo s */
632 | static void
633 | tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_nulls,
634 | Bitmapset * pkAttrs)
635 | {
636 | int natt;
637 |
638 | appendStringInfoString(s, " \{ ");
639 | /* print all columns individually */
640 | for (natt = 0; natt < tupdesc->natts; natt++)
641 | {
642 | Form_pg_attribute attr; /* the attribute itself */
643 | Oid typid; /* type of current attribute */
644 | Oid typoutput; /* output function */
645 | bool typisvarlena;
646 | Datum origval; /* possibly toasted Datum */
647 | bool isnull; /* column is null? */
648 |
649 | attr = TupleDescAttr(tupdesc, natt);
650 |
651 | /*
652 | * don't print dropped columns, we can't be sure everything is
653 | * available for them
654 | */
655 | if (attr->attisdropped)
656 | continue;
657 |
658 | /*
659 | * Don't print system columns, oid will already have been printed if
660 | * present.
661 | */
662 | if (attr->attnum < 0)
663 | continue;
664 |
665 | typid = attr->atttypid;
666 |
667 | /*
668 | * if pkAttrs is valid and not empty. Check if the current attribute is
669 | * a member of the pkAttrs. If not a member, continue to next attribute.
670 | */
671 | if(pkAttrs && !bms_is_empty(pkAttrs) &&
672 | !bms_is_member(natt + 1 - FirstLowInvalidHeapAttributeNumber,
673 | pkAttrs))
674 | continue;
675 |
676 |
677 | /* get Datum from tuple */
678 | origval = heap_getattr(tuple, natt + 1, tupdesc, &isnull);
679 |
680 | if (isnull && skip_nulls)
681 | continue;
682 |
683 | /* print attribute name */
684 | if ( natt != 0 )
685 | appendStringInfoString(s, ", ");
686 |
687 | appendStringInfoString(s, quote_identifier(NameStr(attr->attname)));
688 |
689 | /* print separator */
690 | appendStringInfoChar(s, ':');
691 |
692 | /* query output function */
693 | getTypeOutputInfo(typid,
694 | &typoutput, &typisvarlena);
695 |
696 | /* print data */
697 | if (isnull)
698 | appendStringInfoString(s, "null");
699 | else if (typisvarlena && VARATT_IS_EXTERNAL_ONDISK(origval))
700 | appendStringInfoString(s, "unchanged-toast-datum");
701 | else if (!typisvarlena)
702 | {
703 | if (typid == TIMESTAMPTZOID || typid == TIMESTAMPTZARRAYOID)
704 | {
705 | char buf[MAXDATELEN + 1];
706 | JsonEncodeDateTime(buf, origval, TIMESTAMPTZOID, NULL);
707 | print_w2m_literal(s, typid, buf);
708 | }
709 | else
710 | print_w2m_literal(s, typid,
711 | OidOutputFunctionCall(typoutput, origval));
712 | }
713 | else
714 | {
715 | Datum val; /* definitely detoasted Datum */
716 | char *result;
717 | char *rp;
718 |
719 | val = PointerGetDatum(PG_DETOAST_DATUM(origval));
720 | if (typid == BYTEAOID )
721 | {
722 | /* Print hex format */
723 | rp = result = palloc(VARSIZE_ANY_EXHDR(val) * 2 + 2 + 1);
724 | *rp++ = '\\';
725 | *rp++ = 'x';
726 | rp += hex_encode(VARDATA_ANY(val), VARSIZE_ANY_EXHDR(val), rp);
727 | *rp = '\0';
728 | print_w2m_literal(s, typid, result);
729 | }
730 | else
731 | print_w2m_literal(s, typid, OidOutputFunctionCall(typoutput, val));
732 | }
733 |
734 | }
735 | appendStringInfoString(s, " }");
736 | }
737 |
738 | /*
739 | * callback for individual changed tuples
740 | */
741 | static void
742 | pg_w2m_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
743 | Relation relation, ReorderBufferChange *change)
744 | {
745 | Wal2MongoData *data;
746 | Form_pg_class class_form;
747 | TupleDesc tupdesc;
748 | MemoryContext old;
749 | Bitmapset *pkAttrs;
750 |
751 | data = ctx->output_plugin_private;
752 |
753 | /* output BEGIN if we haven't yet */
754 | if (data->skip_empty_xacts && !data->xact_wrote_changes)
755 | {
756 | pg_w2m_decode_begin(ctx, data, txn);
757 | }
758 | data->xact_wrote_changes = true;
759 |
760 | class_form = RelationGetForm(relation);
761 | tupdesc = RelationGetDescr(relation);
762 |
763 | /* Avoid leaking memory by using and resetting our own context */
764 | old = MemoryContextSwitchTo(data->context);
765 |
766 | /* write the db switch command */
767 | OutputPluginPrepareWrite(ctx, false);
768 |
769 | /* Here we are concatenating ClusterName, DatabaseName, and SlotName to form
770 | * a unified database name in MongoDB's perspective, so Mongo knows the changes
771 | * are streamed from which cluster, which database and via which slot
772 | *
773 | * TODO: we will introduce more configurable options to fine-tune this output style
774 | * behaviors.
775 | */
776 | if(data->include_cluster_name)
777 | {
778 | char *cluster_name = GetConfigOptionByName("cluster_name", NULL, true);
779 |
780 | appendStringInfo(ctx->out, "use %s_%s_%s;",
781 | data->regress == true ? "mycluster" : (cluster_name[0] == '\0' ? "mycluster" : cluster_name),
782 | data->regress == true ? "mydb" : get_database_name(MyDatabaseId),
783 | ctx->slot->data.name.data[0] == '\0' ? "myslot" :
784 | ctx->slot->data.name.data);
785 | }
786 | else
787 | {
788 | appendStringInfo(ctx->out, "use %s_%s;",
789 | data->regress == true ? "mydb" : get_database_name(MyDatabaseId),
790 | ctx->slot->data.name.data[0] == '\0' ? "myslot" :
791 | ctx->slot->data.name.data);
792 | }
793 | OutputPluginWrite(ctx, false);
794 |
795 | OutputPluginPrepareWrite(ctx, true);
796 | appendStringInfoString(ctx->out,
797 | quote_qualified_identifier("db", class_form->relrewrite ?
798 | get_rel_name(class_form->relrewrite) :
799 | NameStr(class_form->relname)));
800 |
801 | switch (change->action)
802 | {
803 | case REORDER_BUFFER_CHANGE_INSERT:
804 | appendStringInfoString(ctx->out, ".insertOne(");
805 | if (change->data.tp.newtuple == NULL)
806 | appendStringInfoString(ctx->out, " (no-tuple-data)");
807 | else
808 | tuple_to_stringinfo(ctx->out, tupdesc,
809 | &change->data.tp.newtuple->tuple,
810 | true, NULL);
811 | appendStringInfoString(ctx->out, " );");
812 | break;
813 | case REORDER_BUFFER_CHANGE_UPDATE:
814 | appendStringInfoString(ctx->out, ".updateOne(");
815 | if (change->data.tp.oldtuple != NULL)
816 | {
817 | /*
818 | * the old tuple will contain the old value of primary key if it has been changed under DEFAULT replica identity
819 | * the old tuple will contain all old values regardless if they have bee changed under FULL replica identity
820 | * either way, find the primary key columns and print them only.
821 | */
822 | pkAttrs = RelationGetIndexAttrBitmap(relation,
823 | INDEX_ATTR_BITMAP_PRIMARY_KEY);
824 | tuple_to_stringinfo(ctx->out, tupdesc,
825 | &change->data.tp.oldtuple->tuple,
826 | true, pkAttrs);
827 | bms_free(pkAttrs);
828 | }
829 | else
830 | {
831 | /*
832 | * the old tuple is NULL case. This means primary key has not been changed and the replica identity is not set to FULL.
833 | * we need to figure out the primary key column from new tuple
834 | */
835 | if (change->data.tp.newtuple != NULL)
836 | {
837 | pkAttrs = RelationGetIndexAttrBitmap(relation,
838 | INDEX_ATTR_BITMAP_PRIMARY_KEY);
839 | if (!pkAttrs)
840 | {
841 | /* old tuple is NULL and no primary key is present in newtuple: Write null */
842 | appendStringInfoString(ctx->out, "{ selector: \"null\" }");
843 | }
844 | else
845 | {
846 | tuple_to_stringinfo(ctx->out, tupdesc,
847 | &change->data.tp.newtuple->tuple,
848 | true, pkAttrs);
849 | bms_free(pkAttrs);
850 | }
851 | }
852 | }
853 |
854 | if (change->data.tp.newtuple != NULL)
855 | {
856 | appendStringInfoString(ctx->out, ", \{ $set: ");
857 | tuple_to_stringinfo(ctx->out, tupdesc,
858 | &change->data.tp.newtuple->tuple,
859 | false, NULL);
860 | appendStringInfoString(ctx->out, " }");
861 | }
862 | appendStringInfoString(ctx->out, " );");
863 | break;
864 | case REORDER_BUFFER_CHANGE_DELETE:
865 | appendStringInfoString(ctx->out, ".deleteOne(");
866 | /* if there was no PK, we only know that a delete happened */
867 | if (change->data.tp.oldtuple == NULL)
868 | appendStringInfoString(ctx->out, " (no-tuple-data)");
869 | /* In DELETE, only the replica identity is present; display that */
870 | else
871 | {
872 | /* For delete, we looked up the primary key attribute bitmap for use
873 | * in the subsequent tuple_to_stringinfo() call so the output will only
874 | * contain primary key columns in the oldtuple instead of all columns.
875 | */
876 | pkAttrs = RelationGetIndexAttrBitmap(relation,
877 | INDEX_ATTR_BITMAP_PRIMARY_KEY);
878 | tuple_to_stringinfo(ctx->out, tupdesc,
879 | &change->data.tp.oldtuple->tuple,
880 | true, pkAttrs);
881 | bms_free(pkAttrs);
882 | }
883 | appendStringInfoString(ctx->out, " );");
884 | break;
885 | default:
886 | Assert(false);
887 | }
888 |
889 | MemoryContextSwitchTo(old);
890 | MemoryContextReset(data->context);
891 |
892 | OutputPluginWrite(ctx, true);
893 | }
894 |
895 | static void
896 | pg_w2m_decode_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
897 | int nrelations, Relation relations[], ReorderBufferChange *change)
898 | {
899 | /* TODO: to be supported in future version */
900 | elog(DEBUG1, "TRUNCATE replication is not supported\n");
901 | }
902 |
903 | static void
904 | pg_w2m_decode_message(LogicalDecodingContext *ctx,
905 | ReorderBufferTXN *txn, XLogRecPtr lsn, bool transactional,
906 | const char *prefix, Size sz, const char *message)
907 | {
908 | /* message decoding not supported */
909 | elog(DEBUG1, "message decoding is not supported\n");
910 | }
911 |
912 | static bool
913 | split_string_to_list(char *rawstring, char separator, List **sl)
914 | {
915 | char *nextp;
916 | bool done = false;
917 |
918 | nextp = rawstring;
919 |
920 | while (isspace(*nextp))
921 | nextp++; /* skip leading whitespace */
922 |
923 | if (*nextp == '\0')
924 | return true; /* allow empty string */
925 |
926 | /* At the top of the loop, we are at start of a new identifier. */
927 | do
928 | {
929 | char *curname;
930 | char *endp;
931 | char *pname;
932 |
933 | curname = nextp;
934 | while (*nextp && *nextp != separator && !isspace(*nextp))
935 | {
936 | if (*nextp == '\\')
937 | nextp++; /* ignore next character because of escape */
938 | nextp++;
939 | }
940 | endp = nextp;
941 | if (curname == nextp)
942 | return false; /* empty unquoted name not allowed */
943 |
944 | while (isspace(*nextp))
945 | nextp++; /* skip trailing whitespace */
946 |
947 | if (*nextp == separator)
948 | {
949 | nextp++;
950 | while (isspace(*nextp))
951 | nextp++; /* skip leading whitespace for next */
952 | /* we expect another name, so done remains false */
953 | }
954 | else if (*nextp == '\0')
955 | done = true;
956 | else
957 | return false; /* invalid syntax */
958 |
959 | /* Now safe to overwrite separator with a null */
960 | *endp = '\0';
961 |
962 | /*
963 | * Finished isolating current name --- add it to list
964 | */
965 | pname = pstrdup(curname);
966 | *sl = lappend(*sl, pname);
967 |
968 | /* Loop back if we didn't reach end of string */
969 | } while (!done);
970 |
971 | return true;
972 | }
973 |
--------------------------------------------------------------------------------