├── .dockerignore
├── Dockerfile
├── LICENSE
├── README.md
├── ali_decoding
├── Makefile
├── ali_decoding--0.0.1.sql
├── ali_decoding--unpackaged--0.0.1.sql
├── ali_decoding.c
├── ali_decoding.control
└── test
│ └── decode_test.sql
├── dbsync
├── Makefile
├── dbsync-mysql2pgsql.c
├── dbsync-pgsql2pgsql.c
├── demo.cpp
├── ini.c
├── ini.h
├── misc.c
├── misc.h
├── my.cfg
├── mysql2pgsql.c
├── pg_logicaldecode.c
├── pg_logicaldecode.h
├── pgsync.c
├── pgsync.h
├── pqformat.c
├── readcfg.cpp
├── readcfg.h
├── stringinfo.c
├── test
│ └── decode_test.sql
├── utils.c
└── utils.h
└── doc
├── design.md
├── mysql2gp.md
├── mysql2pgsql_ch.md
├── mysql2pgsql_en.md
├── pgsql2pgsql_ch.md
└── pgsql2pgsql_en.md
/.dockerignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dockerignore
3 | .gitlab-ci.yml
4 | .gitignore
5 | .git/
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM centos:7
2 |
3 | ENV PATH=$PATH:/dbsync/bin
4 |
5 | COPY . /tmp/aliyun/
6 |
7 | RUN set -ex \
8 | && { \
9 | echo '[mysql57-community]'; \
10 | echo 'name=mysql57-community'; \
11 | echo 'baseurl=http://repo.mysql.com/yum/mysql-5.7-community/el/$releasever/$basearch/'; \
12 | echo 'enabled=1'; \
13 | echo 'gpgcheck=0'; \
14 | echo '[pgdg10]'; \
15 | echo 'name=pgdg10'; \
16 | echo 'baseurl=https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-$releasever-$basearch'; \
17 | echo 'enabled=1'; \
18 | echo 'gpgcheck=0'; \
19 | } > /etc/yum.repos.d/dbsync_deps.repo \
20 | && cp -ra /var/log/yum.log /tmp/yum.log.old \
21 | && yum install mysql-community-client mysql-community-devel postgresql10-devel gcc gcc-c++ make unzip -y \
22 | && update-alternatives --install /usr/bin/pg_config pgsql-pg_config /usr/pgsql-10/bin/pg_config 300 \
23 | && ( \
24 | cd /tmp/aliyun/dbsync \
25 | && make \
26 | && install -D -d /dbsync/bin /dbsync/lib \
27 | && install -p -D -m 0755 *2pgsql /dbsync/bin \
28 | && install -p -D -m 0755 ali_recvlogical.so /dbsync/lib \
29 | && install -p -D -m 0644 my.cfg ../LICENSE ../README.md /dbsync \
30 | && ln -sf /usr/share/mysql /dbsync/share \
31 | ) \
32 | && update-alternatives --remove pgsql-pg_config /usr/pgsql-10/bin/pg_config \
33 | && mkdir -p /tmp/extbin \
34 | && curl -L https://github.com/aliyun/rds_dbsync/files/1555186/mysql2pgsql.bin.el7.20171213.zip -o /tmp/extbin/bin.zip \
35 | && (cd /tmp/extbin && unzip -o bin.zip && install -p -D -m 0755 mysql2pgsql.bin*/bin/binlog_* /dbsync/bin) \
36 | && yum remove -y mysql-community-devel postgresql10-devel unzip gcc gcc-c++ make cpp glibc-devel glibc-headers libicu-devel libstdc++-devel kernel-headers \
37 | && yum clean all && mv /tmp/yum.log.old /var/log/yum.log \
38 | && rm -rf /tmp/aliyun /tmp/extbin /var/cache/yum/* /etc/yum.repos.d/dbsync_deps.repo \
39 | && ls -alhR /dbsync && ldd /dbsync/bin/* && mysql --version && psql --version && mysql2pgsql -h
40 |
41 | WORKDIR /dbsync
42 |
43 | CMD ["mysql2pgsql", "-h"]
44 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dbsync 项目
2 |
3 | dbsync 项目目标是围绕 PostgreSQL Greenplum ,实现易用的数据的互迁功能。
4 |
5 | ## 支持的功能
6 |
7 | 1. PostgreSQL -> PostgreSQL pgsql2pgsql
8 |
9 | 功能 pg->pg 全量+增量数据同步
10 |
11 | 状态:已开源 [文档](doc/pgsql2pgsql_ch.md)
12 |
13 | 2. MySQL -> PostgreSQL/Greenplum(binlog_minner binlog_loader)
14 |
15 | 功能:基于 MySQL binlog 解析的增量数据同步
16 |
17 | 状态:已开放二进制 [文档](doc/mysql2gp.md)
18 |
19 | 3. PostgreSQL -> PostgreSQL/Greenplum pgsql2gp
20 |
21 | 功能:基于 PostgreSQL 逻辑日志的增量数据同步
22 |
23 | 状态:未开发完成
24 |
25 | 4. MySQL -> PostgreSQL/Greenplum mysql2pgsql
26 |
27 | 功能:以表为单位的多线程全量数据迁移
28 |
29 | 状态:已开源 [文档](doc/mysql2pgsql_ch.md)
30 |
31 |
32 | ## 项目成员
33 | 该项目由阿里云 PostgreSQL 小组开发,为 PostgreSQL 世界贡献一份力量
34 |
35 | 1. PM & 架构设计 曾文旌(义从)
36 | 2. PD 萧少聪(铁庵)
37 | 3. TESTER & 技术支持 周正中(德歌)
38 | 4. DEV 张广舟(明虚)曾文旌(义从)
39 |
40 | ## 使用方法
41 | 1. 修改配置文件 my.cfg 中相关的项,例如需求 MySQL -> PostgreSQL 全量迁移,不需要增量,则只需要配置 src.mysql 和 desc.pgsql ,其他的项不用管。
42 | 2. 执行对应二进制,在二进制所在目录执行 ./mysql2pgsql
43 |
44 | ## 编译步骤
45 |
46 | ### 从零开始
47 |
48 | 1. 下载代码
49 |
50 | `git clone https://github.com/aliyun/rds_dbsync.git`
51 |
52 | 2. 下载安装mysql的开发包
53 |
54 | 下载repo的rpm: `wget http://dev.mysql.com/get/mysql57-community-release-el6-9.noarch.rpm`
55 |
56 | 安装repo:rpm -Uvh mysql57-community-release-el6-9.noarch.rpm
57 |
58 | 编辑 /etc/yum.repos.d/mysql-community.repo,把除mysql 57外的其他repo的enable设为0
59 |
60 | 查看可安装的mysql报:yum list mysql-community-*
61 |
62 | 安装mysql的开发包: yum install mysql-community-devel.x86_64
63 |
64 | 3. 下载安装pg的安装包
65 |
66 | 下载repo的rpm: `wget https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-6-x86_64/pgdg-centos96-9.6-3.noarch.rpm`
67 |
68 | 安装repo:rpm -ivh pgdg-centos96-9.6-3.noarch.rpm
69 |
70 | 编辑/etc/yum.repos.d/pgdg-96-centos.repo,可能需要把https改成http
71 |
72 | 查看可安装的pg包:yum list postgresql96*
73 |
74 | 安装pg的server和开发包:yum install postgresql96-devel.x86_64 postgresql96-server.x86_64
75 |
76 | 4. 执行make
77 |
78 | 5. 打包二进制 make package 将生成一个install目录,里面有二进制和lib
79 |
80 | 6. 执行dbsync:cd install; bin/mysql2pgsql ; bin/pgsql2pgsql ; bin/demo
81 |
82 | ### 打包docker镜像
83 | 以上手动步骤,已集成进 [Dockerfile](Dockerfile),运行 `docker build .` 无意外即完成编译过程,镜像内同时包含 `binlog_minner` `binlog_loader` 两个二进制文件。
84 |
85 | ## 问题反馈
86 | 有任何问题,请反馈到 https://github.com/aliyun/rds_dbsync issues 或联系 158306855@qq.com
87 |
--------------------------------------------------------------------------------
/ali_decoding/Makefile:
--------------------------------------------------------------------------------
1 | # contrib/ali_decoding/Makefile
2 | MODULE_big = ali_decoding
3 | MODULES = ali_decoding
4 | OBJS = ali_decoding.o
5 |
6 | DATA = ali_decoding--0.0.1.sql ali_decoding--unpackaged--0.0.1.sql
7 |
8 | EXTENSION = ali_decoding
9 |
10 | NAME = ali_decoding
11 |
12 | #subdir = contrib/ali_decoding
13 | #top_builddir = ../..
14 | #include $(top_builddir)/src/Makefile.global
15 | #include $(top_srcdir)/contrib/contrib-global.mk
16 |
17 | PG_CONFIG = pg_config
18 | pgsql_lib_dir := $(shell $(PG_CONFIG) --libdir)
19 | PGXS := $(shell $(PG_CONFIG) --pgxs)
20 | include $(PGXS)
21 |
22 |
--------------------------------------------------------------------------------
/ali_decoding/ali_decoding--0.0.1.sql:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliyun/rds_dbsync/edc3f92e71830e9d41b75c721a2ef7729835afcf/ali_decoding/ali_decoding--0.0.1.sql
--------------------------------------------------------------------------------
/ali_decoding/ali_decoding--unpackaged--0.0.1.sql:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aliyun/rds_dbsync/edc3f92e71830e9d41b75c721a2ef7729835afcf/ali_decoding/ali_decoding--unpackaged--0.0.1.sql
--------------------------------------------------------------------------------
/ali_decoding/ali_decoding.c:
--------------------------------------------------------------------------------
1 | /*-------------------------------------------------------------------------
2 | *
3 | * ali_decoding.c
4 | * ali logical decoding output plugin
5 | *
6 | *
7 | *
8 | * IDENTIFICATION
9 | * contrib/ali_decoding/ali_decoding.c
10 | *
11 | *-------------------------------------------------------------------------
12 | */
13 | #include "postgres.h"
14 |
15 | #include "access/sysattr.h"
16 |
17 | #include "catalog/pg_class.h"
18 | #include "catalog/pg_type.h"
19 |
20 | #include "nodes/parsenodes.h"
21 |
22 | #include "replication/output_plugin.h"
23 | #include "replication/logical.h"
24 |
25 | #include "utils/builtins.h"
26 | #include "utils/lsyscache.h"
27 | #include "utils/memutils.h"
28 | #include "utils/rel.h"
29 | #include "utils/relcache.h"
30 | #include "utils/syscache.h"
31 | #include "utils/typcache.h"
32 |
33 | #include "libpq/pqformat.h"
34 | #include "access/tuptoaster.h"
35 | #include "mb/pg_wchar.h"
36 | #include "utils/guc.h"
37 |
38 |
39 | PG_MODULE_MAGIC;
40 |
41 | /* These must be available to pg_dlsym() */
42 | extern void _PG_init(void);
43 | extern void _PG_output_plugin_init(OutputPluginCallbacks *cb);
44 |
45 | typedef struct
46 | {
47 | MemoryContext context;
48 |
49 | bool allow_binary_protocol;
50 | bool allow_sendrecv_protocol;
51 | bool int_datetime_mismatch;
52 |
53 | uint32 client_version;
54 |
55 | size_t client_sizeof_int;
56 | size_t client_sizeof_long;
57 | size_t client_sizeof_datum;
58 | size_t client_maxalign;
59 | bool client_bigendian;
60 | bool client_float4_byval;
61 | bool client_float8_byval;
62 | bool client_int_datetime;
63 |
64 | char *client_encoding;
65 |
66 | bool output_key_info;
67 | bool output_column_info;
68 | bool output_type_as_name;
69 | } Ali_OutputData;
70 |
71 | static void pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
72 | bool is_init);
73 | static void pg_decode_shutdown(LogicalDecodingContext *ctx);
74 | static void pg_decode_begin_txn(LogicalDecodingContext *ctx,
75 | ReorderBufferTXN *txn);
76 | static void pg_decode_commit_txn(LogicalDecodingContext *ctx,
77 | ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
78 | static void pg_decode_change(LogicalDecodingContext *ctx,
79 | ReorderBufferTXN *txn, Relation rel,
80 | ReorderBufferChange *change);
81 | static void write_rel(StringInfo out, Relation rel, Ali_OutputData *data, int action);
82 | static void write_tuple(Ali_OutputData *data, StringInfo out, Relation rel,
83 | HeapTuple tuple);
84 | static void write_colum_info(StringInfo out, Relation rel, Ali_OutputData *data, int action);
85 | static void parse_notnull(DefElem *elem, const char *paramtype);
86 | static void parse_uint32(DefElem *elem, uint32 *res);
87 |
88 | void
89 | _PG_init(void)
90 | {
91 | /* other plugins can perform things here */
92 | }
93 |
94 | /* specify output plugin callbacks */
95 | void
96 | _PG_output_plugin_init(OutputPluginCallbacks *cb)
97 | {
98 | AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
99 |
100 | cb->startup_cb = pg_decode_startup;
101 | cb->begin_cb = pg_decode_begin_txn;
102 | cb->change_cb = pg_decode_change;
103 | cb->commit_cb = pg_decode_commit_txn;
104 | cb->shutdown_cb = pg_decode_shutdown;
105 | }
106 |
107 |
108 | /* initialize this plugin */
109 | static void
110 | pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
111 | bool is_init)
112 | {
113 | ListCell *option;
114 | Ali_OutputData *data;
115 |
116 | data = palloc0(sizeof(Ali_OutputData));
117 | data->context = AllocSetContextCreate(ctx->context,
118 | "ali decode conversion context",
119 | ALLOCSET_DEFAULT_MINSIZE,
120 | ALLOCSET_DEFAULT_INITSIZE,
121 | ALLOCSET_DEFAULT_MAXSIZE);
122 |
123 | ctx->output_plugin_private = data;
124 |
125 | opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
126 |
127 | foreach(option, ctx->output_plugin_options)
128 | {
129 | DefElem *elem = lfirst(option);
130 |
131 | Assert(elem->arg == NULL || IsA(elem->arg, String));
132 |
133 | if (strcmp(elem->defname, "version") == 0)
134 | parse_uint32(elem, &data->client_version);
135 | else if (strcmp(elem->defname, "encoding") == 0)
136 | data->client_encoding = pstrdup(strVal(elem->arg));
137 | else
138 | {
139 | ereport(ERROR,
140 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
141 | errmsg("option \"%s\" = \"%s\" is unknown",
142 | elem->defname,
143 | elem->arg ? strVal(elem->arg) : "(null)")));
144 | }
145 | }
146 |
147 | if (!is_init)
148 | {
149 | /* Set defaults values */
150 | if(data->client_encoding == NULL)
151 | {
152 | data->client_encoding = pstrdup(GetDatabaseEncodingName());
153 | }
154 |
155 | /* fix me */
156 | data->allow_binary_protocol = false;
157 | data->allow_sendrecv_protocol = false;
158 | data->int_datetime_mismatch = false;
159 |
160 | data->output_key_info = true;
161 | data->output_column_info = true;
162 | data->output_type_as_name = true;
163 |
164 | if (strcmp(data->client_encoding, GetDatabaseEncodingName()) != 0)
165 | elog(ERROR, "mismatching encodings are not yet supported");
166 |
167 | if (extra_float_digits < 3)
168 | (void) set_config_option("extra_float_digits", "3",
169 | PGC_USERSET, PGC_S_SESSION,
170 | GUC_ACTION_SAVE, true, 0, false);
171 | }
172 |
173 | return;
174 | }
175 |
176 | /* cleanup this plugin's resources */
177 | static void
178 | pg_decode_shutdown(LogicalDecodingContext *ctx)
179 | {
180 | Ali_OutputData *data = ctx->output_plugin_private;
181 |
182 | if(data->client_encoding != NULL)
183 | {
184 | pfree(data->client_encoding);
185 | data->client_encoding = NULL;
186 | }
187 |
188 | /* cleanup our own resources via memory context reset */
189 | MemoryContextDelete(data->context);
190 |
191 | pfree(ctx->output_plugin_private);
192 | ctx->output_plugin_private = NULL;
193 | }
194 |
195 | /*
196 | * BEGIN callback
197 | */
198 | static void
199 | pg_decode_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
200 | {
201 | int flags = 0;
202 | MemoryContext old;
203 | Ali_OutputData *data;
204 |
205 | data = ctx->output_plugin_private;
206 | old = MemoryContextSwitchTo(data->context);
207 |
208 | AssertVariableIsOfType(&pg_decode_begin_txn, LogicalDecodeBeginCB);
209 |
210 | OutputPluginPrepareWrite(ctx, true);
211 | pq_sendbyte(ctx->out, 'B'); /* BEGIN */
212 |
213 | /* send the flags field its self */
214 | pq_sendint(ctx->out, flags, 4);
215 |
216 | /* fixed fields */
217 | pq_sendint64(ctx->out, txn->final_lsn);
218 | pq_sendint64(ctx->out, txn->commit_time);
219 | pq_sendint(ctx->out, txn->xid, 4);
220 |
221 | OutputPluginWrite(ctx, true);
222 | MemoryContextSwitchTo(old);
223 |
224 | return;
225 | }
226 |
227 | /*
228 | * COMMIT callback
229 | *
230 | * Send the LSN at the time of the commit, the commit time, and the end LSN.
231 | *
232 | * The presence of additional records is controlled by a flag field, with
233 | * records that're present appearing strictly in the order they're listed
234 | * here. There is no sub-record header or other structure beyond the flags
235 | * field.
236 | *
237 | * If you change this, you'll need to change process_remote_commit(...)
238 | * too. Make sure to keep any flags in sync.
239 | */
240 | static void
241 | pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
242 | XLogRecPtr commit_lsn)
243 | {
244 | int flags = 0;
245 | MemoryContext old;
246 | Ali_OutputData *data;
247 |
248 | data = ctx->output_plugin_private;
249 | old = MemoryContextSwitchTo(data->context);
250 |
251 | OutputPluginPrepareWrite(ctx, true);
252 | pq_sendbyte(ctx->out, 'C'); /* sending COMMIT */
253 |
254 | /* send the flags field its self */
255 | pq_sendint(ctx->out, flags, 4);
256 |
257 | /* Send fixed fields */
258 | pq_sendint64(ctx->out, commit_lsn);
259 | pq_sendint64(ctx->out, txn->end_lsn);
260 | pq_sendint64(ctx->out, txn->commit_time);
261 |
262 | OutputPluginWrite(ctx, true);
263 | MemoryContextSwitchTo(old);
264 |
265 | return;
266 | }
267 |
268 | void
269 | pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
270 | Relation relation, ReorderBufferChange *change)
271 | {
272 | Ali_OutputData *data;
273 | MemoryContext old;
274 |
275 | data = ctx->output_plugin_private;
276 |
277 | /* Avoid leaking memory by using and resetting our own context */
278 | old = MemoryContextSwitchTo(data->context);
279 |
280 | OutputPluginPrepareWrite(ctx, true);
281 |
282 | switch (change->action)
283 | {
284 | case REORDER_BUFFER_CHANGE_INSERT:
285 | pq_sendbyte(ctx->out, 'I'); /* action INSERT */
286 | write_rel(ctx->out, relation, data, change->action);
287 | pq_sendbyte(ctx->out, 'N'); /* new tuple follows */
288 | write_tuple(data, ctx->out, relation, &change->data.tp.newtuple->tuple);
289 | break;
290 | case REORDER_BUFFER_CHANGE_UPDATE:
291 | pq_sendbyte(ctx->out, 'U'); /* action UPDATE */
292 | write_rel(ctx->out, relation, data, change->action);
293 | if (change->data.tp.oldtuple != NULL)
294 | {
295 | pq_sendbyte(ctx->out, 'K'); /* old key follows */
296 | write_tuple(data, ctx->out, relation,
297 | &change->data.tp.oldtuple->tuple);
298 | }
299 | pq_sendbyte(ctx->out, 'N'); /* new tuple follows */
300 | write_tuple(data, ctx->out, relation,
301 | &change->data.tp.newtuple->tuple);
302 | break;
303 | case REORDER_BUFFER_CHANGE_DELETE:
304 | pq_sendbyte(ctx->out, 'D'); /* action DELETE */
305 | write_rel(ctx->out, relation, data, change->action);
306 | if (change->data.tp.oldtuple != NULL)
307 | {
308 | pq_sendbyte(ctx->out, 'K'); /* old key follows */
309 | write_tuple(data, ctx->out, relation,
310 | &change->data.tp.oldtuple->tuple);
311 | }
312 | else
313 | pq_sendbyte(ctx->out, 'E'); /* empty */
314 | break;
315 | default:
316 | Assert(false);
317 | }
318 | OutputPluginWrite(ctx, true);
319 |
320 | MemoryContextSwitchTo(old);
321 | MemoryContextReset(data->context);
322 |
323 | }
324 |
325 | /*
326 | * Write schema.relation to the output stream.
327 | */
328 | static void
329 | write_rel(StringInfo out, Relation rel, Ali_OutputData *data, int action)
330 | {
331 | const char *nspname;
332 | int64 nspnamelen;
333 | const char *relname;
334 | int64 relnamelen;
335 |
336 | nspname = get_namespace_name(rel->rd_rel->relnamespace);
337 | if (nspname == NULL)
338 | elog(ERROR, "cache lookup failed for namespace %u",
339 | rel->rd_rel->relnamespace);
340 | nspnamelen = strlen(nspname) + 1;
341 |
342 | relname = NameStr(rel->rd_rel->relname);
343 | relnamelen = strlen(relname) + 1;
344 |
345 | pq_sendint(out, nspnamelen, 2); /* schema name length */
346 | appendBinaryStringInfo(out, nspname, nspnamelen);
347 |
348 | pq_sendint(out, relnamelen, 2); /* table name length */
349 | appendBinaryStringInfo(out, relname, relnamelen);
350 |
351 | if (data->output_column_info == true)
352 | {
353 | write_colum_info(out, rel, data, action);
354 | }
355 | }
356 |
357 | /*
358 | * Write a tuple to the outputstream, in the most efficient format possible.
359 | */
360 | static void
361 | write_tuple(Ali_OutputData *data, StringInfo out, Relation rel,
362 | HeapTuple tuple)
363 | {
364 | TupleDesc desc;
365 | Datum values[MaxTupleAttributeNumber];
366 | bool isnull[MaxTupleAttributeNumber];
367 | int i;
368 |
369 | desc = RelationGetDescr(rel);
370 |
371 | pq_sendbyte(out, 'T'); /* tuple follows */
372 |
373 | pq_sendint(out, desc->natts, 4); /* number of attributes */
374 |
375 | /* try to allocate enough memory from the get go */
376 | enlargeStringInfo(out, tuple->t_len +
377 | desc->natts * ( 1 + 4));
378 |
379 | /*
380 | * XXX: should this prove to be a relevant bottleneck, it might be
381 | * interesting to inline heap_deform_tuple() here, we don't actually need
382 | * the information in the form we get from it.
383 | */
384 | heap_deform_tuple(tuple, desc, values, isnull);
385 |
386 | for (i = 0; i < desc->natts; i++)
387 | {
388 | HeapTuple typtup;
389 | Form_pg_type typclass;
390 | char *outputstr = NULL;
391 | int len = 0;
392 |
393 | Form_pg_attribute att = desc->attrs[i];
394 |
395 | if (isnull[i] || att->attisdropped)
396 | {
397 | pq_sendbyte(out, 'n'); /* null column */
398 | continue;
399 | }
400 | else if (att->attlen == -1 && VARATT_IS_EXTERNAL_ONDISK(values[i]))
401 | {
402 | pq_sendbyte(out, 'u'); /* unchanged toast column */
403 | continue;
404 | }
405 |
406 | typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(att->atttypid));
407 | if (!HeapTupleIsValid(typtup))
408 | elog(ERROR, "cache lookup failed for type %u", att->atttypid);
409 | typclass = (Form_pg_type) GETSTRUCT(typtup);
410 |
411 | pq_sendbyte(out, 't'); /* 'text' data follows */
412 |
413 | outputstr =
414 | OidOutputFunctionCall(typclass->typoutput, values[i]);
415 | len = strlen(outputstr) + 1;
416 | pq_sendint(out, len, 4); /* length */
417 | appendBinaryStringInfo(out, outputstr, len); /* data */
418 | pfree(outputstr);
419 |
420 | ReleaseSysCache(typtup);
421 | }
422 | }
423 |
424 | static void
425 | write_colum_info(StringInfo out, Relation rel, Ali_OutputData *data, int action)
426 | {
427 | TupleDesc desc;
428 | int i;
429 |
430 | desc = RelationGetDescr(rel);
431 |
432 | pq_sendbyte(out, 'C'); /* tuple follows */
433 |
434 | pq_sendint(out, desc->natts, 2); /* number of attributes */
435 |
436 | for (i = 0; i < desc->natts; i++)
437 | {
438 | int attlen;
439 | const char *attname = NULL;
440 | int typelen;
441 | char *typname = NULL;
442 |
443 | Form_pg_attribute att = desc->attrs[i];
444 |
445 | if (att->attisdropped)
446 | {
447 | pq_sendint(out, 0, 2);
448 | continue;
449 | }
450 |
451 | if (att->attnum < 0)
452 | {
453 | pq_sendint(out, 0, 2);
454 | continue;
455 | }
456 |
457 | attname = quote_identifier(NameStr(att->attname));
458 | attlen = strlen(attname) + 1;
459 |
460 | typname = format_type_be(att->atttypid);
461 | typelen = strlen(typname) + 1;
462 |
463 | pq_sendint(out, attlen, 2);
464 | appendBinaryStringInfo(out, attname, attlen);
465 |
466 | if (data->output_type_as_name)
467 | {
468 | pq_sendint(out, typelen, 2);
469 | appendBinaryStringInfo(out, typname, typelen);
470 | }
471 |
472 | }
473 |
474 | if ((action == REORDER_BUFFER_CHANGE_UPDATE ||
475 | action == REORDER_BUFFER_CHANGE_DELETE) &&
476 | data->output_key_info == true)
477 | {
478 | Oid idxoid;
479 | Relation idxrel;
480 | TupleDesc idx_desc;
481 | int idxnatt;
482 | List *latt = NULL;
483 | ListCell *cell = NULL;
484 |
485 | if (rel->rd_indexvalid == 0)
486 | RelationGetIndexList(rel);
487 | idxoid = rel->rd_replidindex;
488 | if (!OidIsValid(idxoid))
489 | {
490 | pq_sendbyte(out, 'P');
491 | return;
492 | }
493 |
494 | idxrel = RelationIdGetRelation(idxoid);
495 | idx_desc = RelationGetDescr(idxrel);
496 | for (idxnatt = 0; idxnatt < idx_desc->natts; idxnatt++)
497 | {
498 | int attno = idxrel->rd_index->indkey.values[idxnatt];
499 | char *attname;
500 |
501 | if (attno < 0)
502 | {
503 | if (attno == ObjectIdAttributeNumber)
504 | continue;
505 | elog(ERROR, "system column in index");
506 | }
507 |
508 | attname = get_relid_attribute_name(RelationGetRelid(rel), attno);
509 | latt = lappend(latt, makeString(attname));
510 | }
511 |
512 | RelationClose(idxrel);
513 |
514 | pq_sendbyte(out, 'M');
515 | idxnatt = list_length(latt);
516 | pq_sendint(out, idxnatt, 2);
517 | foreach(cell, latt)
518 | {
519 | char *col = strVal(lfirst(cell));
520 | int len = strlen(col) + 1;
521 |
522 | pq_sendint(out, len, 2);
523 | appendBinaryStringInfo(out, col, len);
524 | }
525 |
526 | list_free_deep(latt);
527 | }
528 | else
529 | {
530 | pq_sendbyte(out, 'P');
531 | }
532 |
533 | return;
534 | }
535 |
536 | static void
537 | parse_uint32(DefElem *elem, uint32 *res)
538 | {
539 | parse_notnull(elem, "uint32");
540 | errno = 0;
541 | *res = strtoul(strVal(elem->arg), NULL, 0);
542 |
543 | if (errno != 0)
544 | ereport(ERROR,
545 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
546 | errmsg("could not parse uint32 value \"%s\" for parameter \"%s\": %m",
547 | strVal(elem->arg), elem->defname)));
548 | }
549 |
550 | static void
551 | parse_notnull(DefElem *elem, const char *paramtype)
552 | {
553 | if (elem->arg == NULL || strVal(elem->arg) == NULL)
554 | ereport(ERROR,
555 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
556 | errmsg("%s parameter \"%s\" had no value",
557 | paramtype, elem->defname)));
558 | }
559 |
--------------------------------------------------------------------------------
/ali_decoding/ali_decoding.control:
--------------------------------------------------------------------------------
1 | comment = 'ali_decoding'
2 | default_version = '0.0.1'
3 | module_pathname = '$libdir/ali_decoding'
4 | relocatable = true
5 |
--------------------------------------------------------------------------------
/ali_decoding/test/decode_test.sql:
--------------------------------------------------------------------------------
1 |
2 | create schema test_case;
3 | set search_path=test_case;
4 |
5 | -- no index
6 | create table a(a int ,b text, c timestamptz);
7 |
8 | insert into a values(1,'test','1999-01-08 04:05:06');
9 | insert into a values(2,'test','1999-01-08 04:05:06');
10 | insert into a values(3,'test','1999-01-08 04:05:06');
11 |
12 | update a set b = 'test1';
13 | update a set b = 'test2' where a = 3;
14 |
15 | delete from a where a = 2;
16 | delete from a;
17 |
18 | -- primary key
19 | create table b(a int primary key ,b text, c timestamptz);
20 |
21 | insert into b values(1,'test','1999-01-08 04:05:06');
22 | insert into b values(2,'test','1999-01-08 04:05:06');
23 | insert into b values(3,'test','1999-01-08 04:05:06');
24 |
25 | update b set b = 'test1';
26 | update b set a = 5 where a = 1;
27 | update b set b = 'test2' where a = 3;
28 | update b set c = '1999-01-08 04:05:06' where a = 5;
29 | update b set a = 6, c = '1999-01-08 04:05:06' where a = 5;
30 | update b set a = 5, b = 't',c = '1999-01-08 04:05:06' where a = 6;
31 |
32 | delete from b where a = 2;
33 | delete from b;
34 |
35 | -- mprimary key
36 | create table c(a int ,b text, c timestamptz, d bigint, primary key(a,d));
37 |
38 | insert into c values(1,'test','1999-01-08 04:05:06',3);
39 | insert into c values(2,'test','1999-01-08 04:05:06',2);
40 | insert into c values(3,'test','1999-01-08 04:05:06',1);
41 |
42 | update c set b = 'test1';
43 | update c set a = 5 where a = 1;
44 | update c set b = null where a = 3;
45 | delete from c where a = 2;
46 |
47 | -- REPLICA index
48 | create table d(a int ,b text, c timestamptz, d bigint);
49 | CREATE UNIQUE INDEX idx_d_a_d ON d(a,d);
50 | alter table d ALTER COLUMN a set not null;
51 | alter table d ALTER COLUMN d set not null;
52 | alter table d REPLICA IDENTITY USING INDEX idx_d_a_d;
53 |
54 | insert into d values(1,'test','1999-01-08 04:05:06',3);
55 | insert into d values(2,'test','1999-01-08 04:05:06',2);
56 | insert into d values(3,'test','1999-01-08 04:05:06',1);
57 |
58 | update d set b = 'test1';
59 | update d set a = 5 where a = 1;
60 | update d set b = 'test2' where a = 3;
61 | update d set a = 5, b = 't',c = '1999-01-08 04:05:06' where a = 3;
62 | delete from d;
63 |
64 | -- full data
65 | create table e(a int ,b text, c timestamptz, d bigint);
66 | alter table e REPLICA IDENTITY FULL;
67 |
68 | insert into e values(1,'test','1999-01-08 04:05:06',3);
69 | insert into e values(2,'test','1999-01-08 04:05:06',2);
70 | insert into e values(3,'test','1999-01-08 04:05:06',1);
71 |
72 | update e set b = 'test1';
73 | update e set a = 5 where a = 1;
74 | update e set b = 'test2' where a = 3;
75 | update e set a = 5, b = 't',c = '1999-01-08 04:05:06' where a = 3;
76 |
77 | delete from e;
78 |
79 |
80 | -- full data and primary key
81 | create table f(a int primary key,b text, c timestamptz, d bigint);
82 | alter table f REPLICA IDENTITY FULL;
83 |
84 | insert into f values(1,'test','1999-01-08 04:05:06',3);
85 | insert into f values(2,'test','1999-01-08 04:05:06',2);
86 | insert into f values(3,'test','1999-01-08 04:05:06',1);
87 |
88 | update f set b = 'test1';
89 | update f set a = 5 where a = 1;
90 |
91 | alter table f REPLICA IDENTITY DEFAULT;
92 | update f set a = 7 where a = 2;
93 |
94 | update f set b = 'test2' where a = 3;
95 | update f set a = 6, b = 't',c = '1999-01-08 04:05:06' where a = 3;
96 |
97 | delete from f;
98 |
99 | -- data type
100 | create table test_data_type_1(a smallint,b integer,c bigint,d decimal,e numeric);
101 | insert into test_data_type_1 values(-32768, 2147483647, 9223372036854775807, 111.111, 111.111);
102 |
103 | create table test_data_type_2(a real,b double precision,c smallserial,d serial,e bigserial);
104 | insert into test_data_type_2 values(111.111, 111.111, 32767, 2147483647, 9223372036854775807);
105 |
106 | create table test_data_type_3(a money,b character varying(20),c character(20),d text,e char(20));
107 | insert into test_data_type_3 values('12.34', '12.34', '12.34', '12.34', '12.34');
108 |
109 | create table test_data_type_4(a bytea,b bytea,c bytea,d bytea,e bytea);
110 | insert into test_data_type_4 values('\\xDEADBEEF', '\\000', '0', '\\134', '\\176');
111 |
112 | create table test_data_type_5(a timestamp without time zone ,b timestamp with time zone,c timestamp,d time,e time with time zone);
113 | insert into test_data_type_5 values('1999-01-08 04:05:06', '1999-01-08 04:05:06 +8:00', '1999-01-08 04:05:06 -8:00', '1999-01-08 04:05:06 -8:00', '1999-01-08 04:05:06 -8:00');
114 |
115 | CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
116 | create table test_data_type_6(a boolean,b mood,c point,d line,e lseg);
117 | insert into test_data_type_6 values(TRUE, 'happy', '(1,1)', '{1,2,1}', '[(1,2),(2,1)]');
118 |
119 | create table test_data_type_7(a path,b path,c polygon,d circle,e circle);
120 | insert into test_data_type_7 values('[(1,3),(2,2)]', '((1,3),(2,2))', '((1,3),(2,2))', '<(2,2),2>', '((2,3),1)');
121 |
122 | create table test_data_type_8(a cidr,b cidr,c inet,d macaddr,e macaddr);
123 | insert into test_data_type_8 values('192.168.100.128/25', '2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128', '1.2.3.4', '08-00-2b-01-02-03', '08:00:2b:01:02:03');
124 |
125 | CREATE TABLE test_9 (a BIT(3), b BIT VARYING(5));
126 | INSERT INTO test_9 VALUES (B'101', B'00');
127 | INSERT INTO test_9 VALUES (B'10'::bit(3), B'101');
128 |
129 | CREATE TABLE test_10 (a tsvector, b tsvector,c tsquery, d tsquery);
130 | INSERT INTO test_10 VALUES ('a fat cat sat on a mat and ate a fat rat', 'a:1 fat:2 cat:3 sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10 fat:11 rat:12','fat & rat','Fat:ab & Cats');
131 |
132 | create extension "uuid-ossp";
133 | CREATE TABLE test_11 (a uuid, b uuid,c uuid, d uuid);
134 | INSERT INTO test_11 VALUES ('25285134-7314-11e5-8e45-d89d672b3560', 'c12a3d5f-53bb-4223-9fca-0af78b4d269f', 'cf16fe52-3365-3a1f-8572-288d8d2aaa46', '252852d8-7314-11e5-8e45-2f1f0837ccca');
135 |
136 | CREATE TABLE test_12 (a xml, b xml,c xml);
137 | INSERT INTO test_12 VALUES (xml 'bar', XMLPARSE (DOCUMENT 'Manual...'), XMLPARSE (CONTENT 'abcbarfoo'));
138 |
139 | CREATE TABLE test_13 (a xml, b xml,c xml);
140 | INSERT INTO test_13 VALUES ('{"reading": 1.230e-5}',
141 | '[1, 2, "foo", null]',
142 | '{"bar": "baz", "balance": 7.77, "active":false}');
143 |
144 | CREATE TABLE sal_emp_14 (
145 | name text,
146 | pay_by_quarter integer[],
147 | schedule text[][]
148 | );
149 |
150 | INSERT INTO sal_emp_14
151 | VALUES ('Bill',
152 | '{10000, 10000, 10000, 10000}',
153 | '{{"meeting", "lunch"}, {"training", "presentation"}}');
154 |
155 | INSERT INTO sal_emp_14
156 | VALUES ('Carol',
157 | '{20000, 25000, 25000, 25000}',
158 | '{{"breakfast", "consulting"}, {"meeting", "lunch"}}');
159 |
160 | INSERT INTO sal_emp_14
161 | VALUES ('Carol',
162 | ARRAY[20000, 25000, 25000, 25000],
163 | ARRAY[['breakfast', 'consulting'], ['meeting', 'lunch']]);
164 |
165 | CREATE TYPE complex AS (
166 | r double precision,
167 | i double precision
168 | );
169 |
170 | CREATE TYPE inventory_item AS (
171 | name text,
172 | supplier_id integer,
173 | price numeric
174 | );
175 |
176 | CREATE TABLE on_hand_15 (
177 | item inventory_item,
178 | count integer
179 | );
180 |
181 | INSERT INTO on_hand_15 VALUES (ROW('fuzzy dice', 42, 1.99), 1000);
182 |
183 | CREATE TABLE reservation_16 (room int, during tsrange);
184 | INSERT INTO reservation_16 VALUES
185 | (1108, '[2010-01-01 14:30, 2010-01-01 15:30)');
186 |
187 | CREATE TYPE floatrange AS RANGE (
188 | subtype = float8,
189 | subtype_diff = float8mi
190 | );
191 |
192 | create table t_range_16(a floatrange);
193 | insert into t_range_16 values('[1.234, 5.678]');
194 |
195 | create extension hstore;
196 | create table hstore_test_17(item_id serial, data hstore);
197 | INSERT INTO hstore_test_17 (data) VALUES ('"key1"=>"value1", "key2"=>"value2", "key3"=>"value3"');
198 | UPDATE hstore_test_17 SET data = delete(data, 'key2');
199 | UPDATE hstore_test_17 SET data = data || '"key4"=>"some value"'::hstore;
200 |
201 | CREATE EXTENSION postgis;
202 | CREATE EXTENSION postgis_topology;
203 | CREATE EXTENSION fuzzystrmatch;
204 | CREATE EXTENSION postgis_tiger_geocoder;
205 |
206 | create table test_18 (myID int4, pt geometry, myName varchar );
207 | insert into test_18 values (1, 'POINT(0 0)', 'beijing' );
208 | insert into test_18 values (2, 'MULTIPOINT(1 1, 3 4, -1 3)', 'shanghai' );
209 | insert into test_18 values (3, 'LINESTRING(1 1, 2 2, 3 4)', 'tianjin' );
210 | insert into test_18 values (3, 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))', 'tianjin' );
211 | insert into test_18 values (3, 'MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))', 'tianjin' );
212 | insert into test_18 values (3, 'MULTILINESTRING((1 1, 2 2, 3 4),(2 2, 3 3, 4 5))', 'tianjin' );
213 |
214 | insert into test_18 values (3, '01060000000200000001030000000200000005000000000000000000000000000000000000000000000000001040000000000000000000000000000010400000000000001040000000000000000000000000000010400000000000000000000000000000000005000000000000000000F03F000000000000F03F0000000000000040000000000000F03F00000000000000400000000000000040000000000000F03F0000000000000040000000000000F03F000000000000F03F01030000000100000005000000000000000000F0BF000000000000F0BF000000000000F0BF00000000000000C000000000000000C000000000000000C000000000000000C0000000000000F0BF000000000000F0BF000000000000F0BF', 'tianjin' );
215 |
216 | -- m sql in a tran
217 | create table msql(a int primary key,b text, c timestamptz);
218 |
219 | begin;
220 | insert into msql values(1,'test','1999-01-08 04:05:06');
221 | insert into msql values(2,'test','1999-01-08 04:05:06');
222 | insert into msql values(3,'test','1999-01-08 04:05:06');
223 | update msql set b = 'test' where a = 1;
224 | delete from msql where a = 3;
225 | commit;
226 |
227 | -- alter table
228 | create table msql_1(a int primary key,b text, c timestamptz);
229 |
230 | insert into msql_1 values(1,'test','1999-01-08 04:05:06');
231 | alter table msql_1 add COLUMN d int;
232 | insert into msql_1 values(2,'test','1999-01-08 04:05:06',1);
233 | alter table msql_1 drop COLUMN b;
234 | insert into msql_1 values(3,'1999-01-08 04:05:06',2);
235 |
236 | update msql_1 set c = '1999-01-08 04:05:07';
237 | delete from msql_1;
238 |
239 | -- alter table in a tran
240 | create table msql_2(a int primary key,b text, c timestamptz);
241 | begin;
242 | insert into msql_2 values(1,'test','1999-01-08 04:05:06');
243 | alter table msql_2 add COLUMN d int;
244 | insert into msql_2 values(2,'test','1999-01-08 04:05:06',1);
245 | alter table msql_2 drop COLUMN b;
246 | insert into msql_2 values(3,'1999-01-08 04:05:06',2);
247 | update msql_2 set c = '1999-01-08 04:05:07';
248 | commit;
249 |
250 |
251 | -- alter table drop pk
252 | create table msql_3(a int primary key,b text, c timestamptz);
253 | begin;
254 | insert into msql_3 values(1,'test','1999-01-08 04:05:06');
255 | insert into msql_3 values(5,'test','1999-01-08 04:05:06');
256 | alter table msql_3 add COLUMN d int;
257 | insert into msql_3 values(2,'test','1999-01-08 04:05:06',1);
258 | alter table msql_3 drop COLUMN a;
259 | insert into msql_3 values('test','1999-01-08 04:05:06',2);
260 | delete from msql_3;
261 | commit;
262 |
263 | -- SERIAL
264 | CREATE TABLE seq_test
265 | (
266 | id SERIAL primary key ,
267 | name text
268 | ) ;
269 |
270 | insert into seq_test (name) values('test');
271 |
272 | -- toast
273 | create table t_kenyon(id int,vname varchar(48),remark text);
274 | select oid,relname,reltoastrelid from pg_class where relname = 't_kenyon';
275 | insert into t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
276 | insert into t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
277 | insert into t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
278 | insert into t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
279 | insert into t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',10000);
280 | insert into t_kenyon select generate_series(7,8),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',20000);
281 |
282 | -- utf8
283 | create table chinese_text(t text);
284 | insert into chinese_text values('微软 Surface Pro 4 中国开放预售 价格公布');
285 | insert into chinese_text values('\');
286 | insert into chinese_text values('\\');
287 | insert into chinese_text values('\\\');
288 | insert into chinese_text values('///');
289 | insert into chinese_text values('//');
290 | insert into chinese_text values('/');
291 | insert into chinese_text values('''');
292 | insert into chinese_text values('"''"');
293 |
294 | -- bug extra_float_digits default 3
295 | create table tf(c1 float4, c2 float8 ,c3 numeric);
296 | insert into tf values (1.5555555555555555555555,1.5555555555555555555555,1.5555555555555555555555);
297 |
298 | drop schema test_case cascade;
299 |
--------------------------------------------------------------------------------
/dbsync/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal requriement: Mysql >= 5.6, Pgsql >= 9.3
2 | MYSQL_CONFIG = mysql_config
3 | mysql_include_dir = $(shell $(MYSQL_CONFIG) --variable=pkgincludedir)
4 | mysql_lib_dir = $(shell $(MYSQL_CONFIG) --variable=pkglibdir)
5 |
6 | PGFILEDESC = "ali_recvlogical"
7 | NAME = ali_recvlogical
8 | MODULE_big = ali_recvlogical
9 | MODULES = ali_recvlogical
10 |
11 | OBJS = pg_logicaldecode.o pqformat.o stringinfo.o utils.o misc.o pgsync.o ini.o
12 |
13 | PG_CPPFLAGS = -DFRONTEND -I$(srcdir) -I$(libpq_srcdir) -I$(mysql_include_dir)
14 | PG_FLAGS = -DFRONTEND -I$(srcdir) -I$(libpq_srcdir) -I$(mysql_include_dir)
15 |
16 | PG_CONFIG = pg_config
17 | pgsql_lib_dir := $(shell $(PG_CONFIG) --libdir)
18 | PGXS := $(shell $(PG_CONFIG) --pgxs)
19 | include $(PGXS)
20 |
21 | RPATH_LDFLAGS='-Wl,-rpath,$$ORIGIN,-rpath,$$ORIGIN/lib,-rpath,$$ORIGIN/../lib,-rpath,$(mysql_lib_dir),-rpath,$(pgsql_lib_dir)'
22 | export RPATH_LDFLAGS
23 |
24 | LIBS = -lpthread
25 |
26 | all: demo.o dbsync-pgsql2pgsql.o mysql2pgsql.o dbsync-mysql2pgsql.o readcfg.o
27 | $(CXX) $(CFLAGS) demo.o $(OBJS) $(libpq_pgport) $(RPATH_LDFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o demo
28 | $(CXX) $(CFLAGS) readcfg.o dbsync-pgsql2pgsql.o $(OBJS) $(libpq_pgport) $(RPATH_LDFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o pgsql2pgsql
29 | $(CXX) $(CFLAGS) readcfg.o ini.o mysql2pgsql.o dbsync-mysql2pgsql.o misc.o stringinfo.o $(libpq_pgport) $(RPATH_LDFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -L$(mysql_lib_dir) -lmysqlclient -o mysql2pgsql
30 |
31 | clean:
32 | rm -rf *.o pgsql2pgsql mysql2pgsql demo ali_recvlogical.so
33 |
34 | package:
35 | mkdir -p install
36 | mkdir -p install/bin
37 | mkdir -p install/lib
38 | cp -fr pgsql2pgsql install/bin
39 | cp -fr demo install/bin
40 | cp -fr ali_recvlogical.so install/lib
41 | cp -fr mysql2pgsql install/bin
42 | cp -fr $(mysql_lib_dir)/libmysqlclient.so* install/lib
43 | cp -fr $(pgsql_lib_dir)/libpq.so* install/lib
44 |
--------------------------------------------------------------------------------
/dbsync/dbsync-mysql2pgsql.c:
--------------------------------------------------------------------------------
1 |
2 | #include "postgres_fe.h"
3 | #include "libpq-fe.h"
4 |
5 | #include "pgsync.h"
6 | #include "ini.h"
7 | #include "mysql.h"
8 | #include
9 |
10 | extern bool get_ddl_only;
11 | extern bool simple_wo_part;
12 | extern bool first_col_as_dist_key;
13 | extern int buffer_size;
14 |
15 | static int load_table_list_file(const char *filename, char*** p_tables, char*** p_queries);
16 |
17 |
18 | int
19 | main(int argc, char **argv)
20 | {
21 | char *desc = NULL;
22 | mysql_conn_info src = {0};
23 | int num_thread = 5; //Default to 5
24 | void *cfg = NULL;
25 | char *sport = NULL;
26 | //char *tabname = NULL;
27 | int res_getopt = 0;
28 | char *target_schema = NULL;
29 | char *table_list_file = NULL;
30 | char *cfg_file="my.cfg";
31 | char **tables = NULL, **queries = NULL;
32 | char *ignore_copy_error_count_each_table_str = NULL;
33 | uint32 ignore_copy_error_count_each_table = 0;
34 |
35 | while ((res_getopt = getopt(argc, argv, ":l:c:j:dnfhs:b:")) != -1)
36 | {
37 | switch (res_getopt)
38 | {
39 | case 'l':
40 | table_list_file = optarg;
41 | break;
42 | case 'c':
43 | cfg_file = optarg;
44 | break;
45 | case 'j':
46 | num_thread = atoi(optarg);
47 | break;
48 | case 'b':
49 | buffer_size = 1024 * atoi(optarg);
50 | break;
51 | case 's':
52 | target_schema = optarg;
53 | break;
54 | case ':':
55 | fprintf(stderr, "No value specified for -%c\n", optopt);
56 | break;
57 | case 'd':
58 | get_ddl_only = true;
59 | break;
60 | case 'n':
61 | simple_wo_part = true;
62 | break;
63 | case 'f':
64 | first_col_as_dist_key = true;
65 | break;
66 | case 'h':
67 | fprintf(stderr, "Usage: -l -j -d -n -f -s -b -h\n");
68 | fprintf(stderr, "\n -l specifies a file with table listed;\n -j specifies number of threads to do the job;\n -d means get DDL only without fetching data;\n -n means no partion info in DDLs;\n -f means taking first column as distribution key;\n -s specifies the target schema;\n -b specifies the buffer size in KB used to sending copy data to target db, the default is 0\n -h display this usage manual\n");
69 | return 0;
70 | case '?':
71 | fprintf(stderr, "Unsupported option: %c", optopt);
72 | break;
73 | default:
74 | fprintf(stderr, "Parameter parsing error: %c", res_getopt);
75 | return -1;
76 |
77 | }
78 | }
79 |
80 | cfg = init_config(cfg_file);
81 | if (cfg == NULL)
82 | {
83 | fprintf(stderr, "read config file error, insufficient permissions or my.cfg does not exist");
84 | return 1;
85 | }
86 |
87 | memset(&src, 0, sizeof(mysql_conn_info));
88 | get_config(cfg, "src.mysql", "host", &src.host);
89 | get_config(cfg, "src.mysql", "port", &sport);
90 | get_config(cfg, "src.mysql", "user", &src.user);
91 | get_config(cfg, "src.mysql", "password", &src.passwd);
92 | get_config(cfg, "src.mysql", "db", &src.db);
93 | get_config(cfg, "src.mysql", "encodingdir", &src.encodingdir);
94 | get_config(cfg, "src.mysql", "encoding", &src.encoding);
95 | get_config(cfg, "desc.pgsql", "connect_string", &desc);
96 | get_config(cfg, "desc.pgsql", "ignore_copy_error_count_each_table", &ignore_copy_error_count_each_table_str);
97 |
98 | if (src.host == NULL || sport == NULL ||
99 | src.user == NULL || src.passwd == NULL ||
100 | src.db == NULL || src.encodingdir == NULL ||
101 | src.encoding == NULL || desc == NULL)
102 | {
103 | fprintf(stderr, "parameter error, the necessary parameter is empty\n");
104 | return 1;
105 | }
106 |
107 | src.port = atoi(sport);
108 |
109 | if (ignore_copy_error_count_each_table_str)
110 | {
111 | ignore_copy_error_count_each_table = atoi(ignore_copy_error_count_each_table_str);
112 | }
113 |
114 | fprintf(stderr, "ignore copy error count %u each table\n", ignore_copy_error_count_each_table);
115 |
116 | if (table_list_file != NULL)
117 | {
118 | if (load_table_list_file(table_list_file, &tables, &queries))
119 | {
120 | fprintf(stderr, "Error occurs while loading table list file %s \n", table_list_file);
121 | return -1;
122 | }
123 |
124 | src.tabnames = (char **) tables;
125 | src.queries = (char**) queries;
126 | }
127 |
128 | /* Only one thread is needed when just generating DDL */
129 | if (get_ddl_only)
130 | num_thread = 1;
131 |
132 | return mysql2pgsql_sync_main(desc , num_thread, &src, target_schema, ignore_copy_error_count_each_table);
133 | }
134 |
135 |
136 | int load_table_list_file(const char *filename, char*** p_tables, char*** p_queries) {
137 | FILE *fp = NULL;
138 | int n, sz, num_lines = 0;
139 | char *table_list = NULL;
140 | char **table_array = NULL;
141 | char **query_array = NULL;
142 | char *p = NULL;
143 | char *tail = NULL;
144 | char *table_begin = NULL;
145 | char *table_end = NULL;
146 | char *query_begin = NULL;
147 | int cur_table = 0;
148 |
149 | /* Open file */
150 | fp = fopen(filename, "rb");
151 | if (!fp) {
152 | fprintf(stderr, "Error opening file %s", filename);
153 | goto fail;
154 | }
155 |
156 | /* Get file size */
157 | fseek(fp, 0, SEEK_END);
158 | sz = ftell(fp);
159 | if(sz < 0) {
160 | fprintf(stderr, "Error ftell file %s", filename);
161 | goto fail;
162 | }
163 | rewind(fp);
164 |
165 | /* Load file content into memory, null terminate, init end var */
166 | table_list = (char*) palloc0(sz + 1);
167 | if (!table_list)
168 | {
169 | fprintf(stderr, "Error malloc mem for file %s", filename);
170 | goto fail;
171 | }
172 |
173 | table_list[sz] = '\0';
174 | p = table_list;
175 | tail = table_list + sz;
176 | n = fread(table_list, 1, sz, fp);
177 | if (n != sz) {
178 | fprintf(stderr, "Error reading file %s", filename);
179 | goto fail;
180 | }
181 |
182 | /* Count lines of the file */
183 | while(p < tail)
184 | {
185 | switch (*p)
186 | {
187 | case '\0':
188 | fprintf(stderr, "Unexpected terminator encountered in file %s \n", filename);
189 | goto fail;
190 | case '\n':
191 | num_lines++;
192 | break;
193 |
194 | default:
195 | break;
196 | }
197 | p++;
198 | }
199 |
200 | /* Add the last line */
201 | num_lines++;
202 |
203 | /* Get memory for table array, with the last element being NULL */
204 | table_array = (char **) palloc0((num_lines + 1) * sizeof(char*));
205 | query_array = (char **) palloc0((num_lines + 1) * sizeof(char*));
206 |
207 | /* Parse data */
208 | p = table_list;
209 | table_begin = table_list;
210 | cur_table = 0;
211 | while(p <= tail)
212 | {
213 | if (*p == '\n' || p == tail)
214 | {
215 | /* Get the table name without leanding and trailing blanks
216 | * E.g. following line will generate a table name "tab 1"
217 | * | tab 1 : select * from tab |
218 | */
219 | while (*table_begin == ' ' || *table_begin == '\t')
220 | table_begin++;
221 |
222 | table_end = table_begin;
223 | while (*table_end != ':' && table_end != p)
224 | table_end++;
225 |
226 | query_begin = table_end + 1;
227 | table_end--;
228 | while (table_end >= table_begin &&
229 | (*table_end == ' ' || *table_end == '\t'))
230 | table_end--;
231 |
232 | if (table_end < table_begin)
233 | table_begin = NULL;
234 | else
235 | *(table_end+1) = '\0';
236 |
237 | while ((*query_begin == ' ' || *query_begin == '\t') && query_begin < p)
238 | query_begin++;
239 |
240 | if (query_begin >= p)
241 | query_begin = NULL;
242 | else
243 | *p = '\0';
244 |
245 | if (table_begin)
246 | {
247 | table_array[cur_table] = table_begin;
248 | query_array[cur_table] = query_begin;
249 | cur_table++;
250 | fprintf(stderr, "-- Adding table: %s\n", table_begin);
251 | }
252 |
253 | table_begin = p + 1;
254 | }
255 |
256 | p++;
257 | }
258 |
259 | /* Clean up and return */
260 | fclose(fp);
261 | *p_tables = table_array;
262 | *p_queries = query_array;
263 | return 0;
264 |
265 | fail:
266 | if (fp)
267 | fclose(fp);
268 | if (table_list)
269 | free(table_list);
270 | if (table_array)
271 | free(table_array);
272 | if (query_array)
273 | free(query_array);
274 |
275 | return -1;
276 | }
277 |
278 |
--------------------------------------------------------------------------------
/dbsync/dbsync-pgsql2pgsql.c:
--------------------------------------------------------------------------------
1 |
2 | #include "postgres_fe.h"
3 | #include "lib/stringinfo.h"
4 |
5 | #include
6 |
7 | #include "common/fe_memutils.h"
8 | #include "libpq-fe.h"
9 | #include "libpq/pqsignal.h"
10 | #include "pqexpbuffer.h"
11 | #include "libpq/pqformat.h"
12 |
13 | #include "pg_logicaldecode.h"
14 | #include "pgsync.h"
15 | #include "ini.h"
16 |
17 | int
18 | main(int argc, char **argv)
19 | {
20 | char *src = NULL;
21 | char *desc = NULL;
22 | char *local = NULL;
23 | void *cfg = NULL;
24 |
25 | cfg = init_config("my.cfg");
26 | if (cfg == NULL)
27 | {
28 | fprintf(stderr, "read config file error, insufficient permissions or my.cfg does not exist");
29 | return 1;
30 | }
31 |
32 | get_config(cfg, "src.pgsql", "connect_string", &src);
33 | get_config(cfg, "local.pgsql", "connect_string", &local);
34 | get_config(cfg, "desc.pgsql", "connect_string", &desc);
35 |
36 | if (src == NULL || desc == NULL || local == NULL)
37 | {
38 | fprintf(stderr, "parameter error, the necessary parameter is empty");
39 | return 1;
40 | }
41 |
42 | return db_sync_main(src, desc, local ,5);
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/dbsync/demo.cpp:
--------------------------------------------------------------------------------
1 | /*-------------------------------------------------------------------------
2 | *
3 | * pg_recvlogical.c - receive data from a logical decoding slot in a streaming
4 | * fashion and write it to a local file.
5 | *
6 | * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
7 | *
8 | * IDENTIFICATION
9 | * src/bin/pg_basebackup/pg_recvlogical.c
10 | *-------------------------------------------------------------------------
11 | */
12 |
13 | #include "postgres_fe.h"
14 |
15 | #include
16 | #include
17 | #include
18 |
19 | #include "lib/stringinfo.h"
20 |
21 | #include "access/xlog_internal.h"
22 | #include "common/fe_memutils.h"
23 | #include "getopt_long.h"
24 | #include "libpq-fe.h"
25 | #include "libpq/pqsignal.h"
26 | #include "pqexpbuffer.h"
27 | #include "libpq/pqformat.h"
28 |
29 | #include "pg_logicaldecode.h"
30 | #include "catalog/catversion.h"
31 |
32 |
33 | /* Time to sleep between reconnection attempts */
34 | #define RECONNECT_SLEEP_TIME 5
35 |
36 | static int standby_message_timeout = 10 * 1000; /* 10 sec = default */
37 |
38 | static volatile bool time_to_abort = false;
39 |
40 | /*
41 | * Unfortunately we can't do sensible signal handling on windows...
42 | */
43 | #ifndef WIN32
44 |
45 | /*
46 | * When sigint is called, just tell the system to exit at the next possible
47 | * moment.
48 | */
49 | static void
50 | sigint_handler(int signum)
51 | {
52 | time_to_abort = true;
53 | }
54 |
55 | #endif
56 |
57 |
58 | int
59 | main(int argc, char **argv)
60 | {
61 | Decoder_handler *hander;
62 | int rc = 0;
63 | bool init = false;
64 |
65 | hander = init_hander();
66 | hander->connection_string = (char *)"host=192.168.1.1 port=3001 dbname=test user=test password=123456";
67 | XLogRecPtr lsn;
68 |
69 | init_logfile(hander);
70 | rc = check_handler_parameters(hander);
71 | if(rc != 0)
72 | {
73 | exit(1);
74 | }
75 |
76 | rc = initialize_connection(hander);
77 | if(rc != 0)
78 | {
79 | exit(1);
80 | }
81 | init_streaming(hander);
82 | init = true;
83 |
84 | #ifndef WIN32
85 | signal(SIGINT, sigint_handler);
86 | #endif
87 |
88 | if (hander->do_drop_slot)
89 | {
90 | rc = drop_replication_slot(hander);
91 | return 0;
92 | }
93 |
94 | if (hander->do_create_slot)
95 | {
96 | if(create_replication_slot(hander, &lsn, (char *)"test") == NULL)
97 | {
98 | exit(1);
99 | }
100 | }
101 |
102 | if (!hander->do_start_slot)
103 | {
104 | disconnect(hander);
105 | exit(0);
106 | }
107 |
108 | while (true)
109 | {
110 | ALI_PG_DECODE_MESSAGE *msg = NULL;
111 |
112 | if (time_to_abort)
113 | {
114 | if (hander->copybuf != NULL)
115 | {
116 | PQfreemem(hander->copybuf);
117 | hander->copybuf = NULL;
118 | }
119 | if (hander->conn)
120 | {
121 | PQfinish(hander->conn);
122 | hander->conn = NULL;
123 | }
124 | break;
125 | }
126 |
127 | if (!init)
128 | {
129 | initialize_connection(hander);
130 | init_streaming(hander);
131 | init = true;
132 | }
133 |
134 | msg = exec_logical_decoder(hander, &time_to_abort);
135 | if (msg != NULL)
136 | {
137 | out_put_decode_message(hander, msg, hander->outfd);
138 | hander->flushpos = hander->recvpos;
139 | }
140 | else
141 | {
142 | //printf("%s: disconnected; waiting %d seconds to try again\n",hander->progname, RECONNECT_SLEEP_TIME);
143 | pg_sleep(RECONNECT_SLEEP_TIME * 1000000);
144 | init = false;
145 | }
146 | }
147 | }
148 |
149 |
--------------------------------------------------------------------------------
/dbsync/ini.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #include "postgres_fe.h"
8 | #include "port.h"
9 | #include "ini.h"
10 |
11 | struct ini_t {
12 | char *data;
13 | char *end;
14 | };
15 |
16 | /* Case insensitive string compare */
17 | static int strcmpci(const char *a, const char *b) {
18 | for (;;) {
19 | int d = tolower(*a) - tolower(*b);
20 | if (d != 0 || !*a) {
21 | return d;
22 | }
23 | a++, b++;
24 | }
25 | }
26 |
27 | /* Returns the next string in the split data */
28 | static char* next(ini_t *ini, char *p) {
29 | p += strlen(p);
30 | while (p < ini->end && *p == '\0') {
31 | p++;
32 | }
33 | return p;
34 | }
35 |
36 | static void trim_back(ini_t *ini, char *p) {
37 | while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) {
38 | *p-- = '\0';
39 | }
40 | }
41 |
42 | static char* discard_line(ini_t *ini, char *p) {
43 | while (p < ini->end && *p != '\n') {
44 | *p++ = '\0';
45 | }
46 | return p;
47 | }
48 |
49 | static char *unescape_quoted_value(ini_t *ini, char *p) {
50 | /* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q`
51 | * as escape sequences are always larger than their resultant data */
52 | char *q = p;
53 | p++;
54 | while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') {
55 | if (*p == '\\') {
56 | /* Handle escaped char */
57 | p++;
58 | switch (*p) {
59 | default : *q = *p; break;
60 | case 'r' : *q = '\r'; break;
61 | case 'n' : *q = '\n'; break;
62 | case 't' : *q = '\t'; break;
63 | case '\r' :
64 | case '\n' :
65 | case '\0' : goto end;
66 | }
67 |
68 | } else {
69 | /* Handle normal char */
70 | *q = *p;
71 | }
72 | q++, p++;
73 | }
74 | end:
75 | return q;
76 | }
77 |
78 | /* Splits data in place into strings containing section-headers, keys and
79 | * values using one or more '\0' as a delimiter. Unescapes quoted values */
80 | static void split_data(ini_t *ini) {
81 | char *value_start, *line_start;
82 | char *p = ini->data;
83 |
84 | while (p < ini->end) {
85 | switch (*p) {
86 | case '\r':
87 | case '\n':
88 | case '\t':
89 | case ' ':
90 | *p = '\0';
91 | /* Fall through */
92 |
93 | case '\0':
94 | p++;
95 | break;
96 |
97 | case '[':
98 | p += strcspn(p, "]\n");
99 | *p = '\0';
100 | break;
101 |
102 | case ';':
103 | p = discard_line(ini, p);
104 | break;
105 |
106 | default:
107 | line_start = p;
108 | p += strcspn(p, "=\n");
109 |
110 | /* Is line missing a '='? */
111 | if (*p != '=') {
112 | p = discard_line(ini, line_start);
113 | break;
114 | }
115 | trim_back(ini, p - 1);
116 |
117 | /* Replace '=' and whitespace after it with '\0' */
118 | do {
119 | *p++ = '\0';
120 | } while (*p == ' ' || *p == '\r' || *p == '\t');
121 |
122 | /* Is a value after '=' missing? */
123 | if (*p == '\n' || *p == '\0') {
124 | p = discard_line(ini, line_start);
125 | break;
126 | }
127 |
128 | if (*p == '"') {
129 | /* Handle quoted string value */
130 | value_start = p;
131 | p = unescape_quoted_value(ini, p);
132 |
133 | /* Was the string empty? */
134 | if (p == value_start) {
135 | p = discard_line(ini, line_start);
136 | break;
137 | }
138 |
139 | /* Discard the rest of the line after the string value */
140 | p = discard_line(ini, p);
141 |
142 | } else {
143 | /* Handle normal value */
144 | p += strcspn(p, "\n");
145 | trim_back(ini, p - 1);
146 | }
147 | break;
148 | }
149 | }
150 | }
151 |
152 | ini_t* ini_load(const char *filename) {
153 | ini_t *ini = NULL;
154 | FILE *fp = NULL;
155 | int n, sz;
156 |
157 | /* Init ini struct */
158 | ini = (ini_t*) malloc(sizeof(*ini));
159 | if (!ini) {
160 | goto fail;
161 | }
162 | memset(ini, 0, sizeof(*ini));
163 |
164 | /* Open file */
165 | fp = fopen(filename, "rb");
166 | if (!fp) {
167 | goto fail;
168 | }
169 |
170 | /* Get file size */
171 | fseek(fp, 0, SEEK_END);
172 | sz = ftell(fp);
173 | if(sz < 0) {
174 | goto fail;
175 | }
176 | rewind(fp);
177 |
178 | /* Load file content into memory, null terminate, init end var */
179 | ini->data = (char*) malloc(sz + 1);
180 | ini->data[sz] = '\0';
181 | ini->end = ini->data + sz;
182 | n = fread(ini->data, 1, sz, fp);
183 | if (n != sz) {
184 | goto fail;
185 | }
186 |
187 | /* Prepare data */
188 | split_data(ini);
189 |
190 | /* Clean up and return */
191 | fclose(fp);
192 | return ini;
193 |
194 | fail:
195 | if (fp) fclose(fp);
196 | if (ini) ini_free(ini);
197 | return NULL;
198 | }
199 |
200 | void ini_free(ini_t *ini) {
201 | free(ini->data);
202 | free(ini);
203 | }
204 |
205 | const char* ini_get(ini_t *ini, const char *section, const char *key) {
206 | const char *current_section = "";
207 | char *val;
208 | char *p = ini->data;
209 |
210 | if (*p == '\0') {
211 | p = next(ini, p);
212 | }
213 |
214 | while (p < ini->end) {
215 | if (*p == '[') {
216 | /* Handle section */
217 | current_section = p + 1;
218 |
219 | } else {
220 | /* Handle key */
221 | val = next(ini, p);
222 | if (!section || !strcmpci(section, current_section)) {
223 | if (!strcmpci(p, key)) {
224 | return val;
225 | }
226 | }
227 | p = val;
228 | }
229 |
230 | p = next(ini, p);
231 | }
232 |
233 | return NULL;
234 | }
235 |
236 | int ini_sget(
237 | ini_t *ini, const char *section, const char *value,
238 | const char *scanfmt, void *dst
239 | ) {
240 | const char *val = ini_get(ini, section, value);
241 | if (!val) {
242 | return 0;
243 | }
244 | if (scanfmt) {
245 | sscanf(val, scanfmt, dst);
246 | } else {
247 | *((const char**) dst) = val;
248 | }
249 | return 1;
250 | }
251 |
--------------------------------------------------------------------------------
/dbsync/ini.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015 rxi
3 | *
4 | * This library is free software; you can redistribute it and/or modify it
5 | * under the terms of the MIT license. See LICENSE for details.
6 | */
7 |
8 | #ifndef INI_H
9 | #define INI_H
10 |
11 | #define INI_VERSION "0.1.1"
12 |
13 | #ifdef __cplusplus
14 | extern "C" {
15 | #endif
16 |
17 | typedef struct ini_t ini_t;
18 |
19 | extern ini_t* ini_load(const char *filename);
20 | extern void ini_free(ini_t *ini);
21 | extern const char* ini_get(ini_t *ini, const char *section, const char *key);
22 | extern int ini_sget(ini_t *ini, const char *section, const char *value,
23 | const char *scanfmt, void *dst);
24 | extern void *init_config(char *cfgpath);
25 | extern int get_config(void *cfg, char *sec, char* key, char **value);
26 |
27 | #ifdef __cplusplus
28 | } /* extern "C" */
29 | #endif
30 |
31 | #endif
32 |
--------------------------------------------------------------------------------
/dbsync/misc.c:
--------------------------------------------------------------------------------
1 |
2 | #include "postgres_fe.h"
3 | #include "lib/stringinfo.h"
4 | #include "common/fe_memutils.h"
5 |
6 | #include "libpq-fe.h"
7 |
8 | #include "access/transam.h"
9 | #include "libpq/pqformat.h"
10 | #include "pqexpbuffer.h"
11 |
12 | #include "misc.h"
13 |
14 | #include
15 |
16 |
17 | #ifndef WIN32
18 | #include
19 | #include
20 | #endif
21 |
22 | bool
23 | WaitThreadEnd(int n, Thread *th)
24 | {
25 | ThreadHandle *hanlde = NULL;
26 | int i;
27 |
28 | hanlde = (ThreadHandle *)malloc(sizeof(ThreadHandle) * n);
29 | for(i = 0; i < n; i++)
30 | {
31 | hanlde[i]=th[i].os_handle;
32 | }
33 |
34 | #ifdef WIN32
35 | WaitForMultipleObjects(n, hanlde, TRUE, INFINITE);
36 | #else
37 | for(i = 0; i < n; i++)
38 | pthread_join(hanlde[i], NULL);
39 | #endif
40 |
41 | free(hanlde);
42 |
43 | return true;
44 | }
45 |
46 | int
47 | ThreadCreate(Thread *th,
48 | void *(*start)(void *arg),
49 | void *arg)
50 | {
51 | int rc = -1;
52 | #ifdef WIN32
53 | th->os_handle = (HANDLE)_beginthreadex(NULL,
54 | 0,
55 | (unsigned(__stdcall*)(void*)) start,
56 | arg,
57 | 0,
58 | &th->thid);
59 |
60 | /* error for returned value 0 */
61 | if (th->os_handle == (HANDLE) 0)
62 | th->os_handle = INVALID_HANDLE_VALUE;
63 | else
64 | rc = 1;
65 | #else
66 | rc = pthread_create(&th->os_handle,
67 | NULL,
68 | start,
69 | arg);
70 | #endif
71 | return rc;
72 | }
73 |
74 | void
75 | ThreadExit(int code)
76 | {
77 | #ifdef WIN32
78 | _endthreadex((unsigned) code);
79 | #else
80 | pthread_exit((void *)NULL);
81 | return;
82 | #endif
83 | }
84 |
85 |
86 | PGconn *
87 | pglogical_connect(const char *connstring, const char *connname)
88 | {
89 | PGconn *conn;
90 | StringInfoData dsn;
91 |
92 | initStringInfo(&dsn);
93 | appendStringInfo(&dsn,
94 | "%s fallback_application_name='%s'",
95 | connstring, connname);
96 |
97 | conn = PQconnectdb(dsn.data);
98 | if (PQstatus(conn) != CONNECTION_OK)
99 | {
100 | fprintf(stderr,"could not connect to the postgresql server: %s dsn was: %s",
101 | PQerrorMessage(conn), dsn.data);
102 | return NULL;
103 | }
104 |
105 | return conn;
106 | }
107 |
108 | bool
109 | is_greenplum(PGconn *conn)
110 | {
111 | char *query = "select version from version()";
112 | bool is_greenplum = false;
113 | char *result;
114 | PGresult *res;
115 |
116 | res = PQexec(conn, query);
117 | if (PQresultStatus(res) != PGRES_TUPLES_OK)
118 | {
119 | fprintf(stderr, "init sql run failed: %s", PQresultErrorMessage(res));
120 | return false;
121 | }
122 | result = PQgetvalue(res, 0, 0);
123 | if (strstr(result, "Greenplum") != NULL)
124 | {
125 | is_greenplum = true;
126 | }
127 |
128 | PQclear(res);
129 |
130 | return is_greenplum;
131 | }
132 |
133 | size_t
134 | quote_literal_internal(char *dst, const char *src, size_t len)
135 | {
136 | const char *s;
137 | char *savedst = dst;
138 |
139 | for (s = src; s < src + len; s++)
140 | {
141 | if (*s == '\\')
142 | {
143 | *dst++ = ESCAPE_STRING_SYNTAX;
144 | break;
145 | }
146 | }
147 |
148 | *dst++ = '\'';
149 | while (len-- > 0)
150 | {
151 | if (SQL_STR_DOUBLE(*src, true))
152 | *dst++ = *src;
153 | *dst++ = *src++;
154 | }
155 | *dst++ = '\'';
156 |
157 | return dst - savedst;
158 | }
159 |
160 | int
161 | start_copy_origin_tx(PGconn *conn, const char *snapshot, int pg_version, bool is_greenplum)
162 | {
163 | PGresult *res;
164 | const char *setup_query = NULL;
165 | StringInfoData query;
166 |
167 | if (is_greenplum == false)
168 | {
169 | setup_query = "BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY;\n";
170 | }
171 | else
172 | {
173 | setup_query = "BEGIN";
174 | }
175 |
176 | initStringInfo(&query);
177 | appendStringInfoString(&query, setup_query);
178 |
179 | if (snapshot)
180 | appendStringInfo(&query, "SET TRANSACTION SNAPSHOT '%s';\n", snapshot);
181 |
182 | res = PQexec(conn, query.data);
183 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
184 | {
185 | fprintf(stderr, "BEGIN on origin node failed: %s",
186 | PQresultErrorMessage(res));
187 | return 1;
188 | }
189 |
190 | PQclear(res);
191 |
192 | setup_connection(conn, pg_version, is_greenplum);
193 |
194 | return 0;
195 | }
196 |
197 | int
198 | start_copy_target_tx(PGconn *conn, int pg_version, bool is_greenplum)
199 | {
200 | PGresult *res;
201 | const char *setup_query =
202 | "BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;\n";
203 |
204 | res = PQexec(conn, setup_query);
205 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
206 | {
207 | fprintf(stderr, "BEGIN on target node failed: %s",
208 | PQresultErrorMessage(res));
209 | return 1;
210 | }
211 |
212 | PQclear(res);
213 |
214 | setup_connection(conn, pg_version, is_greenplum);
215 |
216 | return 0;
217 | }
218 |
219 | int
220 | finish_copy_origin_tx(PGconn *conn)
221 | {
222 | PGresult *res;
223 |
224 | /* Close the transaction and connection on origin node. */
225 | res = PQexec(conn, "ROLLBACK");
226 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
227 | {
228 | fprintf(stderr, "ROLLBACK on origin node failed: %s",
229 | PQresultErrorMessage(res));
230 | return 1;
231 | }
232 |
233 | PQclear(res);
234 | //PQfinish(conn);
235 | return 0;
236 | }
237 |
238 | int
239 | finish_copy_target_tx(PGconn *conn)
240 | {
241 | PGresult *res;
242 |
243 | /* Close the transaction and connection on target node. */
244 | res = PQexec(conn, "COMMIT");
245 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
246 | {
247 | fprintf(stderr, "COMMIT on target node failed: %s",
248 | PQresultErrorMessage(res));
249 | return 1;
250 | }
251 |
252 | PQclear(res);
253 | //PQfinish(conn);
254 | return 0;
255 | }
256 |
257 | int
258 | setup_connection(PGconn *conn, int remoteVersion, bool is_greenplum)
259 | {
260 | char *dumpencoding = "utf8";
261 |
262 | /*
263 | * Set the client encoding if requested. If dumpencoding == NULL then
264 | * either it hasn't been requested or we're a cloned connection and then
265 | * this has already been set in CloneArchive according to the original
266 | * connection encoding.
267 | */
268 | if (PQsetClientEncoding(conn, dumpencoding) < 0)
269 | {
270 | fprintf(stderr, "invalid client encoding \"%s\" specified\n",
271 | dumpencoding);
272 | return 1;
273 | }
274 |
275 | /*
276 | * Get the active encoding and the standard_conforming_strings setting, so
277 | * we know how to escape strings.
278 | */
279 | //AH->encoding = PQclientEncoding(conn);
280 |
281 | //std_strings = PQparameterStatus(conn, "standard_conforming_strings");
282 | //AH->std_strings = (std_strings && strcmp(std_strings, "on") == 0);
283 |
284 | /* Set the datestyle to ISO to ensure the dump's portability */
285 | ExecuteSqlStatement(conn, "SET DATESTYLE = ISO");
286 |
287 | /* Likewise, avoid using sql_standard intervalstyle */
288 | if (remoteVersion >= 80400)
289 | ExecuteSqlStatement(conn, "SET INTERVALSTYLE = POSTGRES");
290 |
291 | /*
292 | * If supported, set extra_float_digits so that we can dump float data
293 | * exactly (given correctly implemented float I/O code, anyway)
294 | */
295 | if (remoteVersion >= 90000)
296 | ExecuteSqlStatement(conn, "SET extra_float_digits TO 3");
297 | else if (remoteVersion >= 70400)
298 | ExecuteSqlStatement(conn, "SET extra_float_digits TO 2");
299 |
300 | /*
301 | * If synchronized scanning is supported, disable it, to prevent
302 | * unpredictable changes in row ordering across a dump and reload.
303 | */
304 | if (remoteVersion >= 80300 && !is_greenplum)
305 | ExecuteSqlStatement(conn, "SET synchronize_seqscans TO off");
306 |
307 | /*
308 | * Disable timeouts if supported.
309 | */
310 | if (remoteVersion >= 70300)
311 | ExecuteSqlStatement(conn, "SET statement_timeout = 0");
312 | if (remoteVersion >= 90300)
313 | ExecuteSqlStatement(conn, "SET lock_timeout = 0");
314 |
315 | return 0;
316 | }
317 |
318 | int
319 | ExecuteSqlStatement(PGconn *conn, const char *query)
320 | {
321 | PGresult *res;
322 | int rc = 0;
323 |
324 | res = PQexec(conn, query);
325 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
326 | {
327 | fprintf(stderr, "set %s failed: %s",
328 | query, PQerrorMessage(conn));
329 | rc = 1;
330 | }
331 | PQclear(res);
332 |
333 | return rc;
334 | }
335 |
336 | #ifndef FRONTEND
337 | #error "This file is not expected to be compiled for backend code"
338 | #endif
339 |
340 | void *
341 | pg_malloc(size_t size)
342 | {
343 | void *tmp;
344 |
345 | /* Avoid unportable behavior of malloc(0) */
346 | if (size == 0)
347 | size = 1;
348 | tmp = malloc(size);
349 | if (!tmp)
350 | {
351 | fprintf(stderr, _("out of memory\n"));
352 | exit(EXIT_FAILURE);
353 | }
354 | return tmp;
355 | }
356 |
357 | void *
358 | pg_malloc0(size_t size)
359 | {
360 | void *tmp;
361 |
362 | tmp = pg_malloc(size);
363 | MemSet(tmp, 0, size);
364 | return tmp;
365 | }
366 |
367 | void *
368 | palloc(Size size)
369 | {
370 | return pg_malloc(size);
371 | }
372 |
373 | void *
374 | palloc0(Size size)
375 | {
376 | return pg_malloc0(size);
377 | }
378 |
379 | char *
380 | pstrdup(const char *in)
381 | {
382 | return pg_strdup(in);
383 | }
384 |
385 | void *
386 | repalloc(void *pointer, Size size)
387 | {
388 | return pg_realloc(pointer, size);
389 | }
390 |
391 | void *
392 | pg_realloc(void *ptr, size_t size)
393 | {
394 | void *tmp;
395 |
396 | /* Avoid unportable behavior of realloc(NULL, 0) */
397 | if (ptr == NULL && size == 0)
398 | size = 1;
399 | tmp = realloc(ptr, size);
400 | if (!tmp)
401 | {
402 | fprintf(stderr, _("out of memory\n"));
403 | exit(EXIT_FAILURE);
404 | }
405 | return tmp;
406 | }
407 |
408 | char *
409 | pg_strdup(const char *in)
410 | {
411 | char *tmp;
412 |
413 | if (!in)
414 | {
415 | fprintf(stderr,
416 | _("cannot duplicate null pointer (internal error)\n"));
417 | exit(EXIT_FAILURE);
418 | }
419 | tmp = strdup(in);
420 | if (!tmp)
421 | {
422 | fprintf(stderr, _("out of memory\n"));
423 | exit(EXIT_FAILURE);
424 | }
425 | return tmp;
426 | }
427 |
428 | void
429 | pfree(void *pointer)
430 | {
431 | pg_free(pointer);
432 | }
433 |
434 | void
435 | pg_free(void *ptr)
436 | {
437 | if (ptr != NULL)
438 | free(ptr);
439 | }
440 |
441 | char *
442 | psprintf(const char *fmt,...)
443 | {
444 | size_t len = 128; /* initial assumption about buffer size */
445 |
446 | for (;;)
447 | {
448 | char *result;
449 | va_list args;
450 | size_t newlen;
451 |
452 | /*
453 | * Allocate result buffer. Note that in frontend this maps to malloc
454 | * with exit-on-error.
455 | */
456 | result = (char *) palloc(len);
457 |
458 | /* Try to format the data. */
459 | va_start(args, fmt);
460 | newlen = pvsnprintf(result, len, fmt, args);
461 | va_end(args);
462 |
463 | if (newlen < len)
464 | return result; /* success */
465 |
466 | /* Release buffer and loop around to try again with larger len. */
467 | pfree(result);
468 | len = newlen;
469 | }
470 | }
471 |
472 | /*
473 | * pvsnprintf
474 | *
475 | * Attempt to format text data under the control of fmt (an sprintf-style
476 | * format string) and insert it into buf (which has length len, len > 0).
477 | *
478 | * If successful, return the number of bytes emitted, not counting the
479 | * trailing zero byte. This will always be strictly less than len.
480 | *
481 | * If there's not enough space in buf, return an estimate of the buffer size
482 | * needed to succeed (this *must* be more than the given len, else callers
483 | * might loop infinitely).
484 | *
485 | * Other error cases do not return, but exit via elog(ERROR) or exit().
486 | * Hence, this shouldn't be used inside libpq.
487 | *
488 | * This function exists mainly to centralize our workarounds for
489 | * non-C99-compliant vsnprintf implementations. Generally, any call that
490 | * pays any attention to the return value should go through here rather
491 | * than calling snprintf or vsnprintf directly.
492 | *
493 | * Note that the semantics of the return value are not exactly C99's.
494 | * First, we don't promise that the estimated buffer size is exactly right;
495 | * callers must be prepared to loop multiple times to get the right size.
496 | * Second, we return the recommended buffer size, not one less than that;
497 | * this lets overflow concerns be handled here rather than in the callers.
498 | */
499 | size_t
500 | pvsnprintf(char *buf, size_t len, const char *fmt, va_list args)
501 | {
502 | int nprinted;
503 |
504 | Assert(len > 0);
505 |
506 | errno = 0;
507 |
508 | /*
509 | * Assert check here is to catch buggy vsnprintf that overruns the
510 | * specified buffer length. Solaris 7 in 64-bit mode is an example of a
511 | * platform with such a bug.
512 | */
513 | #ifdef USE_ASSERT_CHECKING
514 | buf[len - 1] = '\0';
515 | #endif
516 |
517 | nprinted = vsnprintf(buf, len, fmt, args);
518 |
519 | Assert(buf[len - 1] == '\0');
520 |
521 | /*
522 | * If vsnprintf reports an error other than ENOMEM, fail. The possible
523 | * causes of this are not user-facing errors, so elog should be enough.
524 | */
525 | if (nprinted < 0 && errno != 0 && errno != ENOMEM)
526 | {
527 | #ifndef FRONTEND
528 | elog(ERROR, "vsnprintf failed: %m");
529 | #else
530 | fprintf(stderr, "vsnprintf failed: %s\n", strerror(errno));
531 | exit(EXIT_FAILURE);
532 | #endif
533 | }
534 |
535 | /*
536 | * Note: some versions of vsnprintf return the number of chars actually
537 | * stored, not the total space needed as C99 specifies. And at least one
538 | * returns -1 on failure. Be conservative about believing whether the
539 | * print worked.
540 | */
541 | if (nprinted >= 0 && (size_t) nprinted < len - 1)
542 | {
543 | /* Success. Note nprinted does not include trailing null. */
544 | return (size_t) nprinted;
545 | }
546 |
547 | if (nprinted >= 0 && (size_t) nprinted > len)
548 | {
549 | /*
550 | * This appears to be a C99-compliant vsnprintf, so believe its
551 | * estimate of the required space. (If it's wrong, the logic will
552 | * still work, but we may loop multiple times.) Note that the space
553 | * needed should be only nprinted+1 bytes, but we'd better allocate
554 | * one more than that so that the test above will succeed next time.
555 | *
556 | * In the corner case where the required space just barely overflows,
557 | * fall through so that we'll error out below (possibly after
558 | * looping).
559 | */
560 | if ((size_t) nprinted <= MaxAllocSize - 2)
561 | return nprinted + 2;
562 | }
563 |
564 | /*
565 | * Buffer overrun, and we don't know how much space is needed. Estimate
566 | * twice the previous buffer size, but not more than MaxAllocSize; if we
567 | * are already at MaxAllocSize, choke. Note we use this palloc-oriented
568 | * overflow limit even when in frontend.
569 | */
570 | if (len >= MaxAllocSize)
571 | {
572 | #ifndef FRONTEND
573 | ereport(ERROR,
574 | (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
575 | errmsg("out of memory")));
576 | #else
577 | fprintf(stderr, _("out of memory\n"));
578 | exit(EXIT_FAILURE);
579 | #endif
580 | }
581 |
582 | if (len >= MaxAllocSize / 2)
583 | return MaxAllocSize;
584 |
585 | return len * 2;
586 | }
587 |
588 |
589 |
--------------------------------------------------------------------------------
/dbsync/misc.h:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ifndef PG_MISC_H
4 | #define PG_MISC_H
5 |
6 | #include "postgres_fe.h"
7 |
8 | #include "lib/stringinfo.h"
9 | #include "lib/stringinfo.h"
10 | #include "common/fe_memutils.h"
11 |
12 | #include "libpq-fe.h"
13 |
14 | #include "access/transam.h"
15 | #include "libpq/pqformat.h"
16 | #include "pqexpbuffer.h"
17 |
18 | #ifdef WIN32
19 | typedef CRITICAL_SECTION pthread_mutex_t;
20 | typedef HANDLE ThreadHandle;
21 | typedef DWORD ThreadId;
22 | typedef unsigned thid_t;
23 |
24 | typedef struct Thread
25 | {
26 | ThreadHandle os_handle;
27 | thid_t thid;
28 | }Thread;
29 |
30 | typedef CRITICAL_SECTION pthread_mutex_t;
31 | typedef DWORD pthread_t;
32 | #define pthread_mutex_lock(A) (EnterCriticalSection(A),0)
33 | #define pthread_mutex_trylock(A) win_pthread_mutex_trylock((A))
34 | #define pthread_mutex_unlock(A) (LeaveCriticalSection(A), 0)
35 | #define pthread_mutex_init(A,B) (InitializeCriticalSection(A),0)
36 | #define pthread_mutex_lock(A) (EnterCriticalSection(A),0)
37 | #define pthread_mutex_trylock(A) win_pthread_mutex_trylock((A))
38 | #define pthread_mutex_unlock(A) (LeaveCriticalSection(A), 0)
39 | #define pthread_mutex_destroy(A) (DeleteCriticalSection(A), 0)
40 |
41 |
42 | #else
43 | typedef pthread_t ThreadHandle;
44 | typedef pthread_t ThreadId;
45 | typedef pthread_t thid_t;
46 |
47 | typedef struct Thread
48 | {
49 | pthread_t os_handle;
50 | } Thread;
51 |
52 | #define SIGALRM 14
53 | #endif
54 |
55 | #define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */
56 |
57 | extern bool WaitThreadEnd(int n, Thread *th);
58 | extern void ThreadExit(int code);
59 | extern int ThreadCreate(Thread *th, void *(*start)(void *arg), void *arg);
60 |
61 | extern PGconn *pglogical_connect(const char *connstring, const char *connname);
62 | extern bool is_greenplum(PGconn *conn);
63 | extern size_t quote_literal_internal(char *dst, const char *src, size_t len);
64 | extern int start_copy_origin_tx(PGconn *conn, const char *snapshot, int pg_version, bool is_greenplum);
65 | extern int finish_copy_origin_tx(PGconn *conn);
66 | extern int start_copy_target_tx(PGconn *conn, int pg_version, bool is_greenplum);
67 | extern int finish_copy_target_tx(PGconn *conn);
68 | extern int ExecuteSqlStatement(PGconn *conn, const char *query);
69 | extern int setup_connection(PGconn *conn, int remoteVersion, bool is_greenplum);
70 |
71 | #endif
72 |
73 |
74 |
--------------------------------------------------------------------------------
/dbsync/my.cfg:
--------------------------------------------------------------------------------
1 | [src.mysql]
2 | host = "192.168.1.2"
3 | port = "3306"
4 | user = "test"
5 | password = "123456"
6 | db = "test"
7 | encodingdir = "share"
8 | encoding = "utf8"
9 | binlogfile = "mysql-bin.000001"
10 | binlogfile_offset = "0"
11 |
12 | [src.pgsql]
13 | connect_string = "host=192.168.1.1 dbname=test port=5432 user=gptest password=123456"
14 |
15 | [local.pgsql]
16 | connect_string = "host=192.168.1.1 dbname=test port=5433 user=gptest password=123456"
17 |
18 | [desc.pgsql]
19 | connect_string = "host=192.168.1.1 dbname=test port=5434 user=gptest password=123456"
20 | ignore_copy_error_count_each_table = "0"
21 | target_schema = "public"
22 |
23 | [binlogloader]
24 | loader_table_list = "loader_table_list.txt"
25 | load_batch = "10"
26 | load_batch_gap = "10"
27 |
--------------------------------------------------------------------------------
/dbsync/mysql2pgsql.c:
--------------------------------------------------------------------------------
1 | /*
2 | * mysql2pgsql.c
3 | *
4 | */
5 |
6 | #include "postgres_fe.h"
7 | #include "lib/stringinfo.h"
8 | #include "common/fe_memutils.h"
9 |
10 | #include "libpq-fe.h"
11 |
12 | #include "access/transam.h"
13 | #include "libpq/pqformat.h"
14 | #include "pqexpbuffer.h"
15 | #include "pgsync.h"
16 | #include "libpq/pqsignal.h"
17 | #include "catalog/pg_type.h"
18 |
19 | #include
20 |
21 | #ifndef WIN32
22 | #include
23 | #endif
24 |
25 | #include "mysql.h"
26 | #include "utils.h"
27 | #include
28 |
29 | static volatile bool time_to_abort = false;
30 | bool get_ddl_only = false;
31 | bool simple_wo_part = false;
32 | bool first_col_as_dist_key = false;
33 | /* buffer for sending copy data to target, unit is Byte */
34 | int buffer_size = 0;
35 |
36 | #define STMT_SHOW_TABLES "show full tables in `%s` where table_type='BASE TABLE'"
37 |
38 | #define STMT_SELECT "select * from `%s`.`%s`"
39 |
40 | static MYSQL *connect_to_mysql(mysql_conn_info* hd);
41 | static void *mysql2pgsql_copy_data(void *arg);
42 | static void quote_literal_local_withoid(StringInfo s, const char *rawstr, Oid type, PQExpBuffer buffer);
43 | static int setup_connection_from_mysql(PGconn *conn);
44 | static void sigint_handler(int signum);
45 |
46 | #ifndef WIN32
47 | static void
48 | sigint_handler(int signum)
49 | {
50 | time_to_abort = true;
51 | }
52 | #endif
53 |
54 | static Oid *
55 | fetch_colmum_info(char *schemaname, char *tabname, MYSQL_RES *my_res, bool is_target_gp)
56 | {
57 | MYSQL_FIELD *field;
58 | int col_num = 0;
59 | Oid *col_type = NULL;
60 | int i = 0;
61 | PQExpBuffer ddl;
62 | /* Mysql column name len should be no more than 64 */
63 | char first_col_name[256] = {0};
64 |
65 | ddl = createPQExpBuffer();
66 |
67 | if (!get_ddl_only)
68 | {
69 | appendPQExpBufferStr(ddl, "-- Reference DDL to create the target table:\n");
70 | }
71 |
72 | appendPQExpBuffer(ddl, "CREATE TABLE %s%s%s%s (",
73 | is_target_gp ? "" : "IF NOT EXISTS ", schemaname ? schemaname : "", schemaname ? "." : "", tabname);
74 |
75 | col_num = mysql_num_fields(my_res);
76 | col_type = palloc0(sizeof(Oid) * col_num);
77 | for (i = 0; i < col_num; i++)
78 | {
79 | int type;
80 |
81 | if (i == 0)
82 | {
83 | }
84 | else
85 | {
86 | appendPQExpBufferStr(ddl, ", ");
87 | }
88 |
89 | field = mysql_fetch_field(my_res);
90 |
91 | if (i == 0)
92 | {
93 | sprintf(first_col_name, "%s", field->org_name);
94 | }
95 |
96 | type = field->type;
97 | switch(type)
98 | {
99 | case MYSQL_TYPE_VARCHAR:
100 | case MYSQL_TYPE_VAR_STRING:
101 | case MYSQL_TYPE_STRING:
102 | case MYSQL_TYPE_BIT:
103 | case MYSQL_TYPE_BLOB:
104 | case MYSQL_TYPE_LONG_BLOB:
105 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "text");
106 | col_type[i] = TEXTOID;
107 | break;
108 |
109 | case MYSQL_TYPE_TIMESTAMP:
110 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "timestamptz");
111 | col_type[i] = TIMESTAMPTZOID;
112 | break;
113 | case MYSQL_TYPE_DATE:
114 | case MYSQL_TYPE_TIME:
115 | case MYSQL_TYPE_DATETIME:
116 | case MYSQL_TYPE_YEAR:
117 | case MYSQL_TYPE_NEWDATE:
118 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "timestamp");
119 | col_type[i] = TIMESTAMPOID;
120 | break;
121 |
122 | case MYSQL_TYPE_SHORT:
123 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "int2");
124 | col_type[i] = INT2OID;
125 | break;
126 |
127 | case MYSQL_TYPE_TINY:
128 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "int4");
129 | col_type[i] = INT4OID;
130 | break;
131 |
132 | case MYSQL_TYPE_LONG:
133 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "int4");
134 | col_type[i] = INT4OID;
135 | break;
136 |
137 | case MYSQL_TYPE_LONGLONG:
138 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "int8");
139 | col_type[i] = INT8OID;
140 | break;
141 |
142 | case MYSQL_TYPE_FLOAT:
143 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "float4");
144 | col_type[i] = FLOAT4OID;
145 | break;
146 |
147 | case MYSQL_TYPE_DOUBLE:
148 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "float8");
149 | col_type[i] = FLOAT8OID;
150 | break;
151 |
152 | case MYSQL_TYPE_DECIMAL:
153 | case MYSQL_TYPE_NEWDECIMAL:
154 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "numeric");
155 | col_type[i] = NUMERICOID;
156 | break;
157 | case MYSQL_TYPE_INT24:
158 | appendPQExpBuffer(ddl, "%s %s", field->org_name, "int4");
159 | col_type[i] = INT4OID;
160 | break;
161 |
162 | default:
163 | fprintf(stderr, "unsupported col %s type %d\n", field->org_name, type);
164 | return NULL;
165 | }
166 | }
167 |
168 | if (is_target_gp)
169 | {
170 | appendPQExpBuffer(ddl, ") with (APPENDONLY=true, ORIENTATION=column, COMPRESSTYPE=zlib, COMPRESSLEVEL=1, BLOCKSIZE=1048576, OIDS=false) DISTRIBUTED BY (%s)", first_col_as_dist_key ? first_col_name : "");
171 |
172 | if (!simple_wo_part)
173 | appendPQExpBuffer(ddl," PARTITION BY RANGE () (START (date '') INCLUSIVE END (date '') EXCLUSIVE EVERY (INTERVAL '<1 month>' ))");
174 | }
175 | else
176 | {
177 | appendPQExpBuffer(ddl, ")");
178 | }
179 |
180 | fprintf(stderr, "%s;\n\n", ddl->data);
181 |
182 | destroyPQExpBuffer(ddl);
183 |
184 | return col_type;
185 | }
186 |
187 |
188 | static MYSQL *
189 | connect_to_mysql(mysql_conn_info* hd)
190 | {
191 | int ret = -1;
192 | bool m_reConn = true;
193 |
194 | //my_init();
195 | //mysql_thread_init();
196 | MYSQL *m_mysqlConnection = mysql_init(NULL);
197 | if (hd->encoding != NULL)
198 | {
199 | ret = mysql_options(m_mysqlConnection, MYSQL_SET_CHARSET_NAME, hd->encoding);
200 | if (ret != 0)
201 | {
202 | fprintf(stderr, "set CHARSET_NAME to %s error: %s\n", hd->encoding, mysql_error(m_mysqlConnection));
203 | return NULL;
204 | }
205 | }
206 | if (hd->encodingdir != NULL)
207 | {
208 | ret = mysql_options(m_mysqlConnection, MYSQL_SET_CHARSET_DIR, hd->encodingdir);
209 | if (ret != 0)
210 | {
211 | fprintf(stderr, "set CHARSET_DIR to %s error: %s\n", hd->encodingdir, mysql_error(m_mysqlConnection));
212 | return NULL;
213 | }
214 | }
215 |
216 | if (1)
217 | {
218 | int opt_local_infile = 1;
219 | mysql_options(m_mysqlConnection, MYSQL_OPT_LOCAL_INFILE, (char*) &opt_local_infile);
220 | }
221 |
222 | // printf("set reconnect %s", m_reConn ? "true" : "false");
223 | ret = mysql_options(m_mysqlConnection, MYSQL_OPT_RECONNECT, &m_reConn);
224 | if (ret != 0)
225 | {
226 | fprintf(stderr, "set OPT_RECONNECT error: %s\n", mysql_error(m_mysqlConnection));
227 | return NULL;
228 | }
229 |
230 | if (! mysql_real_connect(m_mysqlConnection,
231 | hd->host,
232 | hd->user,
233 | hd->passwd,
234 | hd->db,
235 | hd->port,
236 | NULL,
237 | CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS))
238 | {
239 | fprintf(stderr, "connect error: %s\n", mysql_error(m_mysqlConnection));
240 |
241 | return NULL;
242 | }
243 |
244 | ret = mysql_query(m_mysqlConnection, "set unique_checks = 0;");
245 | if (ret != 0)
246 | {
247 | fprintf(stderr, "set unique_checks = 0 error: %s\n", mysql_error(m_mysqlConnection));
248 | return NULL;
249 | }
250 |
251 | // hd->conn_hd = m_mysqlConnection;
252 |
253 | return m_mysqlConnection;
254 | }
255 |
256 | /*
257 | * Entry point for mysql2pgsql
258 | */
259 | int
260 | mysql2pgsql_sync_main(char *desc, int nthread, mysql_conn_info *hd, char* target_schema, uint32 ignore_error_count)
261 | {
262 | int i = 0;
263 | Thread_hd th_hd;
264 | Thread *thread = NULL;
265 | PGresult *res = NULL;
266 | PGconn *desc_conn;
267 | long s_count = 0;
268 | long t_count = 0;
269 | bool have_err = false;
270 | TimevalStruct before,
271 | after;
272 | double elapsed_msec = 0;
273 | int ntask = 0;
274 | MYSQL *conn_src = NULL;
275 | MYSQL_RES *my_res = NULL;
276 | char **p = NULL;
277 |
278 | #ifndef WIN32
279 | signal(SIGINT, sigint_handler);
280 | #endif
281 |
282 | GETTIMEOFDAY(&before);
283 |
284 | memset(&th_hd, 0, sizeof(Thread_hd));
285 | th_hd.nth = nthread;
286 | th_hd.desc = desc;
287 | th_hd.mysql_src = hd;
288 | th_hd.ignore_error_count = ignore_error_count;
289 |
290 | conn_src = connect_to_mysql(hd);
291 | if (conn_src == NULL)
292 | {
293 | fprintf(stderr, "init src conn failed.\n");
294 | return 1;
295 | }
296 |
297 | desc_conn = pglogical_connect(desc, EXTENSION_NAME "_main");
298 | if (desc_conn == NULL)
299 | {
300 | fprintf(stderr, "init desc conn failed: %s\n", PQerrorMessage(desc_conn));
301 | return 1;
302 | }
303 | th_hd.desc_version = PQserverVersion(desc_conn);
304 | th_hd.desc_is_greenplum = is_greenplum(desc_conn);
305 | PQfinish(desc_conn);
306 |
307 | if (hd->tabnames == NULL)
308 | {
309 | PQExpBuffer query;
310 | MYSQL_ROW row;
311 | int ret = -1;
312 |
313 | query = createPQExpBuffer();
314 |
315 | appendPQExpBuffer(query, STMT_SHOW_TABLES, hd->db);
316 | ret = mysql_real_query(conn_src, query->data, strlen(query->data));
317 | if (ret != 0)
318 | {
319 | fprintf(stderr, "init desc conn failed: %s", mysql_error(conn_src));
320 | return 1;
321 | }
322 |
323 | my_res = mysql_store_result(conn_src);
324 | if (my_res == NULL)
325 | {
326 | fprintf(stderr, "get src db table failed: %s", mysql_error(conn_src));
327 | return 1;
328 | }
329 |
330 | ntask = mysql_num_rows(my_res);
331 | th_hd.ntask = ntask;
332 | if (th_hd.ntask >= 1)
333 | {
334 | th_hd.task = (Task_hd *)palloc0(sizeof(Task_hd) * th_hd.ntask);
335 | }
336 |
337 | /*
338 | * The linked-array th_hd.task serves as a task queue for all the worker threads to pick up tasks and consume
339 | */
340 | for (i = 0; i < th_hd.ntask; i++)
341 | {
342 | row = mysql_fetch_row(my_res);
343 | th_hd.task[i].id = i;
344 | th_hd.task[i].schemaname = target_schema;
345 | th_hd.task[i].relname = pstrdup(row[0]);
346 | th_hd.task[i].query = NULL;
347 | th_hd.task[i].count = 0;
348 | th_hd.task[i].complete = false;
349 |
350 | /* Set the former entry's link to this entry. Last entry's next feild would remain NULL */
351 | if (i != 0)
352 | {
353 | th_hd.task[i-1].next = &th_hd.task[i];
354 | }
355 | }
356 | mysql_free_result(my_res);
357 | PQclear(res);
358 | destroyPQExpBuffer(query);
359 | }
360 | else
361 | {
362 | for (i = 0, p = hd->tabnames; *p != NULL; p++, i++)
363 | {
364 | }
365 |
366 | ntask = i;
367 | th_hd.ntask = ntask;
368 | th_hd.task = (Task_hd *)palloc0(sizeof(Task_hd) * th_hd.ntask);
369 |
370 | for (i = 0, p = hd->tabnames; *p != NULL; p++, i++)
371 | {
372 | th_hd.task[i].id = i;
373 | th_hd.task[i].schemaname = target_schema;
374 | th_hd.task[i].relname = *p;
375 | th_hd.task[i].query = hd->queries[i];
376 |
377 | th_hd.task[i].count = 0;
378 | th_hd.task[i].complete = false;
379 | /* Set the former entry's link to this entry. Last entry's next feild would remain NULL */
380 | if (i != 0)
381 | {
382 | th_hd.task[i-1].next = &th_hd.task[i];
383 | }
384 | }
385 | }
386 |
387 |
388 | th_hd.l_task = &(th_hd.task[0]);
389 |
390 | th_hd.th = (ThreadArg *)palloc0(sizeof(ThreadArg) * th_hd.nth);
391 | for (i = 0; i < th_hd.nth; i++)
392 | {
393 | th_hd.th[i].id = i;
394 | th_hd.th[i].count = 0;
395 | th_hd.th[i].all_ok = false;
396 |
397 | th_hd.th[i].hd = &th_hd;
398 | }
399 | pthread_mutex_init(&th_hd.t_lock, NULL);
400 |
401 | if (!get_ddl_only)
402 | {
403 | fprintf(stderr, "Starting data sync\n");
404 | }
405 |
406 | thread = (Thread *)palloc0(sizeof(Thread) * th_hd.nth);
407 | for (i = 0; i < th_hd.nth; i++)
408 | {
409 | ThreadCreate(&thread[i], mysql2pgsql_copy_data, &th_hd.th[i]);
410 | }
411 |
412 | WaitThreadEnd(th_hd.nth, thread);
413 |
414 | GETTIMEOFDAY(&after);
415 | DIFF_MSEC(&after, &before, elapsed_msec);
416 |
417 | for (i = 0; i < th_hd.nth; i++)
418 | {
419 | if(th_hd.th[i].all_ok)
420 | {
421 | s_count += th_hd.th[i].count;
422 | }
423 | else
424 | {
425 | have_err = true;
426 | }
427 | }
428 |
429 | for (i = 0; i < ntask; i++)
430 | {
431 | t_count += th_hd.task[i].count;
432 | }
433 |
434 | if (!get_ddl_only)
435 | {
436 | fprintf(stderr, "Number of rows migrated: %ld (number of source tables' rows: %ld) \n", s_count, t_count);
437 | fprintf(stderr, "Data sync time cost %.3f ms\n", elapsed_msec);
438 | }
439 | else
440 | {
441 | fprintf(stderr, "-- Number of tables: %d \n", ntask);
442 | }
443 |
444 | if (have_err)
445 | {
446 | fprintf(stderr, "errors occured during migration\n");
447 | }
448 |
449 | return 0;
450 | }
451 |
452 | static void *
453 | mysql2pgsql_copy_data(void *arg)
454 | {
455 | ThreadArg *args = (ThreadArg *)arg;
456 | Thread_hd *hd = args->hd;
457 | MYSQL_RES *my_res;
458 | PGresult *res2;
459 | PQExpBuffer query;
460 | char *nspname;
461 | char *relname;
462 | Task_hd *curr = NULL;
463 | TimevalStruct before,
464 | after;
465 | double elapsed_msec = 0;
466 | MYSQL *origin_conn = NULL;
467 | PGconn *target_conn = NULL;
468 | int ret = -1;
469 | MYSQL_ROW row;
470 | Oid *column_oids = NULL;
471 | StringInfoData s_tmp;
472 | int i = 0;
473 | int target_version;
474 | bool isgp = false;
475 |
476 | initStringInfo(&s_tmp);
477 | origin_conn = connect_to_mysql(hd->mysql_src);
478 | if (origin_conn == NULL)
479 | {
480 | fprintf(stderr, "init src conn failed");
481 | return NULL;
482 | }
483 |
484 | target_conn = pglogical_connect(hd->desc, EXTENSION_NAME "_copy");
485 | if (target_conn == NULL)
486 | {
487 | fprintf(stderr, "init desc conn failed: %s", PQerrorMessage(target_conn));
488 | return NULL;
489 | }
490 | target_version = PQserverVersion(target_conn);
491 | isgp = is_greenplum(target_conn);
492 | setup_connection(target_conn, target_version, isgp);
493 | setup_connection_from_mysql(target_conn);
494 |
495 | query = createPQExpBuffer();
496 |
497 | if (get_ddl_only)
498 | fprintf(stderr, "\n-- Reference commands to create target tables %s: \n---------------\n\n",
499 | isgp ? "(Please choose a distribution key and replace it with for each table)" : "");
500 | while(1)
501 | {
502 | int nlist = 0;
503 | int n_col = 0;
504 | int row_count = 0;
505 |
506 | GETTIMEOFDAY(&before);
507 | pthread_mutex_lock(&hd->t_lock);
508 | nlist = hd->ntask;
509 | if (nlist == 1)
510 | {
511 | curr = hd->l_task;
512 | hd->l_task = NULL;
513 | hd->ntask = 0;
514 | }
515 | else if (nlist > 1)
516 | {
517 | Task_hd *tmp = hd->l_task->next;
518 | curr = hd->l_task;
519 | hd->l_task = tmp;
520 | hd->ntask--;
521 | }
522 | else
523 | {
524 | curr = NULL;
525 | }
526 |
527 | pthread_mutex_unlock(&hd->t_lock);
528 |
529 | if(curr == NULL)
530 | {
531 | break;
532 | }
533 |
534 | if (!get_ddl_only)
535 | start_copy_target_tx(target_conn, hd->desc_version, hd->desc_is_greenplum);
536 |
537 | nspname = hd->mysql_src->db;
538 | relname = curr->relname;
539 |
540 | //fprintf(stderr, "relname %s, query %s \n", relname, curr->query ? curr->query : "");
541 |
542 | if (curr && curr->query)
543 | {
544 | appendPQExpBufferStr(query, curr->query);
545 | }
546 | else
547 | {
548 | appendPQExpBuffer(query, STMT_SELECT,
549 | nspname,
550 | relname);
551 | }
552 |
553 | if (get_ddl_only)
554 | {
555 | appendPQExpBufferStr(query, " limit 1 ");
556 | }
557 | else
558 | {
559 | fprintf(stderr, "Query to get source data for target table %s: %s \n", relname, query->data);
560 | }
561 |
562 | ret = mysql_query(origin_conn, query->data);
563 | if (ret != 0)
564 | {
565 | fprintf(stderr, "run query error: %s\n", mysql_error(origin_conn));
566 | goto exit;
567 | }
568 | my_res = mysql_use_result(origin_conn);
569 | column_oids = fetch_colmum_info(curr->schemaname, relname, my_res, isgp);
570 | if (column_oids == NULL)
571 | {
572 | fprintf(stderr, "get table %s column type error\n", relname);
573 | goto exit;
574 | }
575 |
576 | if (get_ddl_only)
577 | {
578 | curr->complete = true;
579 | mysql_free_result(my_res);
580 | resetPQExpBuffer(query);
581 | continue;
582 | }
583 |
584 | n_col = mysql_num_fields(my_res);
585 |
586 | resetPQExpBuffer(query);
587 | appendPQExpBuffer(query, "COPY %s%s%s FROM stdin DELIMITERS '|' with csv QUOTE ''''",
588 | curr->schemaname ? PQescapeIdentifier(target_conn, curr->schemaname, strlen(curr->schemaname)) : "", curr->schemaname ? "." : "",
589 | PQescapeIdentifier(target_conn, relname,
590 | strlen(relname)));
591 |
592 | if (isgp && hd->ignore_error_count > 0)
593 | {
594 | appendPQExpBuffer(query, " SEGMENT REJECT LIMIT %u",
595 | hd->ignore_error_count);
596 |
597 | }
598 |
599 | res2 = PQexec(target_conn, query->data);
600 | if (PQresultStatus(res2) != PGRES_COPY_IN)
601 | {
602 | fprintf(stderr,"table copy failed Query '%s': %s",
603 | query->data, PQerrorMessage(target_conn));
604 | goto exit;
605 | }
606 |
607 |
608 | resetPQExpBuffer(query);
609 | while ((row = mysql_fetch_row(my_res)) != NULL)
610 | {
611 | unsigned long *lengths;
612 |
613 | lengths = mysql_fetch_lengths(my_res);
614 | for (i = 0; i < n_col; i++)
615 | {
616 | if (i != 0)
617 | {
618 | appendPQExpBufferStr(query, "|");
619 | }
620 |
621 | /* value of the field is NULL if it is fact NULL */
622 | if(lengths[i] >= 0 && row[i] != NULL)
623 | {
624 | quote_literal_local_withoid(&s_tmp, row[i], column_oids[i], query);
625 | }
626 | }
627 |
628 | appendPQExpBufferStr(query, "\n");
629 | row_count++;
630 |
631 | if (query->len >= buffer_size)
632 | {
633 | if (PQputCopyData(target_conn, query->data, query->len) != 1)
634 | {
635 | fprintf(stderr,"writing to target table failed destination connection reported: %s",
636 | PQerrorMessage(target_conn));
637 | goto exit;
638 | }
639 |
640 | /* Reset buffer for next use */
641 | resetPQExpBuffer(query);
642 | }
643 |
644 | if (time_to_abort)
645 | {
646 | args->count = row_count;
647 | curr->count = row_count;
648 | fprintf(stderr, "receive shutdown sigint\n");
649 | goto exit;
650 | }
651 | }
652 |
653 | if (query->len > 0)
654 | {
655 | if (PQputCopyData(target_conn, query->data, query->len) != 1)
656 | {
657 | fprintf(stderr,"writing to target table failed destination connection reported: %s",
658 | PQerrorMessage(target_conn));
659 | goto exit;
660 | }
661 |
662 | resetPQExpBuffer(query);
663 | }
664 |
665 | args->count = row_count;
666 | curr->count = row_count;
667 |
668 | /* Send local finish */
669 | if (PQputCopyEnd(target_conn, NULL) != 1)
670 | {
671 | fprintf(stderr,"sending copy-completion to destination connection failed destination connection reported: %s",
672 | PQerrorMessage(target_conn));
673 | goto exit;
674 | }
675 |
676 | PQclear(res2);
677 | res2 = PQgetResult(target_conn);
678 | if (PQresultStatus(res2) != PGRES_COMMAND_OK)
679 | {
680 | fprintf(stderr, "COPY failed for table \"%s\": %s",
681 | relname, PQerrorMessage(target_conn));
682 | goto exit;
683 | }
684 |
685 | finish_copy_target_tx(target_conn);
686 | curr->complete = true;
687 | PQclear(res2);
688 | resetPQExpBuffer(query);
689 | mysql_free_result(my_res);
690 |
691 | GETTIMEOFDAY(&after);
692 | DIFF_MSEC(&after, &before, elapsed_msec);
693 | fprintf(stderr,"thread %d migrate task %d table %s.%s %ld rows complete, time cost %.3f ms\n",
694 | args->id, curr->id, nspname, relname, curr->count, elapsed_msec);
695 | }
696 |
697 | args->all_ok = true;
698 |
699 | if (get_ddl_only)
700 | fprintf(stderr, "---------------\n\n");
701 |
702 | exit:
703 |
704 | mysql_close(origin_conn);
705 | PQfinish(target_conn);
706 | ThreadExit(0);
707 | return NULL;
708 | }
709 |
710 | static void
711 | quote_literal_local_withoid(StringInfo s, const char *rawstr, Oid type, PQExpBuffer buffer)
712 | {
713 | char *result;
714 | int len;
715 | int newlen;
716 | bool need_process = true;
717 |
718 | if (INT2OID == type)
719 | need_process = false;
720 | else if (INT4OID == type)
721 | need_process = false;
722 | else if (INT8OID == type)
723 | need_process = false;
724 | else if (FLOAT4OID == type)
725 | need_process = false;
726 | else if (FLOAT8OID == type)
727 | need_process = false;
728 | else if (NUMERICOID == type)
729 | need_process = false;
730 | else if (TIMESTAMPOID == type)
731 | {
732 | if (strcmp(rawstr, "0000-00-00") == 0 ||
733 | strcmp(rawstr, "00:00:00") == 0 ||
734 | strcmp(rawstr, "0000-00-00 00:00:00") == 0 ||
735 | strcmp(rawstr, "0000") == 0)
736 | {
737 | return;
738 | }
739 |
740 | need_process = false;
741 | }
742 |
743 | if (need_process == false)
744 | {
745 | appendPQExpBuffer(buffer, "%s", rawstr);
746 | return;
747 | }
748 |
749 | /*if (TIMESTAMPOID == type)
750 | need_process = false;
751 |
752 | if (need_process == false)
753 | {
754 | appendPQExpBuffer(buffer, "'");
755 | appendPQExpBuffer(buffer, "%s", rawstr);
756 | appendPQExpBuffer(buffer, "'");
757 | return;
758 | }*/
759 |
760 | len = strlen(rawstr);
761 | resetStringInfo(s);
762 | appendStringInfoSpaces(s, len * 2 + 3);
763 |
764 | result = s->data;
765 |
766 | newlen = quote_literal_internal(result, rawstr, len);
767 | result[newlen] = '\0';
768 |
769 | appendPQExpBufferStr(buffer, result);
770 |
771 | return;
772 | }
773 |
774 | static int
775 | setup_connection_from_mysql(PGconn *conn)
776 | {
777 | ExecuteSqlStatement(conn, "SET standard_conforming_strings TO off");
778 | ExecuteSqlStatement(conn, "SET backslash_quote TO on");
779 |
780 | return 0;
781 | }
782 |
783 |
--------------------------------------------------------------------------------
/dbsync/pg_logicaldecode.c:
--------------------------------------------------------------------------------
1 | /* -------------------------------------------------------------------------
2 | *
3 | * pg_logicaldecode.c
4 | * Replication
5 | *
6 | * Replication
7 | *
8 | * Copyright (C) 2012-2015, Alibaba Group
9 | *
10 | * IDENTIFICATION
11 | * pg_logicaldecode.c
12 | *
13 | * -------------------------------------------------------------------------
14 | */
15 | #include "postgres_fe.h"
16 | #include "lib/stringinfo.h"
17 | #include "common/fe_memutils.h"
18 |
19 | #include "libpq-fe.h"
20 |
21 | #include "access/transam.h"
22 | #include "libpq/pqformat.h"
23 | #include "pg_logicaldecode.h"
24 | #include "pqexpbuffer.h"
25 |
26 | #include
27 |
28 |
29 |
30 | static bool process_remote_begin(StringInfo s, ALI_PG_DECODE_MESSAGE *msg);
31 | static bool process_remote_commit(StringInfo s, ALI_PG_DECODE_MESSAGE *msg);
32 | static bool process_remote_insert(StringInfo s, ALI_PG_DECODE_MESSAGE *msg);
33 | static bool process_remote_update(StringInfo s, ALI_PG_DECODE_MESSAGE *msg);
34 | static bool process_remote_delete(StringInfo s, ALI_PG_DECODE_MESSAGE *msg);
35 | static bool read_tuple_parts(StringInfo s, Decode_TupleData *tup);
36 | static bool process_read_colunm_info(StringInfo s, ALI_PG_DECODE_MESSAGE *msg);
37 |
38 |
39 |
40 | /*
41 | * Read a remote action type and process the action record.
42 | *
43 | * May set got_SIGTERM to stop processing before next record.
44 | */
45 | bool
46 | bdr_process_remote_action(StringInfo s, ALI_PG_DECODE_MESSAGE *msg)
47 | {
48 | char action = pq_getmsgbyte(s);
49 | bool rc = false;
50 |
51 | switch (action)
52 | {
53 | /* BEGIN */
54 | case 'B':
55 | rc = process_remote_begin(s,msg);
56 | break;
57 | /* COMMIT */
58 | case 'C':
59 | rc = process_remote_commit(s,msg);
60 | break;
61 | /* INSERT */
62 | case 'I':
63 | rc = process_remote_insert(s,msg);
64 | break;
65 | /* UPDATE */
66 | case 'U':
67 | rc = process_remote_update(s,msg);
68 | break;
69 | /* DELETE */
70 | case 'D':
71 | rc = process_remote_delete(s,msg);
72 | break;
73 | default:
74 | {
75 | fprintf(stderr, "unknown action of type %c", action);
76 | return false;
77 | }
78 | }
79 |
80 | return true;
81 | }
82 |
83 | static bool
84 | process_remote_begin(StringInfo s, ALI_PG_DECODE_MESSAGE *msg)
85 | {
86 | XLogRecPtr origlsn;
87 | TimestampTz committime;
88 | TransactionId remote_xid;
89 | int flags = 0;
90 |
91 | flags = pq_getmsgint(s, 4);
92 |
93 | origlsn = pq_getmsgint64(s);
94 | Assert(origlsn != InvalidXLogRecPtr);
95 | committime = pq_getmsgint64(s);
96 | remote_xid = pq_getmsgint(s, 4);
97 |
98 | msg->type = MSGKIND_BEGIN;
99 | msg->lsn = origlsn;
100 | msg->tm = committime;
101 | msg->xid = remote_xid;
102 |
103 | return true;
104 | }
105 |
106 | /*
107 | * Process a commit message from the output plugin, advance replication
108 | * identifiers, commit the local transaction, and determine whether replay
109 | * should continue.
110 | *
111 | * Returns true if apply should continue with the next record, false if replay
112 | * should stop after this record.
113 | */
114 | static bool
115 | process_remote_commit(StringInfo s, ALI_PG_DECODE_MESSAGE *msg)
116 | {
117 | XLogRecPtr commit_lsn;
118 | TimestampTz committime;
119 | TimestampTz end_lsn;
120 | int flags;
121 |
122 | flags = pq_getmsgint(s, 4);
123 |
124 | if (flags != 0)
125 | {
126 | fprintf(stderr, "Commit flags are currently unused, but flags was set to %i", flags);
127 | return false;
128 | }
129 |
130 | /* order of access to fields after flags is important */
131 | commit_lsn = pq_getmsgint64(s);
132 | end_lsn = pq_getmsgint64(s);
133 | committime = pq_getmsgint64(s);
134 |
135 | msg->type = MSGKIND_COMMIT;
136 | msg->lsn = commit_lsn;
137 | msg->tm = committime;
138 | msg->end_lsn = end_lsn;
139 |
140 | return true;
141 | }
142 |
143 | static bool
144 | process_remote_insert(StringInfo s, ALI_PG_DECODE_MESSAGE *msg)
145 | {
146 | char action;
147 | int relnamelen;
148 | int nspnamelen;
149 | char *schemaname;
150 | char *relname;
151 |
152 | msg->type = MSGKIND_INSERT;
153 |
154 | nspnamelen = pq_getmsgint(s, 2);
155 | schemaname = (char *) pq_getmsgbytes(s, nspnamelen);
156 |
157 | relnamelen = pq_getmsgint(s, 2);
158 | relname = (char *) pq_getmsgbytes(s, relnamelen);
159 |
160 | msg->relname = relname;
161 | msg->schemaname = schemaname;
162 |
163 | action = pq_getmsgbyte(s);
164 | if (action != 'N' && action != 'C')
165 | {
166 | fprintf(stderr, "expected new tuple but got %d",
167 | action);
168 | return false;
169 | }
170 |
171 | if (action == 'C')
172 | {
173 | process_read_colunm_info(s, msg);
174 | action = pq_getmsgbyte(s);
175 | }
176 |
177 | if (action != 'N')
178 | {
179 | fprintf(stderr, "expected new tuple but got %d",
180 | action);
181 | return false;
182 | }
183 |
184 | return read_tuple_parts(s, &msg->newtuple);
185 | }
186 |
187 | static bool
188 | process_remote_update(StringInfo s, ALI_PG_DECODE_MESSAGE *msg)
189 | {
190 | char action;
191 | bool pkey_sent;
192 | int relnamelen;
193 | int nspnamelen;
194 | char *schemaname;
195 | char *relname;
196 |
197 | msg->type = MSGKIND_UPDATE;
198 |
199 | nspnamelen = pq_getmsgint(s, 2);
200 | schemaname = (char *) pq_getmsgbytes(s, nspnamelen);
201 |
202 | relnamelen = pq_getmsgint(s, 2);
203 | relname = (char *) pq_getmsgbytes(s, relnamelen);
204 |
205 | msg->relname = relname;
206 | msg->schemaname = schemaname;
207 |
208 | action = pq_getmsgbyte(s);
209 |
210 | /* old key present, identifying key changed */
211 | if (action != 'K' && action != 'N' && action != 'C')
212 | {
213 | fprintf(stderr, "expected action 'N' or 'K', got %c",
214 | action);
215 | return false;
216 | }
217 |
218 | if (action == 'C')
219 | {
220 | process_read_colunm_info(s, msg);
221 | action = pq_getmsgbyte(s);
222 | }
223 |
224 | if (action != 'K' && action != 'N')
225 | {
226 | fprintf(stderr, "expected action 'N' or 'K', got %c",
227 | action);
228 | return false;
229 | }
230 |
231 | if (action == 'K')
232 | {
233 | pkey_sent = true;
234 | msg->has_key_or_old = true;
235 | read_tuple_parts(s, &msg->oldtuple);
236 | action = pq_getmsgbyte(s);
237 | }
238 | else
239 | pkey_sent = false;
240 |
241 | /* check for new tuple */
242 | if (action != 'N')
243 | {
244 | fprintf(stderr, "expected action 'N', got %c",
245 | action);
246 | return false;
247 | }
248 |
249 | /* read new tuple */
250 | return read_tuple_parts(s, &msg->newtuple);
251 | }
252 |
253 | static bool
254 | process_read_colunm_info(StringInfo s, ALI_PG_DECODE_MESSAGE *msg)
255 | {
256 | int natt;
257 | int i;
258 | char action;
259 |
260 | natt = pq_getmsgint(s, 2);
261 |
262 | msg->natt = natt;
263 | for (i = 0; i < natt; i++)
264 | {
265 | char *tmp;
266 | int len;
267 |
268 | len = pq_getmsgint(s, 2);
269 | if(len == 0)
270 | {
271 | continue;
272 | }
273 |
274 | tmp = (char *) pq_getmsgbytes(s, len);
275 |
276 | msg->attname[i] = tmp;
277 |
278 | {
279 | len = pq_getmsgint(s, 2);
280 | tmp = (char *) pq_getmsgbytes(s, len);
281 | msg->atttype[i] = tmp;
282 | }
283 | }
284 |
285 | action = pq_getmsgbyte(s);
286 |
287 | if (action != 'M' && action != 'P')
288 | {
289 | fprintf(stderr, "expected new tuple but got %d",
290 | action);
291 | return false;
292 | }
293 |
294 | if (action == 'P')
295 | {
296 | return true;
297 | }
298 |
299 | natt = pq_getmsgint(s, 2);
300 |
301 | msg->k_natt = natt;
302 | for (i = 0; i < natt; i++)
303 | {
304 | char *tmp;
305 | int len;
306 |
307 | len = pq_getmsgint(s, 2);
308 | tmp = (char *) pq_getmsgbytes(s, len);
309 |
310 | msg->k_attname[i] = tmp;
311 | }
312 |
313 | return true;
314 | }
315 |
316 |
317 | static bool
318 | process_remote_delete(StringInfo s, ALI_PG_DECODE_MESSAGE *msg)
319 | {
320 | char action;
321 | int relnamelen;
322 | int nspnamelen;
323 | char *schemaname;
324 | char *relname;
325 |
326 | msg->type = MSGKIND_DELETE;
327 |
328 | nspnamelen = pq_getmsgint(s, 2);
329 | schemaname = (char *) pq_getmsgbytes(s, nspnamelen);
330 |
331 | relnamelen = pq_getmsgint(s, 2);
332 | relname = (char *) pq_getmsgbytes(s, relnamelen);
333 |
334 | msg->relname = relname;
335 | msg->schemaname = schemaname;
336 |
337 | action = pq_getmsgbyte(s);
338 |
339 | if (action != 'K' && action != 'E' && action != 'C')
340 | {
341 | fprintf(stderr, "expected action K or E got %c", action);
342 | return false;
343 | }
344 |
345 | if (action == 'C')
346 | {
347 | process_read_colunm_info(s, msg);
348 | action = pq_getmsgbyte(s);
349 | }
350 |
351 | if (action != 'K' && action != 'E')
352 | {
353 | fprintf(stderr, "expected action K or E got %c", action);
354 | return false;
355 | }
356 |
357 | if (action == 'E')
358 | {
359 | fprintf(stderr, "got delete without pkey\n");
360 | return true;
361 | }
362 |
363 | msg->has_key_or_old = true;
364 | return read_tuple_parts(s, &msg->oldtuple);
365 |
366 | }
367 |
368 | static bool
369 | read_tuple_parts(StringInfo s, Decode_TupleData *tup)
370 | {
371 | int i;
372 | int rnatts;
373 | char action;
374 |
375 | action = pq_getmsgbyte(s);
376 |
377 | if (action != 'T')
378 | {
379 | fprintf(stderr, "expected TUPLE, got %c", action);
380 | return false;
381 | }
382 |
383 | memset(tup->isnull, 1, sizeof(tup->isnull));
384 | memset(tup->changed, 1, sizeof(tup->changed));
385 |
386 | rnatts = pq_getmsgint(s, 4);
387 |
388 | tup->natt = rnatts;
389 |
390 | /* FIXME: unaligned data accesses */
391 | for (i = 0; i < rnatts; i++)
392 | {
393 | char kind = pq_getmsgbyte(s);
394 | //const char *data;
395 | int len;
396 |
397 | switch (kind)
398 | {
399 | case 'n':
400 | tup->svalues[i] = NULL;
401 | break;
402 | case 'u':
403 | tup->isnull[i] = true;
404 | tup->changed[i] = false;
405 | tup->svalues[i] = NULL;
406 |
407 | break;
408 | /*
409 | case 'b':
410 | tup->isnull[i] = false;
411 | len = pq_getmsgint(s, 4);
412 |
413 | data = pq_getmsgbytes(s, len);
414 |
415 | tup->svalues[i] = palloc0(len + 1);
416 | memcpy(tup->svalues[i], data, len);
417 |
418 | break;
419 | case 's':
420 | {
421 | StringInfoData buf;
422 |
423 | tup->isnull[i] = false;
424 | len = pq_getmsgint(s, 4);
425 |
426 | initStringInfo(&buf);
427 | buf.data = (char *) pq_getmsgbytes(s, len);
428 | buf.len = len;
429 |
430 | tup->svalues[i] = palloc0(len + 1);
431 | memcpy(tup->svalues[i], buf.data, len);
432 |
433 | break;
434 | }
435 | */
436 | case 't':
437 | {
438 | tup->isnull[i] = false;
439 | len = pq_getmsgint(s, 4);
440 |
441 | tup->svalues[i] = (char *) pq_getmsgbytes(s, len);
442 | //tup->svalues[i] = palloc0(len + 1);
443 | //memcpy(tup->svalues[i], data, len);
444 | }
445 | break;
446 | default:
447 | {
448 | fprintf(stderr, "unknown column type '%c'", kind);
449 | return false;
450 | }
451 | }
452 |
453 | }
454 |
455 | return true;
456 | }
457 |
458 |
--------------------------------------------------------------------------------
/dbsync/pg_logicaldecode.h:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ifndef PG_LOGICALDECODE_H
4 | #define PG_LOGICALDECODE_H
5 |
6 | #include "postgres_fe.h"
7 |
8 | #include "lib/stringinfo.h"
9 | #include "lib/stringinfo.h"
10 | #include "common/fe_memutils.h"
11 |
12 | #include "libpq-fe.h"
13 |
14 | #include "access/transam.h"
15 | #include "libpq/pqformat.h"
16 | #include "pqexpbuffer.h"
17 | #include "misc.h"
18 |
19 | #ifdef __cplusplus
20 | extern "C"
21 | {
22 | #endif
23 |
24 | /*
25 | * Don't include libpq here, msvc infrastructure requires linking to libpq
26 | * otherwise.
27 | */
28 | struct pg_conn;
29 |
30 | #ifdef HAVE_INT64_TIMESTAMP
31 | typedef int64 timestamp;
32 | typedef int64 TimestampTz;
33 | #else
34 | typedef double timestamp;
35 | typedef double TimestampTz;
36 | #endif
37 |
38 | #define MaxTupleAttributeNumber 1664 /* 8 * 208 */
39 |
40 | #define MSGKIND_BEGIN 'B'
41 | #define MSGKIND_COMMIT 'C'
42 | #define MSGKIND_INSERT 'I'
43 | #define MSGKIND_UPDATE 'U'
44 | #define MSGKIND_DELETE 'D'
45 | #define MSGKIND_DDL 'L'
46 | #define MSGKIND_UNKNOWN 'K'
47 |
48 | typedef struct Decode_TupleData
49 | {
50 | int natt;
51 | bool isnull[MaxTupleAttributeNumber];
52 | bool changed[MaxTupleAttributeNumber];
53 | char *svalues[MaxTupleAttributeNumber];
54 | } Decode_TupleData;
55 |
56 | typedef struct ALI_PG_DECODE_MESSAGE
57 | {
58 | char type;
59 |
60 | XLogRecPtr lsn;
61 | TimestampTz tm;
62 |
63 | TransactionId xid; /* begin */
64 | TimestampTz end_lsn; /* commit */
65 |
66 | char *relname;
67 | char *schemaname;
68 |
69 | int natt;
70 | char *attname[MaxTupleAttributeNumber];
71 | char *atttype[MaxTupleAttributeNumber];
72 |
73 | int k_natt;
74 | char *k_attname[MaxTupleAttributeNumber];
75 |
76 | bool has_key_or_old;
77 | Decode_TupleData newtuple;
78 | Decode_TupleData oldtuple;
79 |
80 | } ALI_PG_DECODE_MESSAGE;
81 |
82 | typedef struct Decoder_handler
83 | {
84 | bool do_create_slot;
85 | bool do_start_slot;
86 | bool do_drop_slot;
87 |
88 | PGconn *conn;
89 | char *connection_string;
90 |
91 | char *progname;
92 | char *replication_slot;
93 |
94 | int outfd;
95 | char *outfile;
96 |
97 | XLogRecPtr startpos;
98 |
99 | XLogRecPtr recvpos;
100 | XLogRecPtr flushpos;
101 | XLogRecPtr last_recvpos;
102 |
103 | ALI_PG_DECODE_MESSAGE msg;
104 |
105 | int verbose;
106 |
107 | char *copybuf;
108 | int standby_message_timeout;
109 | int64 last_status;
110 |
111 | StringInfo buffer;
112 | } Decoder_handler;
113 |
114 |
115 | /* Error level codes */
116 | #define DEBUG5 10 /* Debugging messages, in categories of
117 | * decreasing detail. */
118 | #define DEBUG4 11
119 | #define DEBUG3 12
120 | #define DEBUG2 13
121 | #define DEBUG1 14 /* used by GUC debug_* variables */
122 | #define LOG 15 /* Server operational messages; sent only to
123 | * server log by default. */
124 | #define COMMERROR 16 /* Client communication problems; same as LOG
125 | * for server reporting, but never sent to
126 | * client. */
127 | #define INFO 17 /* Messages specifically requested by user (eg
128 | * VACUUM VERBOSE output); always sent to
129 | * client regardless of client_min_messages,
130 | * but by default not sent to server log. */
131 | #define NOTICE 18 /* Helpful messages to users about query
132 | * operation; sent to client and server log by
133 | * default. */
134 | #define WARNING 19 /* Warnings. NOTICE is for expected messages
135 | * like implicit sequence creation by SERIAL.
136 | * WARNING is for unexpected messages. */
137 | #define ERROR 20 /* user error - abort transaction; return to
138 | * known state */
139 |
140 |
141 | #ifdef HAVE_FUNCNAME__FUNC
142 | #define PG_FUNCNAME_MACRO __func__
143 | #else
144 | #ifdef HAVE_FUNCNAME__FUNCTION
145 | #define PG_FUNCNAME_MACRO __FUNCTION__
146 | #else
147 | #define PG_FUNCNAME_MACRO NULL
148 | #endif
149 | #endif
150 |
151 | extern bool bdr_process_remote_action(StringInfo s, ALI_PG_DECODE_MESSAGE *msg);
152 |
153 | /*
154 | #define elog \
155 | elog_start(__FILE__, __LINE__, PG_FUNCNAME_MACRO), \
156 | elog_finish
157 |
158 | extern void elog_start(const char *filename, int lineno, const char *funcname);
159 | extern void
160 | elog_finish(int elevel, const char *fmt,...)
161 | __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
162 | */
163 |
164 |
165 | extern char *timestamptz_to_str(TimestampTz dt);
166 | extern int64 timestamptz_to_time_t(TimestampTz t);
167 | extern void out_put_tuple(ALI_PG_DECODE_MESSAGE *msg, PQExpBuffer buffer, Decode_TupleData *tuple);
168 | extern int out_put_tuple_to_sql(Decoder_handler *hander, ALI_PG_DECODE_MESSAGE *msg, PQExpBuffer buffer);
169 | extern void out_put_key_att(ALI_PG_DECODE_MESSAGE *msg, PQExpBuffer buffer);
170 | extern void out_put_decode_message(Decoder_handler *hander, ALI_PG_DECODE_MESSAGE *msg, int outfd);
171 | extern bool sendFeedback(Decoder_handler *hander, int64 now, bool force, bool replyRequested);
172 | extern int initialize_connection(Decoder_handler *hander);
173 | extern void disconnect(Decoder_handler *hander);
174 | extern int check_handler_parameters(Decoder_handler *hander);
175 | extern char *create_replication_slot(Decoder_handler *hander,XLogRecPtr *lsn, char *replication_slot);
176 | extern int drop_replication_slot(Decoder_handler *hander);
177 | extern int init_logfile(Decoder_handler *hander);
178 | extern int init_streaming(Decoder_handler *hander);
179 | extern ALI_PG_DECODE_MESSAGE *exec_logical_decoder(Decoder_handler *hander, volatile bool *time_to_stop);
180 | extern void pg_sleep(long microsec);
181 | extern Decoder_handler *init_hander(void);
182 |
183 | #ifdef __cplusplus
184 | }
185 | #endif
186 |
187 |
188 |
189 | #endif /* PG_LOGICALDECODE_H */
190 |
191 |
192 |
--------------------------------------------------------------------------------
/dbsync/pgsync.c:
--------------------------------------------------------------------------------
1 | /*
2 | * pg_sync.c
3 | *
4 | */
5 |
6 |
7 | #include "postgres_fe.h"
8 | #include "lib/stringinfo.h"
9 | #include "common/fe_memutils.h"
10 |
11 | #include "libpq-fe.h"
12 |
13 | #include "access/transam.h"
14 | #include "libpq/pqformat.h"
15 | #include "pg_logicaldecode.h"
16 | #include "pqexpbuffer.h"
17 | #include "pgsync.h"
18 | #include "libpq/pqsignal.h"
19 |
20 | #include
21 |
22 | #ifndef WIN32
23 | #include
24 | #endif
25 |
26 |
27 |
28 | static void *copy_table_data(void *arg);
29 | static char *get_synchronized_snapshot(PGconn *conn);
30 | static bool is_slot_exists(PGconn *conn, char *slotname);
31 | static void *logical_decoding_receive_thread(void *arg);
32 | static void get_task_status(PGconn *conn, char **full_start, char **full_end, char **decoder_start, char **apply_id);
33 | static void update_task_status(PGconn *conn, bool full_start, bool full_end, bool decoder_start, int64 apply_id);
34 | static void *logical_decoding_apply_thread(void *arg);
35 | static int64 get_apply_status(PGconn *conn);
36 | static void sigint_handler(int signum);
37 |
38 | static volatile bool time_to_abort = false;
39 |
40 |
41 | #define ERROR_DUPLICATE_KEY 23505
42 |
43 | #define SQL_TYPE_BEGIN 0
44 | #define SQL_TYPE_COMMIT 1
45 | #define SQL_TYPE_FIRST_STATMENT 2
46 | #define SQL_TYPE_OTHER_STATMENT 3
47 |
48 | #define RECONNECT_SLEEP_TIME 5
49 |
50 | #define ALL_DB_TABLE_SQL "select n.nspname, c.relname from pg_class c, pg_namespace n where n.oid = c.relnamespace and c.relkind = 'r' and n.nspname not in ('pg_catalog','tiger','tiger_data','topology','postgis','information_schema','gp_toolkit','pg_aoseg','pg_toast') order by c.relpages desc;"
51 | #define GET_NAPSHOT "SELECT pg_export_snapshot()"
52 |
53 | #define TASK_ID "1"
54 |
55 | #ifndef WIN32
56 | static void
57 | sigint_handler(int signum)
58 | {
59 | time_to_abort = true;
60 | }
61 | #else
62 | static int64 atoll(const char *nptr)
63 | {
64 | return atol(nptr);
65 | }
66 | #endif
67 |
68 |
69 | /*
70 | * COPY single table over wire.
71 | */
72 | static void *
73 | copy_table_data(void *arg)
74 | {
75 | ThreadArg *args = (ThreadArg *)arg;
76 | Thread_hd *hd = args->hd;
77 | PGresult *res1;
78 | PGresult *res2;
79 | int bytes;
80 | char *copybuf;
81 | StringInfoData query;
82 | char *nspname;
83 | char *relname;
84 | Task_hd *curr = NULL;
85 | TimevalStruct before,
86 | after;
87 | double elapsed_msec = 0;
88 |
89 | PGconn *origin_conn = args->from;
90 | PGconn *target_conn = args->to;
91 |
92 | origin_conn = pglogical_connect(hd->src, EXTENSION_NAME "_copy");
93 | if (origin_conn == NULL)
94 | {
95 | fprintf(stderr, "init src conn failed: %s", PQerrorMessage(origin_conn));
96 | return NULL;
97 | }
98 |
99 | target_conn = pglogical_connect(hd->desc, EXTENSION_NAME "_copy");
100 | if (target_conn == NULL)
101 | {
102 | fprintf(stderr, "init desc conn failed: %s", PQerrorMessage(target_conn));
103 | return NULL;
104 | }
105 |
106 | initStringInfo(&query);
107 | while(1)
108 | {
109 | int nlist = 0;
110 |
111 | GETTIMEOFDAY(&before);
112 | pthread_mutex_lock(&hd->t_lock);
113 | nlist = hd->ntask;
114 | if (nlist == 1)
115 | {
116 | curr = hd->l_task;
117 | hd->l_task = NULL;
118 | hd->ntask = 0;
119 | }
120 | else if (nlist > 1)
121 | {
122 | Task_hd *tmp = hd->l_task->next;
123 | curr = hd->l_task;
124 | hd->l_task = tmp;
125 | hd->ntask--;
126 | }
127 | else
128 | {
129 | curr = NULL;
130 | }
131 |
132 | pthread_mutex_unlock(&hd->t_lock);
133 |
134 | if(curr == NULL)
135 | {
136 | break;
137 | }
138 |
139 | start_copy_origin_tx(origin_conn, hd->snapshot, hd->src_version, hd->desc_is_greenplum);
140 | start_copy_target_tx(target_conn, hd->desc_version, hd->desc_is_greenplum);
141 |
142 | nspname = curr->schemaname;
143 | relname = curr->relname;
144 |
145 | /* Build COPY TO query. */
146 | appendStringInfo(&query, "COPY %s.%s TO stdout",
147 | PQescapeIdentifier(origin_conn, nspname,
148 | strlen(nspname)),
149 | PQescapeIdentifier(origin_conn, relname,
150 | strlen(relname)));
151 |
152 | /* Execute COPY TO. */
153 | res1 = PQexec(origin_conn, query.data);
154 | if (PQresultStatus(res1) != PGRES_COPY_OUT)
155 | {
156 | fprintf(stderr,"table copy failed Query '%s': %s",
157 | query.data, PQerrorMessage(origin_conn));
158 | goto exit;
159 | }
160 |
161 | /* Build COPY FROM query. */
162 | resetStringInfo(&query);
163 | appendStringInfo(&query, "COPY %s.%s FROM stdin",
164 | PQescapeIdentifier(target_conn, nspname,
165 | strlen(nspname)),
166 | PQescapeIdentifier(target_conn, relname,
167 | strlen(relname)));
168 |
169 | /* Execute COPY FROM. */
170 | res2 = PQexec(target_conn, query.data);
171 | if (PQresultStatus(res2) != PGRES_COPY_IN)
172 | {
173 | fprintf(stderr,"table copy failed Query '%s': %s",
174 | query.data, PQerrorMessage(target_conn));
175 | goto exit;
176 | }
177 |
178 | while ((bytes = PQgetCopyData(origin_conn, ©buf, false)) > 0)
179 | {
180 | if (PQputCopyData(target_conn, copybuf, bytes) != 1)
181 | {
182 | fprintf(stderr,"writing to target table failed destination connection reported: %s",
183 | PQerrorMessage(target_conn));
184 | goto exit;
185 | }
186 | args->count++;
187 | curr->count++;
188 | PQfreemem(copybuf);
189 | }
190 |
191 | if (bytes != -1)
192 | {
193 | fprintf(stderr,"reading from origin table failed source connection returned %d: %s",
194 | bytes, PQerrorMessage(origin_conn));
195 | goto exit;
196 | }
197 |
198 | /* Send local finish */
199 | if (PQputCopyEnd(target_conn, NULL) != 1)
200 | {
201 | fprintf(stderr,"sending copy-completion to destination connection failed destination connection reported: %s",
202 | PQerrorMessage(target_conn));
203 | goto exit;
204 | }
205 |
206 | PQclear(res2);
207 | res2 = PQgetResult(target_conn);
208 | if (PQresultStatus(res2) != PGRES_COMMAND_OK)
209 | {
210 | fprintf(stderr, "COPY failed for table \"%s\": %s",
211 | relname, PQerrorMessage(target_conn));
212 | goto exit;
213 | }
214 |
215 | finish_copy_origin_tx(origin_conn);
216 | finish_copy_target_tx(target_conn);
217 | curr->complete = true;
218 | PQclear(res1);
219 | PQclear(res2);
220 | resetStringInfo(&query);
221 |
222 | GETTIMEOFDAY(&after);
223 | DIFF_MSEC(&after, &before, elapsed_msec);
224 | fprintf(stderr,"thread %d migrate task %d table %s.%s %ld rows complete, time cost %.3f ms\n",
225 | args->id, curr->id, nspname, relname, curr->count, elapsed_msec);
226 | }
227 |
228 | args->all_ok = true;
229 |
230 | exit:
231 |
232 | PQfinish(origin_conn);
233 | PQfinish(target_conn);
234 | ThreadExit(0);
235 | return NULL;
236 | }
237 |
238 |
239 | int
240 | db_sync_main(char *src, char *desc, char *local, int nthread)
241 | {
242 | int i = 0;
243 | Thread_hd th_hd;
244 | Thread *thread = NULL;
245 | PGresult *res = NULL;
246 | PGconn *origin_conn_repl;
247 | PGconn *desc_conn;
248 | PGconn *local_conn;
249 | char *snapshot = NULL;
250 | XLogRecPtr lsn = 0;
251 | long s_count = 0;
252 | long t_count = 0;
253 | bool have_err = false;
254 | TimevalStruct before,
255 | after;
256 | double elapsed_msec = 0;
257 | Decoder_handler *hander = NULL;
258 | struct Thread *decoder = NULL;
259 | bool replication_sync = false;
260 | bool need_full_sync = false;
261 | char *full_start = NULL;
262 | char *full_end = NULL;
263 | char *decoder_start = NULL;
264 | char *apply_id = NULL;
265 | int ntask = 0;
266 |
267 | #ifndef WIN32
268 | signal(SIGINT, sigint_handler);
269 | #endif
270 |
271 | GETTIMEOFDAY(&before);
272 |
273 | memset(&th_hd, 0, sizeof(Thread_hd));
274 | th_hd.nth = nthread;
275 | th_hd.src = src;
276 | th_hd.desc = desc;
277 | th_hd.local = local;
278 |
279 | origin_conn_repl = pglogical_connect(src, EXTENSION_NAME "_main");
280 | if (origin_conn_repl == NULL)
281 | {
282 | fprintf(stderr, "conn to src faild: %s", PQerrorMessage(origin_conn_repl));
283 | return 1;
284 | }
285 | th_hd.src_version = PQserverVersion(origin_conn_repl);
286 | th_hd.src_is_greenplum = is_greenplum(origin_conn_repl);
287 |
288 | desc_conn = pglogical_connect(desc, EXTENSION_NAME "_main");
289 | if (desc_conn == NULL)
290 | {
291 | fprintf(stderr, "init desc conn failed: %s", PQerrorMessage(desc_conn));
292 | return 1;
293 | }
294 | th_hd.desc_version = PQserverVersion(desc_conn);
295 | th_hd.desc_is_greenplum = is_greenplum(desc_conn);
296 | PQfinish(desc_conn);
297 |
298 | local_conn = pglogical_connect(local, EXTENSION_NAME "_main");
299 | if (local_conn == NULL)
300 | {
301 | fprintf(stderr, "init local conn failed: %s", PQerrorMessage(local_conn));
302 | need_full_sync = true;
303 | }
304 | else
305 | {
306 | ExecuteSqlStatement(local_conn, "CREATE TABLE IF NOT EXISTS sync_sqls(id bigserial, sql text)");
307 | ExecuteSqlStatement(local_conn, "CREATE TABLE IF NOT EXISTS db_sync_status(id bigserial primary key, full_s_start timestamp DEFAULT NULL, full_s_end timestamp DEFAULT NULL, decoder_start timestamp DEFAULT NULL, apply_id bigint DEFAULT NULL)");
308 | ExecuteSqlStatement(local_conn, "insert into db_sync_status (id) values (" TASK_ID ");");
309 | get_task_status(local_conn, &full_start, &full_end, &decoder_start, &apply_id);
310 |
311 | if (full_start && full_end == NULL)
312 | {
313 | fprintf(stderr, "full sync start %s, but not finish.truncate all data and restart dbsync\n", full_start);
314 | return 1;
315 | }
316 | else if(full_start == NULL && full_end == NULL)
317 | {
318 | need_full_sync = true;
319 | fprintf(stderr, "new dbsync task");
320 | }
321 | else if(full_start && full_end)
322 | {
323 | fprintf(stderr, "full sync start %s, end %s restart decoder sync\n", full_start, full_end);
324 | need_full_sync = false;
325 | }
326 |
327 | if (decoder_start)
328 | {
329 | fprintf(stderr, "decoder sync start %s\n", decoder_start);
330 | }
331 |
332 | if (apply_id)
333 | {
334 | fprintf(stderr, "decoder apply id %s\n", apply_id);
335 | }
336 | }
337 |
338 | if (th_hd.src_is_greenplum == false && th_hd.src_version >= 90400)
339 | {
340 | replication_sync = true;
341 | if (!is_slot_exists(origin_conn_repl, EXTENSION_NAME "_slot"))
342 | {
343 | int rc = 0;
344 |
345 | hander = init_hander();
346 | hander->connection_string = src;
347 | init_logfile(hander);
348 | rc = initialize_connection(hander);
349 | if(rc != 0)
350 | {
351 | fprintf(stderr, "create replication conn failed\n");
352 | return 1;
353 | }
354 | hander->do_create_slot = true;
355 | snapshot = create_replication_slot(hander, &lsn, EXTENSION_NAME "_slot");
356 | if (snapshot == NULL)
357 | {
358 | fprintf(stderr, "create replication slot failed\n");
359 | return 1;
360 | }
361 |
362 | th_hd.slot_name = hander->replication_slot;
363 | }
364 | else
365 | {
366 | fprintf(stderr, "decoder slot %s exist\n", EXTENSION_NAME "_slot");
367 | th_hd.slot_name = EXTENSION_NAME "_slot";
368 | }
369 | }
370 |
371 | if (need_full_sync)
372 | {
373 | const char *setup_query = NULL;
374 | PQExpBuffer query;
375 |
376 | if (th_hd.src_is_greenplum == false)
377 | {
378 | setup_query = "BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY;\n";
379 | }
380 | else
381 | {
382 | setup_query = "BEGIN";
383 | }
384 |
385 | query = createPQExpBuffer();
386 | appendPQExpBuffer(query, "%s", setup_query);
387 |
388 | if (snapshot)
389 | {
390 | appendPQExpBuffer(query, "SET TRANSACTION SNAPSHOT '%s';\n", snapshot);
391 | }
392 |
393 | res = PQexec(origin_conn_repl, query->data);
394 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
395 | {
396 | fprintf(stderr, "init open a tran failed: %s", PQresultErrorMessage(res));
397 | return 1;
398 | }
399 | resetPQExpBuffer(query);
400 |
401 | if (snapshot == NULL)
402 | {
403 | if (th_hd.src_version >= 90200)
404 | {
405 | snapshot = get_synchronized_snapshot(origin_conn_repl);
406 | th_hd.snapshot = snapshot;
407 | }
408 | }
409 |
410 | appendPQExpBuffer(query, ALL_DB_TABLE_SQL);
411 | res = PQexec(origin_conn_repl, query->data);
412 | if (PQresultStatus(res) != PGRES_TUPLES_OK)
413 | {
414 | fprintf(stderr, "init sql run failed: %s", PQresultErrorMessage(res));
415 | return 1;
416 | }
417 |
418 | ntask = PQntuples(res);
419 | th_hd.ntask = ntask;
420 | if (th_hd.ntask >= 1)
421 | {
422 | th_hd.task = (Task_hd *)palloc0(sizeof(Task_hd) * th_hd.ntask);
423 | }
424 |
425 | for (i = 0; i < th_hd.ntask; i++)
426 | {
427 | th_hd.task[i].id = i;
428 | th_hd.task[i].schemaname = pstrdup(PQgetvalue(res, i, 0));
429 | th_hd.task[i].relname = pstrdup(PQgetvalue(res, i, 1));
430 | th_hd.task[i].count = 0;
431 | th_hd.task[i].complete = false;
432 | if (i != th_hd.ntask - 1)
433 | {
434 | th_hd.task[i].next = &th_hd.task[i+1];
435 | }
436 | }
437 |
438 | th_hd.l_task = &(th_hd.task[0]);
439 | PQclear(res);
440 | destroyPQExpBuffer(query);
441 |
442 | th_hd.th = (ThreadArg *)malloc(sizeof(ThreadArg) * th_hd.nth);
443 | for (i = 0; i < th_hd.nth; i++)
444 | {
445 | th_hd.th[i].id = i;
446 | th_hd.th[i].count = 0;
447 | th_hd.th[i].all_ok = false;
448 |
449 | th_hd.th[i].hd = &th_hd;
450 | }
451 | pthread_mutex_init(&th_hd.t_lock, NULL);
452 |
453 | fprintf(stderr, "starting full sync");
454 | if (snapshot)
455 | {
456 | fprintf(stderr, " with snapshot %s", snapshot);
457 | }
458 | fprintf(stderr, "\n");
459 |
460 | thread = (Thread *)palloc0(sizeof(Thread) * th_hd.nth);
461 | for (i = 0; i < th_hd.nth; i++)
462 | {
463 | ThreadCreate(&thread[i], copy_table_data, &th_hd.th[i]);
464 | }
465 |
466 | update_task_status(local_conn, true, false, false, -1);
467 | }
468 |
469 | if (replication_sync)
470 | {
471 | decoder = (Thread *)palloc0(sizeof(Thread) * 2);
472 | fprintf(stderr, "starting logical decoding sync thread\n");
473 | ThreadCreate(&decoder[0], logical_decoding_receive_thread, &th_hd);
474 | update_task_status(local_conn, false, false, true, -1);
475 | }
476 |
477 | if (need_full_sync)
478 | {
479 | WaitThreadEnd(th_hd.nth, thread);
480 | update_task_status(local_conn, false, true, false, -1);
481 |
482 | GETTIMEOFDAY(&after);
483 | DIFF_MSEC(&after, &before, elapsed_msec);
484 |
485 | for (i = 0; i < th_hd.nth; i++)
486 | {
487 | if(th_hd.th[i].all_ok)
488 | {
489 | s_count += th_hd.th[i].count;
490 | }
491 | else
492 | {
493 | have_err = true;
494 | }
495 | }
496 |
497 | for (i = 0; i < ntask; i++)
498 | {
499 | t_count += th_hd.task[i].count;
500 | }
501 |
502 | fprintf(stderr, "job migrate row %ld task row %ld \n", s_count, t_count);
503 | fprintf(stderr, "full sync time cost %.3f ms\n", elapsed_msec);
504 | if (have_err)
505 | {
506 | fprintf(stderr, "migration process with errors\n");
507 | }
508 | }
509 |
510 | if (replication_sync)
511 | {
512 | ThreadCreate(&decoder[1], logical_decoding_apply_thread, &th_hd);
513 | fprintf(stderr, "starting decoder apply thread\n");
514 | WaitThreadEnd(2, decoder);
515 | }
516 |
517 | PQfinish(origin_conn_repl);
518 | PQfinish(local_conn);
519 |
520 | return 0;
521 | }
522 |
523 |
524 | static char *
525 | get_synchronized_snapshot(PGconn *conn)
526 | {
527 | char *query = "SELECT pg_export_snapshot()";
528 | char *result;
529 | PGresult *res;
530 |
531 | res = PQexec(conn, query);
532 | if (PQresultStatus(res) != PGRES_TUPLES_OK)
533 | {
534 | fprintf(stderr, "init sql run failed: %s", PQresultErrorMessage(res));
535 | return NULL;
536 | }
537 | result = pstrdup(PQgetvalue(res, 0, 0));
538 | PQclear(res);
539 |
540 | return result;
541 | }
542 |
543 | static bool
544 | is_slot_exists(PGconn *conn, char *slotname)
545 | {
546 | PGresult *res;
547 | int ntups;
548 | bool exist = false;
549 | PQExpBuffer query;
550 |
551 | query = createPQExpBuffer();
552 | appendPQExpBuffer(query, "select slot_name from pg_replication_slots where slot_name = '%s';",
553 | slotname);
554 |
555 | res = PQexec(conn, query->data);
556 | if (PQresultStatus(res) != PGRES_TUPLES_OK)
557 | {
558 | PQclear(res);
559 | destroyPQExpBuffer(query);
560 | return false;
561 | }
562 |
563 | /* Expecting a single result only */
564 | ntups = PQntuples(res);
565 | if (ntups == 1)
566 | {
567 | exist = true;
568 | }
569 |
570 | PQclear(res);
571 | destroyPQExpBuffer(query);
572 |
573 | return exist;
574 | }
575 |
576 | static void
577 | get_task_status(PGconn *conn, char **full_start, char **full_end, char **decoder_start, char **apply_id)
578 | {
579 | PGresult *res;
580 | char *query = "SELECT full_s_start , full_s_end, decoder_start, apply_id FROM db_sync_status where id =" TASK_ID;
581 |
582 | res = PQexec(conn, query);
583 | if (PQresultStatus(res) != PGRES_TUPLES_OK)
584 | {
585 | PQclear(res);
586 | return;
587 | }
588 |
589 | if (PQntuples(res) != 1)
590 | {
591 | PQclear(res);
592 | return;
593 | }
594 |
595 | if (!PQgetisnull(res, 0, 0))
596 | {
597 | *full_start = pstrdup(PQgetvalue(res, 0, 0));
598 | }
599 |
600 | if (!PQgetisnull(res, 0, 1))
601 | {
602 | *full_end = pstrdup(PQgetvalue(res, 0, 1));
603 | }
604 |
605 | if (!PQgetisnull(res, 0, 2))
606 | {
607 | *decoder_start = pstrdup(PQgetvalue(res, 0, 2));
608 | }
609 |
610 | if (!PQgetisnull(res, 0, 3))
611 | {
612 | *apply_id = pstrdup(PQgetvalue(res, 0, 3));
613 | }
614 |
615 | PQclear(res);
616 |
617 | return;
618 | }
619 |
620 | static void
621 | update_task_status(PGconn *conn, bool full_start, bool full_end, bool decoder_start, int64 apply_id)
622 | {
623 | PQExpBuffer query;
624 |
625 | query = createPQExpBuffer();
626 |
627 | if (full_start)
628 | {
629 | appendPQExpBuffer(query, "UPDATE db_sync_status SET full_s_start = now() WHERE id = %s",
630 | TASK_ID);
631 | ExecuteSqlStatement(conn, query->data);
632 | }
633 |
634 | if (full_end)
635 | {
636 | appendPQExpBuffer(query, "UPDATE db_sync_status SET full_s_end = now() WHERE id = %s",
637 | TASK_ID);
638 | ExecuteSqlStatement(conn, query->data);
639 | }
640 |
641 | if (decoder_start)
642 | {
643 | appendPQExpBuffer(query, "UPDATE db_sync_status SET decoder_start = now() WHERE id = %s",
644 | TASK_ID);
645 | ExecuteSqlStatement(conn, query->data);
646 | }
647 |
648 | if (apply_id >= 0)
649 | {
650 | appendPQExpBuffer(query, "UPDATE db_sync_status SET apply_id = " INT64_FORMAT " WHERE id = %s",
651 | apply_id, TASK_ID);
652 | ExecuteSqlStatement(conn, query->data);
653 | }
654 |
655 | destroyPQExpBuffer(query);
656 |
657 | return;
658 | }
659 |
660 | static void *
661 | logical_decoding_receive_thread(void *arg)
662 | {
663 | Thread_hd *hd = (Thread_hd *)arg;
664 | Decoder_handler *hander;
665 | int rc = 0;
666 | bool init = false;
667 | PGconn *local_conn;
668 | PQExpBuffer buffer;
669 | char *stmtname = "insert_sqls";
670 | Oid type[1];
671 | const char *paramValues[1];
672 | PGresult *res = NULL;
673 |
674 | type[0] = 25;
675 | buffer = createPQExpBuffer();
676 |
677 | local_conn = pglogical_connect(hd->local, EXTENSION_NAME "_decoding");
678 | if (local_conn == NULL)
679 | {
680 | fprintf(stderr, "init src conn failed: %s", PQerrorMessage(local_conn));
681 | goto exit;
682 | }
683 | setup_connection(local_conn, 90400, false);
684 |
685 | res = PQprepare(local_conn, stmtname, "INSERT INTO sync_sqls (sql) VALUES($1)", 1, type);
686 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
687 | {
688 | fprintf(stderr, "create PQprepare failed: %s", PQerrorMessage(local_conn));
689 | PQfinish(local_conn);
690 | goto exit;
691 | }
692 | PQclear(res);
693 |
694 | hander = init_hander();
695 | hander->connection_string = hd->src;
696 | init_logfile(hander);
697 | rc = check_handler_parameters(hander);
698 | if(rc != 0)
699 | {
700 | exit(1);
701 | }
702 |
703 | rc = initialize_connection(hander);
704 | if(rc != 0)
705 | {
706 | exit(1);
707 | }
708 |
709 | hander->replication_slot = hd->slot_name;
710 | init_streaming(hander);
711 | init = true;
712 |
713 | while (true)
714 | {
715 | ALI_PG_DECODE_MESSAGE *msg = NULL;
716 |
717 | if (time_to_abort)
718 | {
719 | if (hander->copybuf != NULL)
720 | {
721 | PQfreemem(hander->copybuf);
722 | hander->copybuf = NULL;
723 | }
724 | if (hander->conn)
725 | {
726 | PQfinish(hander->conn);
727 | hander->conn = NULL;
728 | }
729 | if (local_conn)
730 | {
731 | PQdescribePrepared(local_conn, stmtname);
732 | PQfinish(local_conn);
733 | }
734 | break;
735 | }
736 |
737 | if (!init)
738 | {
739 | initialize_connection(hander);
740 | init_streaming(hander);
741 | init = true;
742 | }
743 |
744 | msg = exec_logical_decoder(hander, &time_to_abort);
745 | if (msg != NULL)
746 | {
747 | out_put_tuple_to_sql(hander, msg, buffer);
748 | if(msg->type == MSGKIND_BEGIN)
749 | {
750 | res = PQexec(local_conn, "BEGIN");
751 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
752 | {
753 | fprintf(stderr, "decoding receive thread begin a local trans failed: %s", PQerrorMessage(local_conn));
754 | goto exit;
755 | }
756 | PQclear(res);
757 | }
758 |
759 | paramValues[0] = buffer->data;
760 | res = PQexecPrepared(local_conn, stmtname, 1, paramValues, NULL, NULL, 1);
761 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
762 | {
763 | fprintf(stderr, "exec prepare INSERT INTO sync_sqls failed: %s", PQerrorMessage(local_conn));
764 | time_to_abort = true;
765 | goto exit;
766 | }
767 | PQclear(res);
768 |
769 | hander->flushpos = hander->recvpos;
770 | if(msg->type == MSGKIND_COMMIT)
771 | {
772 | res = PQexec(local_conn, "END");
773 | if (PQresultStatus(res) != PGRES_COMMAND_OK)
774 | {
775 | fprintf(stderr, "decoding receive thread commit a local trans failed: %s", PQerrorMessage(local_conn));
776 | goto exit;
777 | }
778 | PQclear(res);
779 | }
780 |
781 | resetPQExpBuffer(buffer);
782 | }
783 | else
784 | {
785 | fprintf(stderr, "decoding receive no record, sleep and reconnect");
786 | pg_sleep(RECONNECT_SLEEP_TIME * 1000000);
787 | init = false;
788 | }
789 | }
790 |
791 |
792 | exit:
793 |
794 | destroyPQExpBuffer(buffer);
795 |
796 | ThreadExit(0);
797 | return NULL;
798 | }
799 |
800 | static void *
801 | logical_decoding_apply_thread(void *arg)
802 | {
803 | Thread_hd *hd = (Thread_hd *)arg;
804 | PGconn *local_conn = NULL;
805 | PGconn *local_conn_u = NULL;
806 | PGconn *apply_conn = NULL;
807 | Oid type[1];
808 | PGresult *resreader = NULL;
809 | PGresult *applyres = NULL;
810 | int pgversion;
811 | bool is_gp = false;
812 | int64 apply_id = 0;
813 |
814 | type[0] = 25;
815 |
816 | local_conn = pglogical_connect(hd->local, EXTENSION_NAME "apply_reader");
817 | if (local_conn == NULL)
818 | {
819 | fprintf(stderr, "decoding applyer init src conn failed: %s", PQerrorMessage(local_conn));
820 | goto exit;
821 | }
822 | setup_connection(local_conn, 90400, false);
823 | apply_id = get_apply_status(local_conn);
824 | if (apply_id == -1)
825 | {
826 | goto exit;
827 | }
828 |
829 | local_conn_u = pglogical_connect(hd->local, EXTENSION_NAME "apply_update_status");
830 | if (local_conn_u == NULL)
831 | {
832 | fprintf(stderr, "decoding applyer init src conn failed: %s", PQerrorMessage(local_conn_u));
833 | goto exit;
834 | }
835 | setup_connection(local_conn_u, 90400, false);
836 |
837 | apply_conn = pglogical_connect(hd->desc, EXTENSION_NAME "_decoding_apply");
838 | if (apply_conn == NULL)
839 | {
840 | fprintf(stderr, "decoding_apply init desc conn failed: %s", PQerrorMessage(apply_conn));
841 | goto exit;
842 | }
843 | pgversion = PQserverVersion(apply_conn);
844 | is_gp = is_greenplum(apply_conn);
845 | setup_connection(apply_conn, pgversion, is_gp);
846 |
847 | while (!time_to_abort)
848 | {
849 | const char *paramValues[1];
850 | char *ssql;
851 | char tmp[16];
852 | int n_commit = 0;
853 | int sqltype = SQL_TYPE_BEGIN;
854 |
855 | sprintf(tmp, INT64_FORMAT, apply_id);
856 | paramValues[0] = tmp;
857 |
858 | resreader = PQexec(local_conn, "BEGIN");
859 | if (PQresultStatus(resreader) != PGRES_COMMAND_OK)
860 | {
861 | fprintf(stderr, "BEGIN command failed: %s\n", PQerrorMessage(local_conn));
862 | PQclear(resreader);
863 | goto exit;
864 | }
865 | PQclear(resreader);
866 |
867 | resreader = PQexecParams(local_conn,
868 | "DECLARE ali_decoder_cursor CURSOR FOR select id, sql from sync_sqls where id > $1 order by id",
869 | 1,
870 | NULL,
871 | paramValues,
872 | NULL,
873 | NULL,
874 | 1);
875 |
876 | if (PQresultStatus(resreader) != PGRES_COMMAND_OK)
877 | {
878 | fprintf(stderr, "DECLARE CURSOR command failed: %s\n", PQerrorMessage(local_conn));
879 | PQclear(resreader);
880 | goto exit;
881 | }
882 | PQclear(resreader);
883 |
884 | while(!time_to_abort)
885 | {
886 | resreader = PQexec(local_conn, "FETCH FROM ali_decoder_cursor");
887 | if (PQresultStatus(resreader) != PGRES_TUPLES_OK)
888 | {
889 | fprintf(stderr, "FETCH ALL command didn't return tuples properly: %s\n", PQerrorMessage(local_conn));
890 | PQclear(resreader);
891 | }
892 |
893 | if (PQntuples(resreader) == 0)
894 | {
895 | PQclear(resreader);
896 | resreader = PQexec(local_conn, "CLOSE ali_decoder_cursor");
897 | PQclear(resreader);
898 | resreader = PQexec(local_conn, "END");
899 | PQclear(resreader);
900 |
901 | if (n_commit != 0)
902 | {
903 | n_commit = 0;
904 | update_task_status(local_conn_u, false, false, false, apply_id);
905 | }
906 |
907 | pg_sleep(1000000);
908 | break;
909 | }
910 |
911 | ssql = PQgetvalue(resreader, 0, 1);
912 | if(strcmp(ssql,"begin;") == 0)
913 | {
914 | sqltype = SQL_TYPE_BEGIN;
915 | }
916 | else if (strcmp(ssql,"commit;") == 0)
917 | {
918 | sqltype = SQL_TYPE_COMMIT;
919 | }
920 | else if(sqltype == SQL_TYPE_BEGIN)
921 | {
922 | sqltype = SQL_TYPE_FIRST_STATMENT;
923 | }
924 | else
925 | {
926 | sqltype = SQL_TYPE_OTHER_STATMENT;
927 | }
928 |
929 | applyres = PQexec(apply_conn, ssql);
930 | if (PQresultStatus(applyres) != PGRES_COMMAND_OK)
931 | {
932 | char *sqlstate = PQresultErrorField(applyres, PG_DIAG_SQLSTATE);
933 | int errcode = 0;
934 | fprintf(stderr, "exec apply id %s, sql %s failed: %s\n", PQgetvalue(resreader, 0, 0), ssql, PQerrorMessage(apply_conn));
935 | errcode = atoi(sqlstate);
936 | if (errcode == ERROR_DUPLICATE_KEY && sqltype == SQL_TYPE_FIRST_STATMENT)
937 | {
938 | PQclear(applyres);
939 | applyres = PQexec(apply_conn, "END");
940 | if (PQresultStatus(applyres) != PGRES_COMMAND_OK)
941 | {
942 | goto exit;
943 | }
944 | PQclear(applyres);
945 | applyres = PQexec(apply_conn, "BEGIN");
946 | if (PQresultStatus(applyres) != PGRES_COMMAND_OK)
947 | {
948 | goto exit;
949 | }
950 | sqltype = SQL_TYPE_BEGIN;
951 | }
952 | else
953 | {
954 | PQclear(resreader);
955 | PQclear(applyres);
956 | goto exit;
957 | }
958 | }
959 |
960 | if (sqltype == SQL_TYPE_COMMIT)
961 | {
962 | n_commit++;
963 | apply_id = atoll(PQgetvalue(resreader, 0, 0));
964 | if(n_commit == 5)
965 | {
966 | n_commit = 0;
967 | update_task_status(local_conn_u, false, false, false, apply_id);
968 | }
969 | }
970 | PQclear(resreader);
971 | PQclear(applyres);
972 | }
973 | }
974 |
975 | exit:
976 |
977 | if (local_conn)
978 | {
979 | PQfinish(local_conn);
980 | }
981 |
982 | if (local_conn_u)
983 | {
984 | PQfinish(local_conn_u);
985 | }
986 |
987 | if (apply_conn)
988 | {
989 | PQfinish(apply_conn);
990 | }
991 |
992 | time_to_abort = true;
993 |
994 | ThreadExit(0);
995 | return NULL;
996 | }
997 |
998 | static int64
999 | get_apply_status(PGconn *conn)
1000 | {
1001 | PGresult *res;
1002 | char *query = "SELECT apply_id FROM db_sync_status where id =" TASK_ID;
1003 | int64 rc = 0;
1004 |
1005 | res = PQexec(conn, query);
1006 | if (PQresultStatus(res) != PGRES_TUPLES_OK)
1007 | {
1008 | PQclear(res);
1009 | return -1;
1010 | }
1011 |
1012 | if (PQntuples(res) != 1)
1013 | {
1014 | PQclear(res);
1015 | return -1;
1016 | }
1017 |
1018 | if (!PQgetisnull(res, 0, 0))
1019 | {
1020 | char *tmp = PQgetvalue(res, 0, 0);
1021 | rc = atoll(tmp);
1022 | }
1023 |
1024 | PQclear(res);
1025 |
1026 | return rc;
1027 | }
1028 |
1029 |
--------------------------------------------------------------------------------
/dbsync/pgsync.h:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ifndef PG_SYNC_H
4 | #define PG_SYNC_H
5 |
6 | #include "postgres_fe.h"
7 |
8 | #include "lib/stringinfo.h"
9 | #include "lib/stringinfo.h"
10 | #include "common/fe_memutils.h"
11 |
12 | #include "libpq/pqformat.h"
13 | #include "pqexpbuffer.h"
14 |
15 | #include "misc.h"
16 |
17 | #ifdef __cplusplus
18 | extern "C"
19 | {
20 | #endif
21 |
22 | #ifndef WIN32
23 | #include
24 |
25 | typedef struct timeval TimevalStruct;
26 |
27 | #define GETTIMEOFDAY(T) gettimeofday(T, NULL)
28 | #define DIFF_MSEC(T, U, res) \
29 | do { \
30 | res = ((((int) ((T)->tv_sec - (U)->tv_sec)) * 1000000.0 + \
31 | ((int) ((T)->tv_usec - (U)->tv_usec))) / 1000.0); \
32 | } while(0)
33 |
34 | #else
35 |
36 | #include
37 | #include
38 | #include
39 |
40 | typedef LARGE_INTEGER TimevalStruct;
41 | #define GETTIMEOFDAY(T) QueryPerformanceCounter(T)
42 | #define DIFF_MSEC(T, U, res) \
43 | do { \
44 | LARGE_INTEGER frq; \
45 | \
46 | QueryPerformanceFrequency(&frq); \
47 | res = (double)(((T)->QuadPart - (U)->QuadPart)/(double)frq.QuadPart); \
48 | res *= 1000; \
49 | } while(0)
50 |
51 | #endif
52 |
53 | #ifdef WIN32
54 | static int64 atoll(const char *nptr);
55 | #endif
56 |
57 |
58 | #include "mysql.h"
59 |
60 | typedef struct ThreadArg
61 | {
62 | int id;
63 | long count;
64 | bool all_ok;
65 | PGconn *from;
66 | PGconn *to;
67 |
68 | struct Thread_hd *hd;
69 | }ThreadArg;
70 |
71 | typedef struct mysql_conn_info
72 | {
73 | char *host;
74 | int port;
75 | char *user;
76 | char *passwd;
77 | char *encoding;
78 | char *db;
79 | char *encodingdir;
80 | char **tabnames;
81 | char **queries;
82 | }mysql_conn_info;
83 |
84 | typedef struct Thread_hd
85 | {
86 | int nth;
87 | struct ThreadArg *th;
88 |
89 | const char *snapshot;
90 | char *src;
91 | int src_version;
92 | bool src_is_greenplum;
93 |
94 | char *slot_name;
95 |
96 | mysql_conn_info *mysql_src;
97 |
98 | char *desc;
99 | int desc_version;
100 | bool desc_is_greenplum;
101 | char *local;
102 |
103 | int ntask;
104 | struct Task_hd *task;
105 | struct Task_hd *l_task;
106 | pthread_mutex_t t_lock;
107 |
108 | int ntask_com;
109 | struct Task_hd *task_com;
110 | pthread_mutex_t t_lock_com;
111 |
112 | uint32 ignore_error_count;
113 | }Thread_hd;
114 |
115 | typedef struct Task_hd
116 | {
117 | int id;
118 | char *schemaname; /* the schema name, or NULL */
119 | char *relname; /* the relation/sequence name */
120 | char *query;
121 | long count;
122 | bool complete;
123 |
124 | struct Task_hd *next;
125 | }Task_hd;
126 |
127 |
128 | #define EXTENSION_NAME "rds_logical_sync"
129 |
130 | extern int db_sync_main(char *src, char *desc, char *local, int nthread);
131 |
132 |
133 | extern int mysql2pgsql_sync_main(char *desc, int nthread, mysql_conn_info *hd, char* target_schema, uint32 ignore_error_count);
134 |
135 |
136 | #ifdef __cplusplus
137 | }
138 | #endif
139 |
140 | #endif
141 |
142 |
--------------------------------------------------------------------------------
/dbsync/pqformat.c:
--------------------------------------------------------------------------------
1 | /*-------------------------------------------------------------------------
2 | *
3 | * pqformat.c
4 | * Routines for formatting and parsing frontend/backend messages
5 | *
6 | * Outgoing messages are built up in a StringInfo buffer (which is expansible)
7 | * and then sent in a single call to pq_putmessage. This module provides data
8 | * formatting/conversion routines that are needed to produce valid messages.
9 | * Note in particular the distinction between "raw data" and "text"; raw data
10 | * is message protocol characters and binary values that are not subject to
11 | * character set conversion, while text is converted by character encoding
12 | * rules.
13 | *
14 | * Incoming messages are similarly read into a StringInfo buffer, via
15 | * pq_getmessage, and then parsed and converted from that using the routines
16 | * in this module.
17 | *
18 | * These same routines support reading and writing of external binary formats
19 | * (typsend/typreceive routines). The conversion routines for individual
20 | * data types are exactly the same, only initialization and completion
21 | * are different.
22 | *
23 | *
24 | * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
25 | * Portions Copyright (c) 1994, Regents of the University of California
26 | *
27 | * src/backend/libpq/pqformat.c
28 | *
29 | *-------------------------------------------------------------------------
30 | */
31 | /*
32 | * INTERFACE ROUTINES
33 | * Message assembly and output:
34 | * pq_beginmessage - initialize StringInfo buffer
35 | * pq_sendbyte - append a raw byte to a StringInfo buffer
36 | * pq_sendint - append a binary integer to a StringInfo buffer
37 | * pq_sendint64 - append a binary 8-byte int to a StringInfo buffer
38 | * pq_sendfloat4 - append a float4 to a StringInfo buffer
39 | * pq_sendfloat8 - append a float8 to a StringInfo buffer
40 | * pq_sendbytes - append raw data to a StringInfo buffer
41 | * pq_sendcountedtext - append a counted text string (with character set conversion)
42 | * pq_sendtext - append a text string (with conversion)
43 | * pq_sendstring - append a null-terminated text string (with conversion)
44 | * pq_send_ascii_string - append a null-terminated text string (without conversion)
45 | * pq_endmessage - send the completed message to the frontend
46 | * Note: it is also possible to append data to the StringInfo buffer using
47 | * the regular StringInfo routines, but this is discouraged since required
48 | * character set conversion may not occur.
49 | *
50 | * typsend support (construct a bytea value containing external binary data):
51 | * pq_begintypsend - initialize StringInfo buffer
52 | * pq_endtypsend - return the completed string as a "bytea*"
53 | *
54 | * Special-case message output:
55 | * pq_puttextmessage - generate a character set-converted message in one step
56 | * pq_putemptymessage - convenience routine for message with empty body
57 | *
58 | * Message parsing after input:
59 | * pq_getmsgbyte - get a raw byte from a message buffer
60 | * pq_getmsgint - get a binary integer from a message buffer
61 | * pq_getmsgint64 - get a binary 8-byte int from a message buffer
62 | * pq_getmsgfloat4 - get a float4 from a message buffer
63 | * pq_getmsgfloat8 - get a float8 from a message buffer
64 | * pq_getmsgbytes - get raw data from a message buffer
65 | * pq_copymsgbytes - copy raw data from a message buffer
66 | * pq_getmsgtext - get a counted text string (with conversion)
67 | * pq_getmsgstring - get a null-terminated text string (with conversion)
68 | * pq_getmsgend - verify message fully consumed
69 | */
70 |
71 | #include "postgres_fe.h"
72 |
73 | #include "lib/stringinfo.h"
74 | #include "pg_logicaldecode.h"
75 |
76 | #include "utils.h"
77 |
78 | //#include
79 | //#include
80 | //#include
81 |
82 | //#include "libpq/libpq.h"
83 | //#include "libpq/pqformat.h"
84 | //#include "mb/pg_wchar.h"
85 |
86 | /* --------------------------------
87 | * pq_beginmessage - initialize for sending a message
88 | * --------------------------------
89 | */
90 | void
91 | pq_beginmessage(StringInfo buf, char msgtype)
92 | {
93 | initStringInfo(buf);
94 |
95 | /*
96 | * We stash the message type into the buffer's cursor field, expecting
97 | * that the pq_sendXXX routines won't touch it. We could alternatively
98 | * make it the first byte of the buffer contents, but this seems easier.
99 | */
100 | buf->cursor = msgtype;
101 | }
102 |
103 | /* --------------------------------
104 | * pq_sendbyte - append a raw byte to a StringInfo buffer
105 | * --------------------------------
106 | */
107 | void
108 | pq_sendbyte(StringInfo buf, int byt)
109 | {
110 | appendStringInfoCharMacro(buf, byt);
111 | }
112 |
113 | /* --------------------------------
114 | * pq_sendbytes - append raw data to a StringInfo buffer
115 | * --------------------------------
116 | */
117 | void
118 | pq_sendbytes(StringInfo buf, const char *data, int datalen)
119 | {
120 | appendBinaryStringInfo(buf, data, datalen);
121 | }
122 |
123 | /* --------------------------------
124 | * pq_sendcountedtext - append a counted text string (with character set conversion)
125 | *
126 | * The data sent to the frontend by this routine is a 4-byte count field
127 | * followed by the string. The count includes itself or not, as per the
128 | * countincludesself flag (pre-3.0 protocol requires it to include itself).
129 | * The passed text string need not be null-terminated, and the data sent
130 | * to the frontend isn't either.
131 | * --------------------------------
132 | */
133 | /*
134 | void
135 | pq_sendcountedtext(StringInfo buf, const char *str, int slen,
136 | bool countincludesself)
137 | {
138 | int extra = countincludesself ? 4 : 0;
139 | char *p;
140 |
141 | p = pg_server_to_client(str, slen);
142 | if (p != str)
143 | {
144 | slen = strlen(p);
145 | pq_sendint(buf, slen + extra, 4);
146 | appendBinaryStringInfo(buf, p, slen);
147 | pfree(p);
148 | }
149 | else
150 | {
151 | pq_sendint(buf, slen + extra, 4);
152 | appendBinaryStringInfo(buf, str, slen);
153 | }
154 | }
155 | */
156 | /* --------------------------------
157 | * pq_sendtext - append a text string (with conversion)
158 | *
159 | * The passed text string need not be null-terminated, and the data sent
160 | * to the frontend isn't either. Note that this is not actually useful
161 | * for direct frontend transmissions, since there'd be no way for the
162 | * frontend to determine the string length. But it is useful for binary
163 | * format conversions.
164 | * --------------------------------
165 | */
166 | /*
167 | void
168 | pq_sendtext(StringInfo buf, const char *str, int slen)
169 | {
170 | char *p;
171 |
172 | p = pg_server_to_client(str, slen);
173 | if (p != str)
174 | {
175 | slen = strlen(p);
176 | appendBinaryStringInfo(buf, p, slen);
177 | pfree(p);
178 | }
179 | else
180 | appendBinaryStringInfo(buf, str, slen);
181 | }
182 | */
183 |
184 | /* --------------------------------
185 | * pq_sendstring - append a null-terminated text string (with conversion)
186 | *
187 | * NB: passed text string must be null-terminated, and so is the data
188 | * sent to the frontend.
189 | * --------------------------------
190 | */
191 | /*
192 | void
193 | pq_sendstring(StringInfo buf, const char *str)
194 | {
195 | int slen = strlen(str);
196 | char *p;
197 |
198 | p = pg_server_to_client(str, slen);
199 | if (p != str)
200 | {
201 | slen = strlen(p);
202 | appendBinaryStringInfo(buf, p, slen + 1);
203 | pfree(p);
204 | }
205 | else
206 | appendBinaryStringInfo(buf, str, slen + 1);
207 | }
208 | */
209 |
210 | /* --------------------------------
211 | * pq_send_ascii_string - append a null-terminated text string (without conversion)
212 | *
213 | * This function intentionally bypasses encoding conversion, instead just
214 | * silently replacing any non-7-bit-ASCII characters with question marks.
215 | * It is used only when we are having trouble sending an error message to
216 | * the client with normal localization and encoding conversion. The caller
217 | * should already have taken measures to ensure the string is just ASCII;
218 | * the extra work here is just to make certain we don't send a badly encoded
219 | * string to the client (which might or might not be robust about that).
220 | *
221 | * NB: passed text string must be null-terminated, and so is the data
222 | * sent to the frontend.
223 | * --------------------------------
224 | */
225 | void
226 | pq_send_ascii_string(StringInfo buf, const char *str)
227 | {
228 | while (*str)
229 | {
230 | char ch = *str++;
231 |
232 | if (IS_HIGHBIT_SET(ch))
233 | ch = '?';
234 | appendStringInfoCharMacro(buf, ch);
235 | }
236 | appendStringInfoChar(buf, '\0');
237 | }
238 |
239 | /* --------------------------------
240 | * pq_sendint - append a binary integer to a StringInfo buffer
241 | * --------------------------------
242 | */
243 | void
244 | pq_sendint(StringInfo buf, int i, int b)
245 | {
246 | unsigned char n8;
247 | uint16 n16;
248 | uint32 n32;
249 |
250 | switch (b)
251 | {
252 | case 1:
253 | n8 = (unsigned char) i;
254 | appendBinaryStringInfo(buf, (char *) &n8, 1);
255 | break;
256 | case 2:
257 | n16 = htons((uint16) i);
258 | appendBinaryStringInfo(buf, (char *) &n16, 2);
259 | break;
260 | case 4:
261 | n32 = htonl((uint32) i);
262 | appendBinaryStringInfo(buf, (char *) &n32, 4);
263 | break;
264 | default:
265 | fprintf(stderr, "unsupported integer size %d", b);
266 | break;
267 | }
268 | }
269 |
270 | /* --------------------------------
271 | * pq_sendint64 - append a binary 8-byte int to a StringInfo buffer
272 | *
273 | * It is tempting to merge this with pq_sendint, but we'd have to make the
274 | * argument int64 for all data widths --- that could be a big performance
275 | * hit on machines where int64 isn't efficient.
276 | * --------------------------------
277 | */
278 | void
279 | pq_sendint64(StringInfo buf, int64 i)
280 | {
281 | uint32 n32;
282 |
283 | /* High order half first, since we're doing MSB-first */
284 | n32 = (uint32) (i >> 32);
285 | n32 = htonl(n32);
286 | appendBinaryStringInfo(buf, (char *) &n32, 4);
287 |
288 | /* Now the low order half */
289 | n32 = (uint32) i;
290 | n32 = htonl(n32);
291 | appendBinaryStringInfo(buf, (char *) &n32, 4);
292 | }
293 |
294 | /* --------------------------------
295 | * pq_sendfloat4 - append a float4 to a StringInfo buffer
296 | *
297 | * The point of this routine is to localize knowledge of the external binary
298 | * representation of float4, which is a component of several datatypes.
299 | *
300 | * We currently assume that float4 should be byte-swapped in the same way
301 | * as int4. This rule is not perfect but it gives us portability across
302 | * most IEEE-float-using architectures.
303 | * --------------------------------
304 | */
305 | void
306 | pq_sendfloat4(StringInfo buf, float4 f)
307 | {
308 | union
309 | {
310 | float4 f;
311 | uint32 i;
312 | } swap;
313 |
314 | swap.f = f;
315 | swap.i = htonl(swap.i);
316 |
317 | appendBinaryStringInfo(buf, (char *) &swap.i, 4);
318 | }
319 |
320 | /* --------------------------------
321 | * pq_sendfloat8 - append a float8 to a StringInfo buffer
322 | *
323 | * The point of this routine is to localize knowledge of the external binary
324 | * representation of float8, which is a component of several datatypes.
325 | *
326 | * We currently assume that float8 should be byte-swapped in the same way
327 | * as int8. This rule is not perfect but it gives us portability across
328 | * most IEEE-float-using architectures.
329 | * --------------------------------
330 | */
331 | void
332 | pq_sendfloat8(StringInfo buf, float8 f)
333 | {
334 | union
335 | {
336 | float8 f;
337 | int64 i;
338 | } swap;
339 |
340 | swap.f = f;
341 | pq_sendint64(buf, swap.i);
342 | }
343 |
344 | /* --------------------------------
345 | * pq_endmessage - send the completed message to the frontend
346 | *
347 | * The data buffer is pfree()d, but if the StringInfo was allocated with
348 | * makeStringInfo then the caller must still pfree it.
349 | * --------------------------------
350 | */
351 | /*
352 | void
353 | pq_endmessage(StringInfo buf)
354 | {
355 |
356 | (void) pq_putmessage(buf->cursor, buf->data, buf->len);
357 |
358 | pfree(buf->data);
359 | buf->data = NULL;
360 | }
361 | */
362 |
363 | /* --------------------------------
364 | * pq_begintypsend - initialize for constructing a bytea result
365 | * --------------------------------
366 | */
367 | void
368 | pq_begintypsend(StringInfo buf)
369 | {
370 | initStringInfo(buf);
371 | /* Reserve four bytes for the bytea length word */
372 | appendStringInfoCharMacro(buf, '\0');
373 | appendStringInfoCharMacro(buf, '\0');
374 | appendStringInfoCharMacro(buf, '\0');
375 | appendStringInfoCharMacro(buf, '\0');
376 | }
377 |
378 | /* --------------------------------
379 | * pq_endtypsend - finish constructing a bytea result
380 | *
381 | * The data buffer is returned as the palloc'd bytea value. (We expect
382 | * that it will be suitably aligned for this because it has been palloc'd.)
383 | * We assume the StringInfoData is just a local variable in the caller and
384 | * need not be pfree'd.
385 | * --------------------------------
386 | */
387 | /*
388 | bytea *
389 | pq_endtypsend(StringInfo buf)
390 | {
391 | bytea *result = (bytea *) buf->data;
392 |
393 | Assert(buf->len >= VARHDRSZ);
394 | SET_VARSIZE(result, buf->len);
395 |
396 | return result;
397 | }
398 | */
399 |
400 | /* --------------------------------
401 | * pq_puttextmessage - generate a character set-converted message in one step
402 | *
403 | * This is the same as the pqcomm.c routine pq_putmessage, except that
404 | * the message body is a null-terminated string to which encoding
405 | * conversion applies.
406 | * --------------------------------
407 | */
408 | /*
409 | void
410 | pq_puttextmessage(char msgtype, const char *str)
411 | {
412 | int slen = strlen(str);
413 | char *p;
414 |
415 | p = pg_server_to_client(str, slen);
416 | if (p != str)
417 | {
418 | (void) pq_putmessage(msgtype, p, strlen(p) + 1);
419 | pfree(p);
420 | return;
421 | }
422 | (void) pq_putmessage(msgtype, str, slen + 1);
423 | }
424 | */
425 |
426 | /* --------------------------------
427 | * pq_putemptymessage - convenience routine for message with empty body
428 | * --------------------------------
429 | */
430 | /*
431 | void
432 | pq_putemptymessage(char msgtype)
433 | {
434 | (void) pq_putmessage(msgtype, NULL, 0);
435 | }
436 | */
437 |
438 | /* --------------------------------
439 | * pq_getmsgbyte - get a raw byte from a message buffer
440 | * --------------------------------
441 | */
442 | int
443 | pq_getmsgbyte(StringInfo msg)
444 | {
445 | if (msg->cursor >= msg->len)
446 | {
447 | fprintf(stderr,"no data left in message");
448 | return -1;
449 | }
450 | return (unsigned char) msg->data[msg->cursor++];
451 | }
452 |
453 | /* --------------------------------
454 | * pq_getmsgint - get a binary integer from a message buffer
455 | *
456 | * Values are treated as unsigned.
457 | * --------------------------------
458 | */
459 | unsigned int
460 | pq_getmsgint(StringInfo msg, int b)
461 | {
462 | unsigned int result;
463 | unsigned char n8;
464 | uint16 n16;
465 | uint32 n32;
466 |
467 | switch (b)
468 | {
469 | case 1:
470 | pq_copymsgbytes(msg, (char *) &n8, 1);
471 | result = n8;
472 | break;
473 | case 2:
474 | pq_copymsgbytes(msg, (char *) &n16, 2);
475 | result = ntohs(n16);
476 | break;
477 | case 4:
478 | pq_copymsgbytes(msg, (char *) &n32, 4);
479 | result = ntohl(n32);
480 | break;
481 | default:
482 | fprintf(stderr, "unsupported integer size %d", b);
483 | result = 0; /* keep compiler quiet */
484 | break;
485 | }
486 | return result;
487 | }
488 |
489 | /* --------------------------------
490 | * pq_getmsgint64 - get a binary 8-byte int from a message buffer
491 | *
492 | * It is tempting to merge this with pq_getmsgint, but we'd have to make the
493 | * result int64 for all data widths --- that could be a big performance
494 | * hit on machines where int64 isn't efficient.
495 | * --------------------------------
496 | */
497 | int64
498 | pq_getmsgint64(StringInfo msg)
499 | {
500 | int64 result;
501 | uint32 h32;
502 | uint32 l32;
503 |
504 | pq_copymsgbytes(msg, (char *) &h32, 4);
505 | pq_copymsgbytes(msg, (char *) &l32, 4);
506 | h32 = ntohl(h32);
507 | l32 = ntohl(l32);
508 |
509 | result = h32;
510 | result <<= 32;
511 | result |= l32;
512 |
513 | return result;
514 | }
515 |
516 | /* --------------------------------
517 | * pq_getmsgfloat4 - get a float4 from a message buffer
518 | *
519 | * See notes for pq_sendfloat4.
520 | * --------------------------------
521 | */
522 | float4
523 | pq_getmsgfloat4(StringInfo msg)
524 | {
525 | union
526 | {
527 | float4 f;
528 | uint32 i;
529 | } swap;
530 |
531 | swap.i = pq_getmsgint(msg, 4);
532 | return swap.f;
533 | }
534 |
535 | /* --------------------------------
536 | * pq_getmsgfloat8 - get a float8 from a message buffer
537 | *
538 | * See notes for pq_sendfloat8.
539 | * --------------------------------
540 | */
541 | float8
542 | pq_getmsgfloat8(StringInfo msg)
543 | {
544 | union
545 | {
546 | float8 f;
547 | int64 i;
548 | } swap;
549 |
550 | swap.i = pq_getmsgint64(msg);
551 | return swap.f;
552 | }
553 |
554 | /* --------------------------------
555 | * pq_getmsgbytes - get raw data from a message buffer
556 | *
557 | * Returns a pointer directly into the message buffer; note this
558 | * may not have any particular alignment.
559 | * --------------------------------
560 | */
561 | const char *
562 | pq_getmsgbytes(StringInfo msg, int datalen)
563 | {
564 | const char *result;
565 |
566 | if (datalen < 0 || datalen > (msg->len - msg->cursor))
567 | {
568 | fprintf(stderr,"insufficient data left in message");
569 | return NULL;
570 | }
571 | result = &msg->data[msg->cursor];
572 | msg->cursor += datalen;
573 | return result;
574 | }
575 |
576 | /* --------------------------------
577 | * pq_copymsgbytes - copy raw data from a message buffer
578 | *
579 | * Same as above, except data is copied to caller's buffer.
580 | * --------------------------------
581 | */
582 | void
583 | pq_copymsgbytes(StringInfo msg, char *buf, int datalen)
584 | {
585 | if (datalen < 0 || datalen > (msg->len - msg->cursor))
586 | {
587 | fprintf(stderr, "insufficient data left in message");
588 | return;
589 | }
590 | memcpy(buf, &msg->data[msg->cursor], datalen);
591 | msg->cursor += datalen;
592 | }
593 |
594 | /* --------------------------------
595 | * pq_getmsgtext - get a counted text string (with conversion)
596 | *
597 | * Always returns a pointer to a freshly palloc'd result.
598 | * The result has a trailing null, *and* we return its strlen in *nbytes.
599 | * --------------------------------
600 | */
601 | /*
602 | char *
603 | pq_getmsgtext(StringInfo msg, int rawbytes, int *nbytes)
604 | {
605 | char *str;
606 | char *p;
607 |
608 | if (rawbytes < 0 || rawbytes > (msg->len - msg->cursor))
609 | ereport(ERROR,
610 | (errcode(ERRCODE_PROTOCOL_VIOLATION),
611 | errmsg("insufficient data left in message")));
612 | str = &msg->data[msg->cursor];
613 | msg->cursor += rawbytes;
614 |
615 | p = pg_client_to_server(str, rawbytes);
616 | if (p != str)
617 | *nbytes = strlen(p);
618 | else
619 | {
620 | p = (char *) palloc(rawbytes + 1);
621 | memcpy(p, str, rawbytes);
622 | p[rawbytes] = '\0';
623 | *nbytes = rawbytes;
624 | }
625 | return p;
626 | }
627 | */
628 |
629 | /* --------------------------------
630 | * pq_getmsgstring - get a null-terminated text string (with conversion)
631 | *
632 | * May return a pointer directly into the message buffer, or a pointer
633 | * to a palloc'd conversion result.
634 | * --------------------------------
635 | */
636 | /*
637 | const char *
638 | pq_getmsgstring(StringInfo msg)
639 | {
640 | char *str;
641 | int slen;
642 |
643 | str = &msg->data[msg->cursor];
644 |
645 | slen = strlen(str);
646 | if (msg->cursor + slen >= msg->len)
647 | ereport(ERROR,
648 | (errcode(ERRCODE_PROTOCOL_VIOLATION),
649 | errmsg("invalid string in message")));
650 | msg->cursor += slen + 1;
651 |
652 | return pg_client_to_server(str, slen);
653 | }
654 | */
655 |
656 | /* --------------------------------
657 | * pq_getmsgend - verify message fully consumed
658 | * --------------------------------
659 | */
660 | void
661 | pq_getmsgend(StringInfo msg)
662 | {
663 | if (msg->cursor != msg->len)
664 | {
665 | fprintf(stderr, "invalid message format");
666 | }
667 | }
668 |
669 |
670 |
--------------------------------------------------------------------------------
/dbsync/readcfg.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include "postgres_fe.h"
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include
12 | #include
13 |
14 | #include
15 | #include
16 |
17 | #include "readcfg.h"
18 | #include
19 |
20 | using namespace std;
21 |
22 | using std::string;
23 |
24 | Config::Config(const string &filename) : _conf(NULL) {
25 | if (filename != "") this->_conf = ini_load(filename.c_str());
26 | if (this->_conf == NULL) {
27 | //fprintf(stderr,"Failed to load config file\n");
28 | cout<<"Failed to load config file\n"<_conf) ini_free(this->_conf);
34 | }
35 |
36 | string Config::Get(const string &sec, const string &key,
37 | const string &defaultvalue) {
38 | string ret = defaultvalue;
39 | if ((key == "") || (sec == "")) return ret;
40 |
41 | if (this->_conf) {
42 | const char *tmp = ini_get(this->_conf, sec.c_str(), key.c_str());
43 | if (tmp) ret = tmp;
44 | }
45 | return ret;
46 | }
47 |
48 | bool Config::Scan(const string &sec, const string &key, const char *scanfmt,
49 | void *dst) {
50 | if ((key == "") || (sec == "")) return false;
51 |
52 | if (this->_conf) {
53 | return ini_sget(this->_conf, sec.c_str(), key.c_str(), scanfmt, dst);
54 | }
55 | return false;
56 | }
57 |
58 | bool to_bool(std::string str) {
59 | std::transform(str.begin(), str.end(), str.begin(), ::tolower);
60 | if ((str == "yes") || (str == "true") || (str == "y") || (str == "t") ||
61 | (str == "1")) {
62 | return true;
63 | } else {
64 | return false;
65 | }
66 | }
67 |
68 | void find_replace(string &str, const string &find, const string &replace) {
69 | if (find.empty()) return;
70 |
71 | size_t pos = 0;
72 |
73 | while ((pos = str.find(find, pos)) != string::npos) {
74 | str.replace(pos, find.length(), replace);
75 | pos += replace.length();
76 | }
77 | }
78 |
79 | void *
80 | init_config(char *cfgpath)
81 | {
82 | Config* s3cfg = NULL;
83 |
84 | s3cfg = new Config(cfgpath);
85 | if (!s3cfg || !s3cfg->Handle())
86 | {
87 | if (s3cfg)
88 | {
89 | delete s3cfg;
90 | s3cfg = NULL;
91 | }
92 | return NULL;
93 | }
94 |
95 | return s3cfg;
96 | }
97 |
98 | int
99 | get_config(void *cfg, char *sec, char* key, char **value)
100 | {
101 | Config* s3cfg = (Config *)cfg;
102 | string ssec;
103 | string skey;
104 | string rc;
105 |
106 | if (sec == NULL || key == NULL)
107 | return 1;
108 |
109 | ssec = string(sec);
110 | skey = string(key);
111 | rc = s3cfg->Get(ssec, skey, "");
112 | if (rc == "")
113 | return 1;
114 |
115 | *value = strdup(rc.c_str());
116 |
117 | return 0;
118 | }
119 |
120 |
--------------------------------------------------------------------------------
/dbsync/readcfg.h:
--------------------------------------------------------------------------------
1 | #ifndef _READCFG_H_
2 | #define _READCFG_H_
3 |
4 | #include "postgres_fe.h"
5 | #include "c.h"
6 |
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 |
13 | #include "ini.h"
14 |
15 | using std::string;
16 |
17 | #define uint64_t uint64
18 |
19 | class Config {
20 | public:
21 | Config(const string& filename);
22 | ~Config();
23 | string Get(const string& sec, const string& key,
24 | const string& defaultvalue);
25 | bool Scan(const string& sec, const string& key, const char* scanfmt,
26 | void* dst);
27 | void* Handle() { return (void*)this->_conf; };
28 |
29 | private:
30 | ini_t* _conf;
31 | };
32 |
33 | bool to_bool(std::string str);
34 |
35 | void find_replace(string& str, const string& find, const string& replace);
36 |
37 | #endif // _UTILFUNCTIONS_
38 |
--------------------------------------------------------------------------------
/dbsync/stringinfo.c:
--------------------------------------------------------------------------------
1 |
2 | #include "postgres_fe.h"
3 | #include "lib/stringinfo.h"
4 | #include "misc.h"
5 |
6 |
7 | /*
8 | * makeStringInfo
9 | *
10 | * Create an empty 'StringInfoData' & return a pointer to it.
11 | */
12 | StringInfo
13 | makeStringInfo(void)
14 | {
15 | StringInfo res;
16 |
17 | res = (StringInfo) palloc(sizeof(StringInfoData));
18 |
19 | initStringInfo(res);
20 |
21 | return res;
22 | }
23 |
24 | /*
25 | * initStringInfo
26 | *
27 | * Initialize a StringInfoData struct (with previously undefined contents)
28 | * to describe an empty string.
29 | */
30 | void
31 | initStringInfo(StringInfo str)
32 | {
33 | int size = 1024; /* initial default buffer size */
34 |
35 | str->data = (char *) palloc(size);
36 | str->maxlen = size;
37 | resetStringInfo(str);
38 | }
39 |
40 | /*
41 | * resetStringInfo
42 | *
43 | * Reset the StringInfo: the data buffer remains valid, but its
44 | * previous content, if any, is cleared.
45 | */
46 | void
47 | resetStringInfo(StringInfo str)
48 | {
49 | str->data[0] = '\0';
50 | str->len = 0;
51 | str->cursor = 0;
52 | }
53 |
54 | /*
55 | * appendStringInfo
56 | *
57 | * Format text data under the control of fmt (an sprintf-style format string)
58 | * and append it to whatever is already in str. More space is allocated
59 | * to str if necessary. This is sort of like a combination of sprintf and
60 | * strcat.
61 | */
62 | void
63 | appendStringInfo(StringInfo str, const char *fmt,...)
64 | {
65 | for (;;)
66 | {
67 | va_list args;
68 | int needed;
69 |
70 | /* Try to format the data. */
71 | va_start(args, fmt);
72 | needed = appendStringInfoVA(str, fmt, args);
73 | va_end(args);
74 |
75 | if (needed == 0)
76 | break; /* success */
77 |
78 | /* Increase the buffer size and try again. */
79 | enlargeStringInfo(str, needed);
80 | }
81 | }
82 |
83 | /*
84 | * appendStringInfoVA
85 | *
86 | * Attempt to format text data under the control of fmt (an sprintf-style
87 | * format string) and append it to whatever is already in str. If successful
88 | * return zero; if not (because there's not enough space), return an estimate
89 | * of the space needed, without modifying str. Typically the caller should
90 | * pass the return value to enlargeStringInfo() before trying again; see
91 | * appendStringInfo for standard usage pattern.
92 | *
93 | * XXX This API is ugly, but there seems no alternative given the C spec's
94 | * restrictions on what can portably be done with va_list arguments: you have
95 | * to redo va_start before you can rescan the argument list, and we can't do
96 | * that from here.
97 | */
98 | int
99 | appendStringInfoVA(StringInfo str, const char *fmt, va_list args)
100 | {
101 | int avail;
102 | size_t nprinted;
103 |
104 | Assert(str != NULL);
105 |
106 | /*
107 | * If there's hardly any space, don't bother trying, just fail to make the
108 | * caller enlarge the buffer first. We have to guess at how much to
109 | * enlarge, since we're skipping the formatting work.
110 | */
111 | avail = str->maxlen - str->len;
112 | if (avail < 16)
113 | return 32;
114 |
115 | nprinted = pvsnprintf(str->data + str->len, (size_t) avail, fmt, args);
116 |
117 | if (nprinted < (size_t) avail)
118 | {
119 | /* Success. Note nprinted does not include trailing null. */
120 | str->len += (int) nprinted;
121 | return 0;
122 | }
123 |
124 | /* Restore the trailing null so that str is unmodified. */
125 | str->data[str->len] = '\0';
126 |
127 | /*
128 | * Return pvsnprintf's estimate of the space needed. (Although this is
129 | * given as a size_t, we know it will fit in int because it's not more
130 | * than MaxAllocSize.)
131 | */
132 | return (int) nprinted;
133 | }
134 |
135 | /*
136 | * appendStringInfoString
137 | *
138 | * Append a null-terminated string to str.
139 | * Like appendStringInfo(str, "%s", s) but faster.
140 | */
141 | void
142 | appendStringInfoString(StringInfo str, const char *s)
143 | {
144 | appendBinaryStringInfo(str, s, strlen(s));
145 | }
146 |
147 | /*
148 | * appendStringInfoChar
149 | *
150 | * Append a single byte to str.
151 | * Like appendStringInfo(str, "%c", ch) but much faster.
152 | */
153 | void
154 | appendStringInfoChar(StringInfo str, char ch)
155 | {
156 | /* Make more room if needed */
157 | if (str->len + 1 >= str->maxlen)
158 | enlargeStringInfo(str, 1);
159 |
160 | /* OK, append the character */
161 | str->data[str->len] = ch;
162 | str->len++;
163 | str->data[str->len] = '\0';
164 | }
165 |
166 | /*
167 | * appendStringInfoSpaces
168 | *
169 | * Append the specified number of spaces to a buffer.
170 | */
171 | void
172 | appendStringInfoSpaces(StringInfo str, int count)
173 | {
174 | if (count > 0)
175 | {
176 | /* Make more room if needed */
177 | enlargeStringInfo(str, count);
178 |
179 | /* OK, append the spaces */
180 | while (--count >= 0)
181 | str->data[str->len++] = ' ';
182 | str->data[str->len] = '\0';
183 | }
184 | }
185 |
186 | /*
187 | * appendBinaryStringInfo
188 | *
189 | * Append arbitrary binary data to a StringInfo, allocating more space
190 | * if necessary.
191 | */
192 | void
193 | appendBinaryStringInfo(StringInfo str, const char *data, int datalen)
194 | {
195 | Assert(str != NULL);
196 |
197 | /* Make more room if needed */
198 | enlargeStringInfo(str, datalen);
199 |
200 | /* OK, append the data */
201 | memcpy(str->data + str->len, data, datalen);
202 | str->len += datalen;
203 |
204 | /*
205 | * Keep a trailing null in place, even though it's probably useless for
206 | * binary data. (Some callers are dealing with text but call this because
207 | * their input isn't null-terminated.)
208 | */
209 | str->data[str->len] = '\0';
210 | }
211 |
212 | /*
213 | * enlargeStringInfo
214 | *
215 | * Make sure there is enough space for 'needed' more bytes
216 | * ('needed' does not include the terminating null).
217 | *
218 | * External callers usually need not concern themselves with this, since
219 | * all stringinfo.c routines do it automatically. However, if a caller
220 | * knows that a StringInfo will eventually become X bytes large, it
221 | * can save some palloc overhead by enlarging the buffer before starting
222 | * to store data in it.
223 | *
224 | * NB: because we use repalloc() to enlarge the buffer, the string buffer
225 | * will remain allocated in the same memory context that was current when
226 | * initStringInfo was called, even if another context is now current.
227 | * This is the desired and indeed critical behavior!
228 | */
229 | void
230 | enlargeStringInfo(StringInfo str, int needed)
231 | {
232 | int newlen;
233 |
234 | /*
235 | * Guard against out-of-range "needed" values. Without this, we can get
236 | * an overflow or infinite loop in the following.
237 | */
238 | if (needed < 0) /* should not happen */
239 | {
240 | fprintf(stderr, "invalid string enlargement request size: %d", needed);
241 | return;
242 | }
243 | if (((Size) needed) >= (MaxAllocSize - (Size) str->len))
244 | {
245 | fprintf(stderr,"Cannot enlarge string buffer containing %d bytes by %d more bytes.",
246 | str->len, needed);
247 | return;
248 | }
249 |
250 | needed += str->len + 1; /* total space required now */
251 |
252 | /* Because of the above test, we now have needed <= MaxAllocSize */
253 |
254 | if (needed <= str->maxlen)
255 | return; /* got enough space already */
256 |
257 | /*
258 | * We don't want to allocate just a little more space with each append;
259 | * for efficiency, double the buffer size each time it overflows.
260 | * Actually, we might need to more than double it if 'needed' is big...
261 | */
262 | newlen = 2 * str->maxlen;
263 | while (needed > newlen)
264 | newlen = 2 * newlen;
265 |
266 | /*
267 | * Clamp to MaxAllocSize in case we went past it. Note we are assuming
268 | * here that MaxAllocSize <= INT_MAX/2, else the above loop could
269 | * overflow. We will still have newlen >= needed.
270 | */
271 | if (newlen > (int) MaxAllocSize)
272 | newlen = (int) MaxAllocSize;
273 |
274 | str->data = (char *) repalloc(str->data, newlen);
275 |
276 | str->maxlen = newlen;
277 | }
278 |
--------------------------------------------------------------------------------
/dbsync/test/decode_test.sql:
--------------------------------------------------------------------------------
1 |
2 | create schema test_case;
3 | set search_path=test_case;
4 |
5 | -- no index
6 | create table a(a int ,b text, c timestamptz);
7 |
8 | insert into a values(1,'test','1999-01-08 04:05:06');
9 | insert into a values(2,'test','1999-01-08 04:05:06');
10 | insert into a values(3,'test','1999-01-08 04:05:06');
11 |
12 | update a set b = 'test1';
13 | update a set b = 'test2' where a = 3;
14 |
15 | delete from a where a = 2;
16 | delete from a;
17 |
18 | -- primary key
19 | create table b(a int primary key ,b text, c timestamptz);
20 |
21 | insert into b values(1,'test','1999-01-08 04:05:06');
22 | insert into b values(2,'test','1999-01-08 04:05:06');
23 | insert into b values(3,'test','1999-01-08 04:05:06');
24 |
25 | update b set b = 'test1';
26 | update b set a = 5 where a = 1;
27 | update b set b = 'test2' where a = 3;
28 | update b set c = '1999-01-08 04:05:06' where a = 5;
29 | update b set a = 6, c = '1999-01-08 04:05:06' where a = 5;
30 | update b set a = 5, b = 't',c = '1999-01-08 04:05:06' where a = 6;
31 |
32 | delete from b where a = 2;
33 | delete from b;
34 |
35 | -- mprimary key
36 | create table c(a int ,b text, c timestamptz, d bigint, primary key(a,d));
37 |
38 | insert into c values(1,'test','1999-01-08 04:05:06',3);
39 | insert into c values(2,'test','1999-01-08 04:05:06',2);
40 | insert into c values(3,'test','1999-01-08 04:05:06',1);
41 |
42 | update c set b = 'test1';
43 | update c set a = 5 where a = 1;
44 | update c set b = null where a = 3;
45 | delete from c where a = 2;
46 |
47 | -- REPLICA index
48 | create table d(a int ,b text, c timestamptz, d bigint);
49 | CREATE UNIQUE INDEX idx_d_a_d ON d(a,d);
50 | alter table d ALTER COLUMN a set not null;
51 | alter table d ALTER COLUMN d set not null;
52 | alter table d REPLICA IDENTITY USING INDEX idx_d_a_d;
53 |
54 | insert into d values(1,'test','1999-01-08 04:05:06',3);
55 | insert into d values(2,'test','1999-01-08 04:05:06',2);
56 | insert into d values(3,'test','1999-01-08 04:05:06',1);
57 |
58 | update d set b = 'test1';
59 | update d set a = 5 where a = 1;
60 | update d set b = 'test2' where a = 3;
61 | update d set a = 5, b = 't',c = '1999-01-08 04:05:06' where a = 3;
62 | delete from d;
63 |
64 | -- full data
65 | create table e(a int ,b text, c timestamptz, d bigint);
66 | alter table e REPLICA IDENTITY FULL;
67 |
68 | insert into e values(1,'test','1999-01-08 04:05:06',3);
69 | insert into e values(2,'test','1999-01-08 04:05:06',2);
70 | insert into e values(3,'test','1999-01-08 04:05:06',1);
71 |
72 | update e set b = 'test1';
73 | update e set a = 5 where a = 1;
74 | update e set b = 'test2' where a = 3;
75 | update e set a = 5, b = 't',c = '1999-01-08 04:05:06' where a = 3;
76 |
77 | delete from e;
78 |
79 |
80 | -- full data and primary key
81 | create table f(a int primary key,b text, c timestamptz, d bigint);
82 | alter table f REPLICA IDENTITY FULL;
83 |
84 | insert into f values(1,'test','1999-01-08 04:05:06',3);
85 | insert into f values(2,'test','1999-01-08 04:05:06',2);
86 | insert into f values(3,'test','1999-01-08 04:05:06',1);
87 |
88 | update f set b = 'test1';
89 | update f set a = 5 where a = 1;
90 |
91 | alter table f REPLICA IDENTITY DEFAULT;
92 | update f set a = 7 where a = 2;
93 |
94 | update f set b = 'test2' where a = 3;
95 | update f set a = 6, b = 't',c = '1999-01-08 04:05:06' where a = 3;
96 |
97 | delete from f;
98 |
99 | -- data type
100 | create table test_data_type_1(a smallint,b integer,c bigint,d decimal,e numeric);
101 | insert into test_data_type_1 values(-32768, 2147483647, 9223372036854775807, 111.111, 111.111);
102 |
103 | create table test_data_type_2(a real,b double precision,c smallserial,d serial,e bigserial);
104 | insert into test_data_type_2 values(111.111, 111.111, 32767, 2147483647, 9223372036854775807);
105 |
106 | create table test_data_type_3(a money,b character varying(20),c character(20),d text,e char(20));
107 | insert into test_data_type_3 values('12.34', '12.34', '12.34', '12.34', '12.34');
108 |
109 | create table test_data_type_4(a bytea,b bytea,c bytea,d bytea,e bytea);
110 | insert into test_data_type_4 values('\\xDEADBEEF', '\\000', '0', '\\134', '\\176');
111 |
112 | create table test_data_type_5(a timestamp without time zone ,b timestamp with time zone,c timestamp,d time,e time with time zone);
113 | insert into test_data_type_5 values('1999-01-08 04:05:06', '1999-01-08 04:05:06 +8:00', '1999-01-08 04:05:06 -8:00', '1999-01-08 04:05:06 -8:00', '1999-01-08 04:05:06 -8:00');
114 |
115 | CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
116 | create table test_data_type_6(a boolean,b mood,c point,d line,e lseg);
117 | insert into test_data_type_6 values(TRUE, 'happy', '(1,1)', '{1,2,1}', '[(1,2),(2,1)]');
118 |
119 | create table test_data_type_7(a path,b path,c polygon,d circle,e circle);
120 | insert into test_data_type_7 values('[(1,3),(2,2)]', '((1,3),(2,2))', '((1,3),(2,2))', '<(2,2),2>', '((2,3),1)');
121 |
122 | create table test_data_type_8(a cidr,b cidr,c inet,d macaddr,e macaddr);
123 | insert into test_data_type_8 values('192.168.100.128/25', '2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128', '1.2.3.4', '08-00-2b-01-02-03', '08:00:2b:01:02:03');
124 |
125 | CREATE TABLE test_9 (a BIT(3), b BIT VARYING(5));
126 | INSERT INTO test_9 VALUES (B'101', B'00');
127 | INSERT INTO test_9 VALUES (B'10'::bit(3), B'101');
128 |
129 | CREATE TABLE test_10 (a tsvector, b tsvector,c tsquery, d tsquery);
130 | INSERT INTO test_10 VALUES ('a fat cat sat on a mat and ate a fat rat', 'a:1 fat:2 cat:3 sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10 fat:11 rat:12','fat & rat','Fat:ab & Cats');
131 |
132 | create extension "uuid-ossp";
133 | CREATE TABLE test_11 (a uuid, b uuid,c uuid, d uuid);
134 | INSERT INTO test_11 VALUES ('25285134-7314-11e5-8e45-d89d672b3560', 'c12a3d5f-53bb-4223-9fca-0af78b4d269f', 'cf16fe52-3365-3a1f-8572-288d8d2aaa46', '252852d8-7314-11e5-8e45-2f1f0837ccca');
135 |
136 | CREATE TABLE test_12 (a xml, b xml,c xml);
137 | INSERT INTO test_12 VALUES (xml 'bar', XMLPARSE (DOCUMENT 'Manual...'), XMLPARSE (CONTENT 'abcbarfoo'));
138 |
139 | CREATE TABLE test_13 (a xml, b xml,c xml);
140 | INSERT INTO test_13 VALUES ('{"reading": 1.230e-5}',
141 | '[1, 2, "foo", null]',
142 | '{"bar": "baz", "balance": 7.77, "active":false}');
143 |
144 | CREATE TABLE sal_emp_14 (
145 | name text,
146 | pay_by_quarter integer[],
147 | schedule text[][]
148 | );
149 |
150 | INSERT INTO sal_emp_14
151 | VALUES ('Bill',
152 | '{10000, 10000, 10000, 10000}',
153 | '{{"meeting", "lunch"}, {"training", "presentation"}}');
154 |
155 | INSERT INTO sal_emp_14
156 | VALUES ('Carol',
157 | '{20000, 25000, 25000, 25000}',
158 | '{{"breakfast", "consulting"}, {"meeting", "lunch"}}');
159 |
160 | INSERT INTO sal_emp_14
161 | VALUES ('Carol',
162 | ARRAY[20000, 25000, 25000, 25000],
163 | ARRAY[['breakfast', 'consulting'], ['meeting', 'lunch']]);
164 |
165 | CREATE TYPE complex AS (
166 | r double precision,
167 | i double precision
168 | );
169 |
170 | CREATE TYPE inventory_item AS (
171 | name text,
172 | supplier_id integer,
173 | price numeric
174 | );
175 |
176 | CREATE TABLE on_hand_15 (
177 | item inventory_item,
178 | count integer
179 | );
180 |
181 | INSERT INTO on_hand_15 VALUES (ROW('fuzzy dice', 42, 1.99), 1000);
182 |
183 | CREATE TABLE reservation_16 (room int, during tsrange);
184 | INSERT INTO reservation_16 VALUES
185 | (1108, '[2010-01-01 14:30, 2010-01-01 15:30)');
186 |
187 | CREATE TYPE floatrange AS RANGE (
188 | subtype = float8,
189 | subtype_diff = float8mi
190 | );
191 |
192 | create table t_range_16(a floatrange);
193 | insert into t_range_16 values('[1.234, 5.678]');
194 |
195 | create extension hstore;
196 | create table hstore_test_17(item_id serial, data hstore);
197 | INSERT INTO hstore_test_17 (data) VALUES ('"key1"=>"value1", "key2"=>"value2", "key3"=>"value3"');
198 | UPDATE hstore_test_17 SET data = delete(data, 'key2');
199 | UPDATE hstore_test_17 SET data = data || '"key4"=>"some value"'::hstore;
200 |
201 | CREATE EXTENSION postgis;
202 | CREATE EXTENSION postgis_topology;
203 | CREATE EXTENSION fuzzystrmatch;
204 | CREATE EXTENSION postgis_tiger_geocoder;
205 |
206 | create table test_18 (myID int4, pt geometry, myName varchar );
207 | insert into test_18 values (1, 'POINT(0 0)', 'beijing' );
208 | insert into test_18 values (2, 'MULTIPOINT(1 1, 3 4, -1 3)', 'shanghai' );
209 | insert into test_18 values (3, 'LINESTRING(1 1, 2 2, 3 4)', 'tianjin' );
210 | insert into test_18 values (3, 'POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))', 'tianjin' );
211 | insert into test_18 values (3, 'MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))', 'tianjin' );
212 | insert into test_18 values (3, 'MULTILINESTRING((1 1, 2 2, 3 4),(2 2, 3 3, 4 5))', 'tianjin' );
213 |
214 | insert into test_18 values (3, '01060000000200000001030000000200000005000000000000000000000000000000000000000000000000001040000000000000000000000000000010400000000000001040000000000000000000000000000010400000000000000000000000000000000005000000000000000000F03F000000000000F03F0000000000000040000000000000F03F00000000000000400000000000000040000000000000F03F0000000000000040000000000000F03F000000000000F03F01030000000100000005000000000000000000F0BF000000000000F0BF000000000000F0BF00000000000000C000000000000000C000000000000000C000000000000000C0000000000000F0BF000000000000F0BF000000000000F0BF', 'tianjin' );
215 |
216 | -- m sql in a tran
217 | create table msql(a int primary key,b text, c timestamptz);
218 |
219 | begin;
220 | insert into msql values(1,'test','1999-01-08 04:05:06');
221 | insert into msql values(2,'test','1999-01-08 04:05:06');
222 | insert into msql values(3,'test','1999-01-08 04:05:06');
223 | update msql set b = 'test' where a = 1;
224 | delete from msql where a = 3;
225 | commit;
226 |
227 | -- alter table
228 | create table msql_1(a int primary key,b text, c timestamptz);
229 |
230 | insert into msql_1 values(1,'test','1999-01-08 04:05:06');
231 | alter table msql_1 add COLUMN d int;
232 | insert into msql_1 values(2,'test','1999-01-08 04:05:06',1);
233 | alter table msql_1 drop COLUMN b;
234 | insert into msql_1 values(3,'1999-01-08 04:05:06',2);
235 |
236 | update msql_1 set c = '1999-01-08 04:05:07';
237 | delete from msql_1;
238 |
239 | -- alter table in a tran
240 | create table msql_2(a int primary key,b text, c timestamptz);
241 | begin;
242 | insert into msql_2 values(1,'test','1999-01-08 04:05:06');
243 | alter table msql_2 add COLUMN d int;
244 | insert into msql_2 values(2,'test','1999-01-08 04:05:06',1);
245 | alter table msql_2 drop COLUMN b;
246 | insert into msql_2 values(3,'1999-01-08 04:05:06',2);
247 | update msql_2 set c = '1999-01-08 04:05:07';
248 | commit;
249 |
250 |
251 | -- alter table drop pk
252 | create table msql_3(a int primary key,b text, c timestamptz);
253 | begin;
254 | insert into msql_3 values(1,'test','1999-01-08 04:05:06');
255 | insert into msql_3 values(5,'test','1999-01-08 04:05:06');
256 | alter table msql_3 add COLUMN d int;
257 | insert into msql_3 values(2,'test','1999-01-08 04:05:06',1);
258 | alter table msql_3 drop COLUMN a;
259 | insert into msql_3 values('test','1999-01-08 04:05:06',2);
260 | delete from msql_3;
261 | commit;
262 |
263 | -- SERIAL
264 | CREATE TABLE seq_test
265 | (
266 | id SERIAL primary key ,
267 | name text
268 | ) ;
269 |
270 | insert into seq_test (name) values('test');
271 |
272 | -- toast
273 | -- create table t_kenyon(id int,vname varchar(48),remark text);
274 | -- select oid,relname,reltoastrelid from pg_class where relname = 't_kenyon';
275 | -- insert into t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
276 | -- insert into t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
277 | -- insert into t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
278 | -- insert into t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
279 | -- insert into t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',10000);
280 | -- insert into t_kenyon select generate_series(7,8),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',20000);
281 |
282 | -- utf8
283 | create table chinese_text(t text);
284 | insert into chinese_text values('微软 Surface Pro 4 中国开放预售 价格公布');
285 | insert into chinese_text values('\');
286 | insert into chinese_text values('\\');
287 | insert into chinese_text values('\\\');
288 | insert into chinese_text values('///');
289 | insert into chinese_text values('//');
290 | insert into chinese_text values('/');
291 | insert into chinese_text values('''');
292 | insert into chinese_text values('"''"');
293 |
294 | -- bug extra_float_digits default 3
295 | create table tf(c1 float4, c2 float8 ,c3 numeric);
296 | insert into tf values (1.5555555555555555555555,1.5555555555555555555555,1.5555555555555555555555);
297 |
298 | drop schema test_case cascade;
299 |
--------------------------------------------------------------------------------
/dbsync/utils.h:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ifndef PG_UTILS_H
4 | #define PG_UTILS_H
5 |
6 | #include "postgres_fe.h"
7 |
8 | #include "lib/stringinfo.h"
9 | #include "lib/stringinfo.h"
10 | #include "common/fe_memutils.h"
11 |
12 | //#include "libpq-fe.h"
13 |
14 | #include "access/transam.h"
15 | #include "libpq/pqformat.h"
16 | #include "pqexpbuffer.h"
17 |
18 |
19 |
20 | extern void pq_copymsgbytes(StringInfo msg, char *buf, int datalen);
21 | extern void fe_sendint64(int64 i, char *buf);
22 | extern PGconn *GetConnection(char *connection_string);
23 | extern int64 feGetCurrentTimestamp(void);
24 | extern bool feTimestampDifferenceExceeds(int64 start_time,
25 | int64 stop_time,
26 | int msec);
27 | extern void feTimestampDifference(int64 start_time, int64 stop_time,
28 | long *secs, int *microsecs);
29 | extern int64 fe_recvint64(char *buf);
30 | extern int getopt(int nargc, char *const * nargv, const char *ostr);
31 |
32 |
33 | #endif
34 |
35 |
36 |
--------------------------------------------------------------------------------
/doc/design.md:
--------------------------------------------------------------------------------
1 | # PostgreSQL 增量同步方案详细设计
2 |
3 | ## 一:方案目的
4 |
5 | 通过PG实例的增量数据同步方案,解决PG在数据迁移中需要进行全量同步,需要长时间停服务的问题.
6 | 通过该方案,打通PG和其他数据产品间的数据通道,做到PG和其他数据产品和异构数据库间的实时的同步.
7 |
8 | ## 二:增量同步方案的技术背景
9 |
10 | 该方案基于PostgreSQL 9.4版本的逻辑流复制技术.
11 | PG9.4 可以做到,利用逻辑流复制,把表的增量数据,以自定义格式的形式组织起来被客户端订阅.
12 | 自定义格式的增量数据是逻辑流复制的关键,通过PG内核给出了开放的接口(5个回调函数),可以把增量数据以任意的形式输出.
13 | PG的客户端根据需要按照服务端规定的格式解析,就能得到完整的增量数据.
14 |
15 | ## 三:订阅增量数据的订阅规则
16 |
17 | PG增量同步方案只能获取对应DB中表的变化信息.
18 | 一张表能够被逻辑流复制订阅,需要满足下列三个条件之一
19 | 1)这张表的流复制方式为全复制,即 REPLICA = FULL
20 | 可以通过DDL语句 alter table t REPLICA FULL 定制对应的表
21 | 这个选项使得对应表中每一行的变化数据被完整的记录到WAL中.所以带来了较多的IO负担.
22 | 2)这张表具有主键约束
23 | 有主键的表的变化信息会被记录到WAL中,相对于REPLICA FULL模式,old row没有变化的列将不会被记录到WAL中,对IO的影响相对FULL更小.
24 | 3)为该表指定一个非空的唯一索引,作为REPLICA INDEX.
25 | 可以通过DDL来定制相应的表
26 | CREATE UNIQUE INDEX idx ON d(a);
27 | alter table t ALTER COLUMN a set not null;
28 | alter table t REPLICA IDENTITY USING INDEX idx;
29 | 该模式记录到WAL中的数据和主键模式相当.
30 | 可以在pg_class的 relreplident 列中看到对应的表处于什么模式.
31 | PG的流复制的规则类似于MYSQL binlog模式的row模式,对于用户相对于更加灵活一些.
32 | 可以阅读相关文档,了解细节信息.
33 | 1 http://www.postgresql.org/docs/9.4/static/sql-altertable.html#SQL-CREATETABLE-REPLICA-IDENTITY
34 | 2 http://www.postgresql.org/docs/9.4/static/logicaldecoding.html
35 |
36 | ## 四:架构解析
37 |
38 | PG的增量方案架构上分为两个大的部分
39 | 1 服务器端
40 | 在被订阅的服务器端,嵌入一个用于 decode 的插件--ali_decoding
41 | 该插件实现了流复制的自定义数据流格式,用户使用pg_create_logical_replication_slot函数创建 logical replication 时指定插件 ali_decoding 和逻辑 slot 名.
42 | 在客户端使用 START_REPLICATION 命令开启对应的流复制传输后,服务器端会有一个backend 进程加载该插件(ali_decoding)开始向用户传输增量数据.
43 |
44 | 2 客户端
45 | START_REPLICATION 命令需要指定对应的逻辑 slot 名和位点.
46 | 所谓的位点是PG WAL日志的日志名和日志文件的偏移的组合.
47 | PG的位点概念对于物理流复制和逻辑流复制是统一的.
48 | 1) PG对应逻辑 slot 的位点信息保存在数据库端,具有断点续传特性.
49 | 2) 除非需要跳过某部分数据,则使用 0/0 去请求拉取对应 slot 的逻辑日志.
50 | 3) PG的位点不对应时间点.
51 |
52 | 对于客户端,在建立连接后,读到的增量信息以消息的形式出现,可以分为三类信息
53 | 1) 事务开始,begin
54 | 2) 事务结束,commit
55 | 3) 事务中单个表的变化信息,分为
56 | a) insert
57 | b) update
58 | c) delete
59 | 根据表 REPLICA 的状态不同,各类DML收到的信息略有变化.
60 |
61 | ## 五:编译和使用
62 |
63 | ### 1 编译
64 |
65 | 1 编译机上下载安装 PG94 或更高版本的二进制,或用源码安装.
66 | 2 使用软链接或别的方式,把对应版本的 pg_config 链接到公共目录
67 | 例: ln -s /u01/pgsql_20150924/bin/pg_config /usr/bin/ 或
68 | export PATH=/u01/pgsql_20150924/bin
69 | 3 下载服务器端和客户端代码,make;make install;
70 |
71 | ### 2 使用
72 |
73 | 1 使用SQL或 demo 中的API创建 logical slot
74 | 例: SELECT * FROM pg_create_logical_replication_slot('regression_slot', 'ali_decoding');
75 |
76 | 2 可以用相应的 SQL 语句查看创建好的 slot
77 | 例: SELECT * FROM pg_replication_slots;
78 |
79 | 3 在客户端目录下,编辑 demo.cpp 填入需要拉取增量的服务器的连接参数,并重新编译成新的demo.
80 | 执行demo.
81 | 对应的增量信息会输出到客户端.
82 | 可以参考 out_put_decode_message 解析和读取增量消息中的数据.
83 |
84 | ## 六:限制
85 | 1 以 ctid 为条件的更新语句,在表处于 REPLICA FULL 时,update 语句无法完整还原.
--------------------------------------------------------------------------------
/doc/mysql2gp.md:
--------------------------------------------------------------------------------
1 | # mysql2gp 使用和部署说明
2 |
3 | ## 一 mysql2gp 介绍
4 | mysql2gp 实现了从 MySQL 中迁移增量数据到 PostgreSQL 或 Greenplum
5 |
6 | 其中增量数据来自于 MySQL 的 binlog, 结合全量数据迁移工具 mysql2pgsql 可以把 MySQL 中的数据完整的迁移到 PostgreSQL Greenplum 中,且保持准实时同步。
7 |
8 | ### 1.1 支持特性和限制
9 | 1 支持拉取 MySQL 5.1 5.5 5.6 5.7 版本的 binlog,需要 binlog 相关参数
10 |
11 | binlog_format = ROW
12 | binlog_row_image = FULL
13 |
14 | 2 支持同步指定表的各类数据变化到目标DB中,包括对应行的 insert update delete。
15 |
16 | 3 数据同步的表需要有单列主键。
17 |
18 | 4 支持对主键进行修改。
19 |
20 | 5 暂时不支持异构数据库的 DDL 同步。
21 |
22 | 6 支持指定表镜像方式同步到 PostgreSQL 或 Greenplum(配置文件方式)。
23 |
24 | 7 支持指定模式的表同步。
25 |
26 | ### 1.2 mysql2gp 实现架构
27 | 简单的说,mysql2gp 的实现方式是:
28 |
29 | 1 在客户端主机(也可以部署在其他主机)上启动一个临时 PG 数据库,用于临时存放从 MySQL 拉去到的 binlog 数据。
30 |
31 | 2 binlog_miner 从源 MySQL 的一个 binlog 文件开始,拉取和解析 binlog 并存放到临时 PG 中。
32 |
33 | 3 binlog_loader 从临时 PG 中读取增量数据,并做适当的处理,最终批量写入到目标 PostgreSQL 或 Greenplum 中去。
34 |
35 | ### 1.3 mysql2gp 模块介绍
36 |
37 | mysql2gp 分为5个部分
38 | 1 binlog_miner 用于拉取目标库中的 binlog, 并保存到临时 DB 中。
39 |
40 | 2 binlog_loader 用于读取临时 DB 中的 binlog 数据并加载到目标库中。
41 |
42 | 3 my.cfg 配置文件,设置需要同步数据的源和目标数据库的链接信息和相关参数。
43 |
44 | 4 loader_table_list.txt 配置文件,设置需要同步的表名列表,用回车符隔开。
45 |
46 | 5 临时 DB,用户保存增量数据的临时数据库。建议和 binlog_miner binlog_loader 部署在同一个主机。
47 |
48 | ## 二 mysql2gp 部署
49 | 建议临时 DB 和客户端二进制部署在同主机
50 |
51 | 部署步骤:
52 |
53 | ### 2.1 部署临时 PG DB
54 | 在目标主机部署一个临时 PG DB 用户存放临时数据,主机需要为临时数据预留足够的保存增量数据的空间。部署完成后获得一个连接临时 PG DB 的连接串,如 “dbname=test port=5432 user=test password=pgsql”
55 |
56 | ### 2.2 配置文件
57 | #### 2.2.1 MySQL 相关
58 |
59 | my.cnf
60 |
61 | ```
62 | [src.mysql]
63 | host = "192.168.1.1"
64 | port = "3301"
65 | user = "test"
66 | password = "123456"
67 | db = "test"
68 | encodingdir = "share"
69 | encoding = "utf8"
70 | binlogfile = "mysql-bin.000001"
71 | ```
72 |
73 | 注意:
74 |
75 | 1 MySQL 的连接信息需要有 select 权限和拉取 binlog 的权限。
76 |
77 | 2 binlogfile 为读取 binlog 的启始文件,必须设置。该配置和全量数据同步工具配合使用。
78 | 通常在开始同步全量 MySQL 数据时记录当前正在写的 binlog 文件名,并配置到 my.cnf 中。
79 |
80 | #### 2.2.2 临时数据库
81 |
82 | my.cnf
83 |
84 | ```
85 | [local.pgsql]
86 | connect_string = "dbname=test port=5432 user=test password=pgsql"
87 |
88 | ```
89 |
90 | 注意:
91 |
92 | 1 连接本地数据库可以不指定 host 信息,这样的链接模式效率较高。
93 |
94 |
95 | #### 2.2.3 目的数据库
96 |
97 | my.cnf
98 |
99 | ```
100 | [desc.pgsql]
101 | connect_string = "host=192.167.1.2 dbname=postgres port=5432 user=test password=pgsql"
102 | target_schema = "test"
103 |
104 | ```
105 |
106 | 注意:
107 |
108 | 1 target_schema 用于指定目标表存在的 schema,也可以不指定,不指时默认 schema 为 public。
109 |
110 |
111 | #### 2.2.4 设置需要同步的表
112 |
113 | 1 my.cnf
114 |
115 | ```
116 | [binlogloader]
117 | loader_table_list = "loader_table_list.txt"
118 |
119 | ```
120 |
121 | 2 loader_table_list.txt
122 |
123 | ```
124 | a
125 | b
126 | ```
127 |
128 | ### 2.3 启动同步进程
129 |
130 | #### 2.3.1 启动 binlog 拉取进程
131 |
132 | 推荐命令行:
133 |
134 | nohup ./binlog_miner 1>minner.log 2>&1 &
135 |
136 | #### 2.3.2 启动 binlog 写入进程
137 |
138 | 推荐命令行:
139 |
140 | nohup ./binlog_loader 1>loader.log 2>&1 &
141 |
--------------------------------------------------------------------------------
/doc/mysql2pgsql_ch.md:
--------------------------------------------------------------------------------
1 | ## mysql2pgsql
2 |
3 | 工具 mysql2pgsql 支持不落地的把 MYSQL 中的表迁移到 HybridDB/Greenplum Database/PostgreSQL/PPAS。此工具的原理是,同时连接源端 mysql 数据库和目的端数据库,从 mysql 库中通过查询得到要导出的数据,然后通过 COPY 命令导入到目的端。此工具支持多线程导入(每个工作线程负责导入一部分数据库表)。
4 |
5 | ## 参数配置
6 |
7 | 修改配置文件 my.cfg、配置源和目的库连接信息。
8 |
9 | - 源库 mysql 的连接信息如下:
10 |
11 | **注意:**源库 mysql 的连接信息中,用户需要有对所有用户表的读权限。
12 |
13 | ```
14 | [src.mysql]
15 | host = "192.168.1.1"
16 | port = "3306"
17 | user = "test"
18 | password = "test"
19 | db = "test"
20 | encodingdir = "share"
21 | encoding = "utf8"
22 | ```
23 |
24 | - 目的库 pgsql (包括 PostgreSQL、PPAS 和 HybridDB for PostgreSQL )的连接信息如下:
25 |
26 | **注意:**目的库 pgsql 的连接信息,用户需要对目标表有写的权限。
27 |
28 | ```
29 | [desc.pgsql]
30 | connect_string = "host=192.168.1.1 dbname=test port=5888 user=test password=pgsql"
31 | ```
32 |
33 | ## mysql2pgsql 用法
34 |
35 | mysql2pgsql 的用法如下所示:
36 |
37 | ```
38 | ./mysql2pgsql -l -d -n -j -s
39 |
40 | ```
41 |
42 | 参数说明:
43 |
44 | - -l:可选参数,指定一个文本文件,文件中含有需要同步的表;如果不指定此参数,则同步配置文件中指定数据库下的所有表。``````为一个文件名,里面含有需要同步的表集合以及表上查询的条件,其内容格式示例如下:
45 |
46 | ```
47 | table1 : select * from table_big where column1 < '2016-08-05'
48 | table2 :
49 | table3
50 | table4 : select column1, column2 from tableX where column1 != 10
51 | table5 : select * from table_big where column1 >= '2016-08-05'
52 | ```
53 |
54 | - -d:可选参数,表示只生成目的表的建表 DDL 语句,不实际进行数据同步。
55 |
56 | - -n:可选参数,需要与-d一起使用,指定在 DDL 语句中不包含表分区定义。
57 |
58 | - -j:可选参数,指定使用多少线程进行数据同步;如果不指定此参数,会使用 5 个线程并发。
59 |
60 | - -s:可选参数,指定目标表的schema,一次命令只能指定一个schema。如果不指定此参数,则数据会导入到public下的表。
61 |
62 | ### 典型用法
63 |
64 | #### 全库迁移
65 |
66 | 全库迁移的操作步骤如下所示:
67 |
68 | 1\. 通过如下命令,获取目的端对应表的 DDL。
69 |
70 | ```
71 | ./mysql2pgsql -d
72 | ```
73 |
74 | 2\. 根据这些 DDL,再加入 distribution key 等信息,在目的端创建表。
75 |
76 | 3\. 执行如下命令,同步所有表:
77 |
78 | ```
79 | ./mysql2pgsql
80 | ```
81 |
82 | 此命令会把配置文件中所指定数据库中的所有 mysql 表数据迁移到目的端。过程中使用 5 个线程(即缺省线程数为 5),读取和导入所有涉及的表数据。
83 |
84 | #### 部分表迁移
85 |
86 | 1\. 编辑一个新文件 tab_list.txt,放入如下内容:
87 |
88 | ```
89 | t1
90 | t2 : select * from t2 where c1 > 138888
91 | ```
92 |
93 | 2\. 执行如下命令,同步指定的 t1 和 t2 表(注意 t2 表只迁移符合 c1 > 138888 条件的数据):
94 |
95 | ```
96 | ./mysql2pgsql -l tab_list.txt
97 | ```
98 |
99 | ## mysql2pgsql 二进制安装包下载
100 |
101 | 下载地址:单击[这里](https://github.com/aliyun/rds_dbsync/releases)。
102 |
103 | ## mysql2pgsql 源码编译说明
104 |
105 | 查看源码编译说明,单击[这里](https://github.com/aliyun/rds_dbsync/blob/master/README.md)。
106 |
--------------------------------------------------------------------------------
/doc/mysql2pgsql_en.md:
--------------------------------------------------------------------------------
1 | ## mysql2pgsql Import data from MySQL
2 |
3 | The mysql2pgsql tool supports migrating tables in MySQL to HybridDB for PostgreSQL, Greenplum Database, PostgreSQL, or PPAS without storing the data separately. This tool connects to the source MySQL database and the target database at the same time, querries and retrieves the data to be exported in the MySQL database, and then imports the data to the target database by using the COPY command. It supports multithread import (every worker thread is in charge of importing a part of database tables).
4 |
5 |
6 |
7 | ## Parameters configuration
8 |
9 | Modify the “my.cfg” configuration file, and configure the source and target database connection information.
10 |
11 | - The connection information of the source MySQL database is as follows:
12 |
13 | **Note:** You need to have the read permission on all user tables in the source MySQL database connection information.
14 |
15 | ```
16 | [src.mysql]
17 | host = "192.168.1.1"
18 | port = "3306"
19 | user = "test"
20 | password = "test"
21 | db = "test"
22 | encodingdir = "share"
23 | encoding = "utf8"
24 | ```
25 |
26 | - The connection information of the target PostgreSQL database (including PostgreSQL, PPAS and HybridDB for PostgreSQL) is as follows:
27 |
28 | **Note:** You need to have the write permission on the target table in the target PostgreSQL database.
29 |
30 |
31 | ```
32 | [desc.pgsql]
33 | connect_string = "host=192.168.1.1 dbname=test port=5888 user=test password=pgsql"
34 | ```
35 |
36 | ## mysql2pgsql Usage discription
37 |
38 | The usage of mysql2pgsql is described as follows:
39 |
40 |
41 | ```
42 | ./mysql2pgsql -l -d -n -j -s
43 |
44 | ```
45 |
46 | Parameter descriptions:
47 |
48 | - -l: Optional parameter, used to specify a text file that contains tables to be synchronized. If this parameter is not specified, all the tables in the database specified in the configuration file are synchronized. is a file name. The file contains tables set to be synchronized and query conditions on the tables. An example of the content format is shown as follows:
49 |
50 |
51 | ```
52 | table1 : select * from table_big where column1 < '2016-08-05'
53 | table2 :
54 | table3
55 | table4 : select column1, column2 from tableX where column1 != 10
56 | table5 : select * from table_big where column1 >= '2016-08-05'
57 | ```
58 |
59 | - -d: Optional parameter, indicating to only generate the tabulation DDL statement of the target table without performing actual data synchronization.
60 |
61 | - -j: Optional parameter, specifying the number of threads used for data synchronization. If this parameter is not specified, five threads are used concurrently.
62 |
63 |
64 | ### Typical usage
65 |
66 | #### Full-database migration
67 |
68 | The procedure is as follows:
69 |
70 | 1\. Run the following command to get the DDL statements of the corresponding table on the target end:
71 |
72 |
73 | ```
74 | ./mysql2pgsql -d
75 | ```
76 |
77 | 2\. Create a table on the target based on these DDL statements with the distribution key information added.
78 |
79 | 3\. Run the following command to synchronize all tables:
80 |
81 |
82 | ```
83 | ./mysql2pgsql
84 | ```
85 |
86 | This command migrates the data from all MySQL tables in the database specified in the configuration file to the target. Five threads are used during the process (the default thread number is five) to read and import the data from all tables involved.
87 |
88 | #### Partial table migration
89 |
90 | The procedure is as follows:
91 |
92 | 1\. Create a new file (tab_list.txt) and insert the following content:
93 |
94 |
95 | ```
96 | t1
97 | t2 : select * from t2 where c1 > 138888
98 | ```
99 |
100 | 2\. Run the following command to synchronize the specified t1 and t2 tables:
101 |
102 |
103 | ```
104 | ./mysql2pgsql -l tab_list.txt
105 | ```
106 |
107 | **Note:** For the t2 table, only the data that meets the c1 > 138888 condition is migrated.
108 |
109 | ## Download and instructions
110 |
111 | [Download the binary installer of mysql2pgsql](https://github.com/aliyun/rds_dbsync/releases)
112 |
113 | ## rds_dbsync project
114 |
115 | [View the mysql2pgsql source code compilation instructions](https://github.com/aliyun/rds_dbsync/blob/master/README.md)
116 |
--------------------------------------------------------------------------------
/doc/pgsql2pgsql_ch.md:
--------------------------------------------------------------------------------
1 | # pgsql2pgsql
2 | 工具 pgsql2pgsql 支持不落地的把 GreenPlum/PostgreSQL/PPAS 中的表迁移到 GreenPlum/PostgreSQL/PPAS
3 |
4 | # pgsql2pgsql 支持的功能
5 |
6 | 1 PostgreSQL/PPAS/GreenPlum 全量数据迁移到 PostgreSQL/PPAS/GreenPlum
7 |
8 | 2 PostgreSQL/PPAS(版本大于9.4) 全量+增量迁移到 PostgreSQL/PPAS
9 |
10 | # 参数配置
11 | 修改配置文件 my.cfg,配置源和目的库连接信息
12 |
13 | 1. 源库 pgsql 连接信息
14 | [src.pgsql]
15 | connect_string = "host=192.168.1.1 dbname=test port=5888 user=test password=pgsql"
16 |
17 | 2. 本地临时DB pgsql 连接信息
18 | [local.pgsql]
19 | connect_string = "host=192.168.1.1 dbname=test port=5888 user=test2 password=pgsql"
20 |
21 | 3. 目的库 pgsql 连接信息
22 | [desc.pgsql]
23 | connect_string = "host=192.168.1.1 dbname=test port=5888 user=test3 password=pgsql"
24 |
25 |
26 | #注意
27 | 1. 如果要做增量数据同步,连接源库需要有创建 replication slot 的权限
28 | 2. 源库 pgsql 的连接信息中,用户最好是对应 DB 的 owner
29 | 3. 目的库 pgsql 的连接信息,用户需要对目标表有写权限
30 | 4. PostgreSQL 9.4 以及以上的版本因为支持逻辑流复制,所以支持作为数据源的增量迁移。打开下列内核参数才能让内核支持逻辑流复制功能。
31 | a. wal_level = logical
32 | b. max_wal_senders = 6
33 | c. max_replication_slots = 6
34 |
35 | # mysql2pgsql用法
36 |
37 |
38 | 1 全库迁移
39 |
40 | ./pgsql2pgsql
41 | 迁移程序会默认把对应 pgsql 库中所有的用户表数据将迁移到 pgsql
42 |
43 | 2 状态信息查询
44 | 连接本地临时DB,可以查看到单次迁移过程中的状态信息。他们放在表 db_sync_status 中,包括全量迁移的开始和结束时间,增量迁移的开始时间,增量同步的数据情况。
45 |
46 |
--------------------------------------------------------------------------------
/doc/pgsql2pgsql_en.md:
--------------------------------------------------------------------------------
1 | # pgsql2pgsql Import data from PostgreSQL
2 |
3 | The pgsql2pgsql tool supports migrating tables in HybridDB for PostgreSQL, Greenplum Database, PostgreSQL, or PPAS to HybridDB for PostgreSQL, Greenplum Database, PostgreSQL, or PPAS without storing the data separately.
4 |
5 | # Features
6 |
7 | pgsql2pgsql supports the following features:
8 |
9 | * 1 Full-database migration from PostgreSQL, PPAS, Greenplum Database, or HybridDB for PostgreSQL to PostgreSQL, PPAS, Greenplum Database, or HybridDB for PostgreSQL.
10 |
11 | * 2 Full-database migration and incremental data migration from PostgreSQL or PPAS (9.4 or later versions) to PostgreSQL, or PPAS.
12 |
13 | # Parameters configuration
14 | Modify the “my.cfg” configuration file, and configure the source and target database connection information.
15 |
16 | * The connection information of the source PostgreSQL database is shown as follows:
17 |
18 | **Note:** The user is preferably the corresponding database owner in the source PostgreSQL database connection information.
19 |
20 | ```
21 | [src.pgsql]
22 | connect_string = "host=192.168.1.1 dbname=test port=5888 user=test password=pgsql"
23 | ```
24 |
25 | * The connection information of the local temporary PostgreSQL database is shown as follows:
26 |
27 | ```
28 | [local.pgsql]
29 | connect_string = "host=192.168.1.1 dbname=test port=5888 user=test2 password=pgsql"
30 | ```
31 |
32 | * The connection information of the target PostgreSQL database is shown as follows:
33 |
34 | **Note:** You need to have the write permission on the target table of the target PostgreSQL database.
35 |
36 |
37 | ```
38 | [desc.pgsql]
39 | connect_string = "host=192.168.1.1 dbname=test port=5888 user=test3 password=pgsql"
40 | ```
41 |
42 | #Note:
43 |
44 | * If you want to perform incremental data synchronization, the connected source database must have the permission to create replication slots.
45 |
46 | * PostgreSQL 9.4 and later versions support logic flow replication, so it supports the incremental migration if PostgreSQL serves as the data source. The kernel only supports logic flow replication after you enable the following kernel parameters.
47 |
48 |
49 | ```
50 | wal_level = logical
51 | max_wal_senders = 6
52 | max_replication_slots = 6
53 | ```
54 |
55 |
56 | # Use pgsql2pgsql
57 |
58 | ## Full-database migration
59 |
60 | Run the following command to perform a full-database migration:
61 |
62 | ```
63 | ./pgsql2pgsql
64 | ```
65 |
66 |
67 | By default, the migration program migrates the table data of all the users in the corresponding PostgreSQL database to PostgreSQL.
68 |
69 | ## Status information query
70 |
71 | Connect to the local temporary database, and you can view the status information in a single migration process. The information is stored in the db_sync_status table, including the start and end time of the full-database migration, the start time of the incremental data migration, and the data situation of incremental synchronization.
72 |
73 |
74 | # Download and instructions
75 |
76 | ## binary download link
77 |
78 | [Download the binary installer of pgsql2pgsql](https://github.com/aliyun/rds_dbsync/releases)
79 |
80 | ## rds_dbsync project
81 |
82 | [View the mysql2pgsql source code compilation instructions](https://github.com/aliyun/rds_dbsync/blob/master/README.md)
83 |
84 |
85 |
--------------------------------------------------------------------------------