Prechádzať zdrojové kódy

init: 初始化 DataX_Plugin 项目

lsh 2 týždňov pred
commit
369ff5e2e8
100 zmenil súbory, kde vykonal 9553 pridanie a 0 odobranie
  1. 158 0
      .gitignore
  2. 39 0
      NOTICE
  3. 203 0
      README.md
  4. 338 0
      adbmysqlwriter/doc/adbmysqlwriter.md
  5. 79 0
      adbmysqlwriter/pom.xml
  6. 35 0
      adbmysqlwriter/src/main/assembly/package.xml
  7. 138 0
      adbmysqlwriter/src/main/java/com/alibaba/datax/plugin/writer/adbmysqlwriter/AdbMysqlWriter.java
  8. 6 0
      adbmysqlwriter/src/main/resources/plugin.json
  9. 20 0
      adbmysqlwriter/src/main/resources/plugin_job_template.json
  10. 113 0
      adbpgwriter/pom.xml
  11. 35 0
      adbpgwriter/src/main/assembly/package.xml
  12. 233 0
      adbpgwriter/src/main/doc/adbpgwriter.md
  13. 117 0
      adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/AdbpgWriter.java
  14. 182 0
      adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/copy/Adb4pgClientProxy.java
  15. 13 0
      adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/copy/AdbProxy.java
  16. 8 0
      adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/package-info.java
  17. 146 0
      adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/util/Adb4pgUtil.java
  18. 12 0
      adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/util/Constant.java
  19. 26 0
      adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/util/Key.java
  20. 6 0
      adbpgwriter/src/main/resources/plugin.json
  21. 13 0
      adbpgwriter/src/main/resources/plugin_job_template.json
  22. 313 0
      adswriter/doc/adswriter.md
  23. 123 0
      adswriter/pom.xml
  24. 36 0
      adswriter/src/main/assembly/package.xml
  25. 40 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/AdsException.java
  26. 410 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/AdsWriter.java
  27. 54 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/AdsWriterErrorCode.java
  28. 406 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/ads/ColumnDataType.java
  29. 72 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/ads/ColumnInfo.java
  30. 135 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/ads/TableInfo.java
  31. 6 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/ads/package-info.java
  32. 222 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/AdsClientProxy.java
  33. 635 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/AdsInsertProxy.java
  34. 153 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/AdsInsertUtil.java
  35. 12 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/AdsProxy.java
  36. 75 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/OperationType.java
  37. 429 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/load/AdsHelper.java
  38. 87 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/load/TableMetaHelper.java
  39. 59 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/load/TransferProjectConf.java
  40. 77 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/odps/DataType.java
  41. 63 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/odps/FieldSchema.java
  42. 114 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/odps/TableMeta.java
  43. 6 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/odps/package-info.java
  44. 6 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/package-info.java
  45. 175 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/util/AdsUtil.java
  46. 29 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/util/Constant.java
  47. 66 0
      adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/util/Key.java
  48. 6 0
      adswriter/src/main/resources/plugin.json
  49. 13 0
      adswriter/src/main/resources/plugin_job_template.json
  50. 217 0
      cassandrareader/doc/cassandrareader.md
  51. 133 0
      cassandrareader/pom.xml
  52. 35 0
      cassandrareader/src/main/assembly/package.xml
  53. 123 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/CassandraReader.java
  54. 32 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/CassandraReaderErrorCode.java
  55. 588 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/CassandraReaderHelper.java
  56. 39 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/Key.java
  57. 1 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings.properties
  58. 0 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_en_US.properties
  59. 1 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_ja_JP.properties
  60. 1 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_zh_CN.properties
  61. 1 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_zh_HK.properties
  62. 1 0
      cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_zh_TW.properties
  63. 6 0
      cassandrareader/src/main/resources/plugin.json
  64. 15 0
      cassandrareader/src/main/resources/plugin_job_template.json
  65. 227 0
      cassandrawriter/doc/cassandrawriter.md
  66. 125 0
      cassandrawriter/pom.xml
  67. 35 0
      cassandrawriter/src/main/assembly/package.xml
  68. 242 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/CassandraWriter.java
  69. 35 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/CassandraWriterErrorCode.java
  70. 351 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/CassandraWriterHelper.java
  71. 43 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/Key.java
  72. 2 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings.properties
  73. 2 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_en_US.properties
  74. 2 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_ja_JP.properties
  75. 2 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_zh_CN.properties
  76. 2 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_zh_HK.properties
  77. 2 0
      cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_zh_TW.properties
  78. 7 0
      cassandrawriter/src/main/resources/plugin.json
  79. 15 0
      cassandrawriter/src/main/resources/plugin_job_template.json
  80. 344 0
      clickhousereader/doc/clickhousereader.md
  81. 91 0
      clickhousereader/pom.xml
  82. 35 0
      clickhousereader/src/main/assembly/package.xml
  83. 85 0
      clickhousereader/src/main/java/com/alibaba/datax/plugin/reader/clickhousereader/ClickhouseReader.java
  84. 6 0
      clickhousereader/src/main/resources/plugin.json
  85. 16 0
      clickhousereader/src/main/resources/plugin_job_template.json
  86. 57 0
      clickhousereader/src/test/resources/basic1.json
  87. 34 0
      clickhousereader/src/test/resources/basic1.sql
  88. 88 0
      clickhousewriter/pom.xml
  89. 35 0
      clickhousewriter/src/main/assembly/package.xml
  90. 329 0
      clickhousewriter/src/main/java/com/alibaba/datax/plugin/writer/clickhousewriter/ClickhouseWriter.java
  91. 31 0
      clickhousewriter/src/main/java/com/alibaba/datax/plugin/writer/clickhousewriter/ClickhouseWriterErrorCode.java
  92. 6 0
      clickhousewriter/src/main/resources/plugin.json
  93. 21 0
      clickhousewriter/src/main/resources/plugin_job_template.json
  94. 83 0
      common/pom.xml
  95. 25 0
      common/src/main/java/com/alibaba/datax/common/base/BaseObject.java
  96. 9 0
      common/src/main/java/com/alibaba/datax/common/constant/CommonConstant.java
  97. 20 0
      common/src/main/java/com/alibaba/datax/common/constant/PluginType.java
  98. 121 0
      common/src/main/java/com/alibaba/datax/common/element/BoolColumn.java
  99. 90 0
      common/src/main/java/com/alibaba/datax/common/element/BytesColumn.java
  100. 0 0
      common/src/main/java/com/alibaba/datax/common/element/Column.java

+ 158 - 0
.gitignore

@@ -0,0 +1,158 @@
+# Created by .ignore support plugin (hsz.mobi)
+.DS_Store
+.AppleDouble
+.LSOverride
+Icon
+._*
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+*.class
+*.log
+*.ctxt
+.mtj.tmp/
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+hs_err_pid*
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/dictionaries
+.idea/**/shelf
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+.idea/**/gradle.xml
+.idea/**/libraries
+cmake-build-debug/
+cmake-build-release/
+.idea/**/mongoSettings.xml
+*.iws
+out/
+.idea_modules/
+atlassian-ide-plugin.xml
+.idea/replstate.xml
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+.idea/httpRequests
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+!/.mvn/wrapper/maven-wrapper.jar
+.idea
+*.iml
+out
+gen### Python template
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+*.manifest
+*.spec
+pip-log.txt
+pip-delete-this-directory.txt
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+*.mo
+*.pot
+*.log
+local_settings.py
+db.sqlite3
+instance/
+.webassets-cache
+.scrapy
+docs/_build/
+target/
+.ipynb_checkpoints
+.python-version
+celerybeat-schedule
+*.sage.py
+.env
+.venv
+.idea/
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+.spyderproject
+.spyproject
+.ropeproject
+/site
+.mypy_cache/
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+.externalToolBuilders/
+*.launch
+*.pydevproject
+.cproject
+.autotools
+.factorypath
+.buildpath
+.target
+.tern-project
+.texlipse
+.springBeans
+.recommenders/
+.cache-main
+.scala_dependencies
+.worksheet

+ 39 - 0
NOTICE

@@ -0,0 +1,39 @@
+========================================================
+DataX 是阿里云 DataWorks数据集成 的开源版本,在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、Hologres、DRDS 等各种异构数据源之间高效的数据同步功能。
+
+DataX is an open source offline data synchronization tool / platform widely used in Alibaba group and other companies. DataX implements efficient data synchronization between heterogeneous data sources including mysql, Oracle, oceanbase, sqlserver, postgre, HDFS, hive, ads, HBase, tablestore (OTS), maxcompute (ODPs), hologres, DRDS, etc.
+
+Copyright 1999-2022 Alibaba Group Holding Ltd.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+===================================================================
+文级别引用,按许可证
+This product contains various third-party components under other open source licenses.
+This section summarizes those components and their licenses. 
+GNU Lesser General Public License
+--------------------------------------
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/conn/CliQuery.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/conn/Connection4TSDB.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/conn/DataPoint4TSDB.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/conn/DumpSeries.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/conn/OpenTSDBConnection.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/conn/OpenTSDBDump.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/opentsdbreader/Constant.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/opentsdbreader/Key.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/opentsdbreader/OpenTSDBReader.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/opentsdbreader/OpenTSDBReaderErrorCode.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/util/HttpUtils.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/util/TSDBUtils.java
+opentsdbreader/src/main/java/com/alibaba/datax/plugin/reader/util/TimeUtils.java
+===================================================================

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 203 - 0
README.md


+ 338 - 0
adbmysqlwriter/doc/adbmysqlwriter.md

@@ -0,0 +1,338 @@
+# DataX AdbMysqlWriter
+
+
+---
+
+
+## 1 快速介绍
+
+AdbMysqlWriter 插件实现了写入数据到 ADB MySQL 目的表的功能。在底层实现上, AdbMysqlWriter 通过 JDBC 连接远程 ADB MySQL 数据库,并执行相应的 `insert into ...` 或者 ( `replace into ...` ) 的 SQL 语句将数据写入 ADB MySQL,内部会分批次提交入库。
+
+AdbMysqlWriter 面向ETL开发工程师,他们使用 AdbMysqlWriter 从数仓导入数据到 ADB MySQL。同时 AdbMysqlWriter 亦可以作为数据迁移工具为DBA等用户提供服务。
+
+
+## 2 实现原理
+
+AdbMysqlWriter 通过 DataX 框架获取 Reader 生成的协议数据,AdbMysqlWriter 通过 JDBC 连接远程 ADB MySQL 数据库,并执行相应的 `insert into ...` 或者 ( `replace into ...` ) 的 SQL 语句将数据写入 ADB MySQL。
+
+
+* `insert into...`(遇到主键重复时会自动忽略当前写入数据,不做更新,作用等同于`insert ignore into`)
+
+##### 或者
+
+* `replace into...`(没有遇到主键/唯一性索引冲突时,与 insert into 行为一致,冲突时会用新行替换原有行所有字段) 的语句写入数据到 MySQL。出于性能考虑,采用了 `PreparedStatement + Batch`,并且设置了:`rewriteBatchedStatements=true`,将数据缓冲到线程上下文 Buffer 中,当 Buffer 累计到预定阈值时,才发起写入请求。
+
+<br />
+
+    注意:整个任务至少需要具备 `insert/replace into...` 的权限,是否需要其他权限,取决于你任务配置中在 preSql 和 postSql 中指定的语句。
+
+
+## 3 功能说明
+
+### 3.1 配置样例
+
+* 这里使用一份从内存产生到 ADB MySQL 导入的数据。
+
+```json
+{
+    "job": {
+        "setting": {
+            "speed": {
+                "channel": 1
+            }
+        },
+        "content": [
+            {
+                 "reader": {
+                    "name": "streamreader",
+                    "parameter": {
+                        "column" : [
+                            {
+                                "value": "DataX",
+                                "type": "string"
+                            },
+                            {
+                                "value": 19880808,
+                                "type": "long"
+                            },
+                            {
+                                "value": "1988-08-08 08:08:08",
+                                "type": "date"
+                            },
+                            {
+                                "value": true,
+                                "type": "bool"
+                            },
+                            {
+                                "value": "test",
+                                "type": "bytes"
+                            }
+                        ],
+                        "sliceRecordCount": 1000
+                    }
+                },
+                "writer": {
+                    "name": "adbmysqlwriter",
+                    "parameter": {
+                        "writeMode": "replace",
+                        "username": "root",
+                        "password": "root",
+                        "column": [
+                            "*"
+                        ],
+                        "preSql": [
+                            "truncate table @table"
+                        ],
+                        "connection": [
+                            {
+                                "jdbcUrl": "jdbc:mysql://ip:port/database?useUnicode=true",
+                                "table": [
+                                    "test"
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }
+        ]
+    }
+}
+
+```
+
+
+### 3.2 参数说明
+
+* **jdbcUrl**
+
+    * 描述:目的数据库的 JDBC 连接信息。作业运行时,DataX 会在你提供的 jdbcUrl 后面追加如下属性:yearIsDateType=false&zeroDateTimeBehavior=convertToNull&rewriteBatchedStatements=true
+
+               注意:1、在一个数据库上只能配置一个 jdbcUrl
+                    2、一个 AdbMySQL 写入任务仅能配置一个 jdbcUrl
+                    3、jdbcUrl按照MySQL官方规范,并可以填写连接附加控制信息,比如想指定连接编码为 gbk ,则在 jdbcUrl 后面追加属性 useUnicode=true&characterEncoding=gbk。具体请参看 Mysql官方文档或者咨询对应 DBA。
+
+ 	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **username**
+
+	* 描述:目的数据库的用户名 <br />
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **password**
+
+	* 描述:目的数据库的密码 <br />
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **table**
+
+	* 描述:目的表的表名称。只能配置一个 AdbMySQL 的表名称。
+
+               注意:table 和 jdbcUrl 必须包含在 connection 配置单元中
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **column**
+
+	* 描述:目的表需要写入数据的字段,字段之间用英文逗号分隔。例如: "column": ["id", "name", "age"]。如果要依次写入全部列,使用`*`表示, 例如: `"column": ["*"]`。
+
+			**column配置项必须指定,不能留空!**
+
+               注意:1、我们强烈不推荐你这样配置,因为当你目的表字段个数、类型等有改动时,你的任务可能运行不正确或者失败
+                    2、 column 不能配置任何常量值
+
+	* 必选:是 <br />
+
+	* 默认值:否 <br />
+
+* **session**
+
+	* 描述: DataX在获取 ADB MySQL 连接时,执行session指定的SQL语句,修改当前connection session属性
+
+	* 必须: 否
+
+	* 默认值: 空
+
+* **preSql**
+
+	* 描述:写入数据到目的表前,会先执行这里的标准语句。如果 Sql 中有你需要操作到的表名称,请使用 `@table` 表示,这样在实际执行 SQL 语句时,会对变量按照实际表名称进行替换。比如希望导入数据前,先对表中数据进行删除操作,那么你可以这样配置:`"preSql":["truncate table @table"]`,效果是:在执行到每个表写入数据前,会先执行对应的 `truncate table 对应表名称` <br />
+
+	* 必选:否 <br />
+
+	* 默认值:无 <br />
+
+* **postSql**
+
+	* 描述:写入数据到目的表后,会执行这里的标准语句。(原理同 preSql ) <br />
+
+	* 必选:否 <br />
+
+	* 默认值:无 <br />
+
+* **writeMode**
+
+	* 描述:控制写入数据到目标表采用 `insert into` 或者 `replace into` 或者 `ON DUPLICATE KEY UPDATE` 语句<br />
+
+	* 必选:是 <br />
+	
+	* 所有选项:insert/replace/update <br />
+
+	* 默认值:replace <br />
+
+* **batchSize**
+
+	* 描述:一次性批量提交的记录数大小,该值可以极大减少DataX与 Adb MySQL 的网络交互次数,并提升整体吞吐量。但是该值设置过大可能会造成DataX运行进程OOM情况。<br />
+
+	* 必选:否 <br />
+
+	* 默认值:2048 <br />
+
+
+### 3.3 类型转换
+
+目前 AdbMysqlWriter 支持大部分 MySQL 类型,但也存在部分个别类型没有支持的情况,请注意检查你的类型。
+
+下面列出 AdbMysqlWriter 针对 MySQL 类型转换列表:
+
+| DataX 内部类型 | AdbMysql 数据类型                 |
+|---------------|---------------------------------|
+| Long          | tinyint, smallint, int, bigint  |
+| Double        | float, double, decimal          |
+| String        | varchar                         |
+| Date          | date, time, datetime, timestamp |
+| Boolean       | boolean                         |
+| Bytes         | binary                          |
+
+## 4 性能报告
+
+### 4.1 环境准备
+
+#### 4.1.1 数据特征
+TPC-H 数据集 lineitem 表,共 17 个字段, 随机生成总记录行数 59986052。未压缩总数据量:7.3GiB
+
+建表语句:
+
+	CREATE TABLE `datax_adbmysqlwriter_perf_lineitem` (
+		`l_orderkey` bigint NOT NULL COMMENT '',
+		`l_partkey` int NOT NULL COMMENT '',
+		`l_suppkey` int NOT NULL COMMENT '',
+		`l_linenumber` int NOT NULL COMMENT '',
+		`l_quantity` decimal(15,2) NOT NULL COMMENT '',
+		`l_extendedprice` decimal(15,2) NOT NULL COMMENT '',
+		`l_discount` decimal(15,2) NOT NULL COMMENT '',
+		`l_tax` decimal(15,2) NOT NULL COMMENT '',
+		`l_returnflag` varchar(1024) NOT NULL COMMENT '',
+		`l_linestatus` varchar(1024) NOT NULL COMMENT '',
+		`l_shipdate` date NOT NULL COMMENT '',
+		`l_commitdate` date NOT NULL COMMENT '',
+		`l_receiptdate` date NOT NULL COMMENT '',
+		`l_shipinstruct` varchar(1024) NOT NULL COMMENT '',
+		`l_shipmode` varchar(1024) NOT NULL COMMENT '',
+		`l_comment` varchar(1024) NOT NULL COMMENT '',
+		`dummy` varchar(1024),
+		PRIMARY KEY (`l_orderkey`, `l_linenumber`)
+	) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='datax perf test';
+
+单行记录类似于:
+
+   	        l_orderkey: 2122789
+             l_partkey: 1233571
+             l_suppkey: 8608
+		  l_linenumber: 1
+		    l_quantity: 35.00
+       l_extendedprice: 52657.85
+		    l_discount: 0.02
+		         l_tax: 0.07
+          l_returnflag: N
+		  l_linestatus: O
+		    l_shipdate: 1996-11-03
+		  l_commitdate: 1996-12-07
+		 l_receiptdate: 1996-11-16
+		l_shipinstruct: COLLECT COD
+		    l_shipmode: FOB
+		     l_comment: ld, regular theodolites.
+                 dummy:
+
+#### 4.1.2 机器参数
+
+* DataX ECS: 24Core48GB
+
+* Adb MySQL 数据库
+	* 计算资源:16Core64GB(集群版)
+	* 弹性IO资源:3
+
+#### 4.1.3 DataX jvm 参数
+
+	-Xms1G -Xmx10G -XX:+HeapDumpOnOutOfMemoryError
+
+### 4.2 测试报告
+
+| 通道数 | 批量提交行数 | DataX速度(Rec/s)   | DataX流量(MB/s) | 导入用时(s) |
+|-----|-------|------------------|---------------|---------|
+| 1   | 512   | 23071            | 2.34          | 2627    |
+| 1   | 1024  | 26080            | 2.65          | 2346    |
+| 1   | 2048  | 28162            | 2.86          | 2153    |
+| 1   | 4096  | 28978            | 2.94          | 2119    |
+| 4   | 512   | 56590            | 5.74          | 1105    |
+| 4   | 1024  | 81062            | 8.22          | 763     |
+| 4   | 2048  | 107117           | 10.87         | 605     |
+| 4   | 4096  | 113181           | 11.48         | 579     |
+| 8   | 512   | 81062            | 8.22          | 786     |
+| 8   | 1024  | 127629           | 12.95         | 519     |
+| 8   | 2048  | 187456           | 19.01         | 369     |
+| 8   | 4096  | 206848           | 20.98         | 341     |
+| 16  | 512   | 130404           | 13.23         | 513     |
+| 16  | 1024  | 214235           | 21.73         | 335     |
+| 16  | 2048  | 299930           | 30.42         | 253     |
+| 16  | 4096  | 333255           | 33.80         | 227     |
+| 32  | 512   | 206848           | 20.98         | 347     |
+| 32  | 1024  | 315716           | 32.02         | 241     |
+| 32  | 2048  | 399907           | 40.56         | 199     |
+| 32  | 4096  | 461431           | 46.80         | 184     |
+| 64  | 512   | 333255           | 33.80         | 231     |
+| 64  | 1024  | 399907           | 40.56         | 204     |
+| 64  | 2048  | 428471           | 43.46         | 199     |
+| 64  | 4096  | 461431           | 46.80         | 187     |
+| 128 | 512   | 333255           | 33.80         | 235     |
+| 128 | 1024  | 399907           | 40.56         | 203     |
+| 128 | 2048  | 425432           | 43.15         | 197     |
+| 128 | 4096  | 387006           | 39.26         | 211     |
+
+说明:
+
+1. datax 使用 txtfilereader 读取本地文件,避免源端存在性能瓶颈。
+
+#### 性能测试小结
+1. channel通道个数和batchSize对性能影响比较大
+2. 通常不建议写入数据库时,通道个数 > 32
+
+## 5 约束限制
+
+## FAQ
+
+***
+
+**Q: AdbMysqlWriter 执行 postSql 语句报错,那么数据导入到目标数据库了吗?**
+
+A: DataX 导入过程存在三块逻辑,pre 操作、导入操作、post 操作,其中任意一环报错,DataX 作业报错。由于 DataX 不能保证在同一个事务完成上述几个操作,因此有可能数据已经落入到目标端。
+
+***
+
+**Q: 按照上述说法,那么有部分脏数据导入数据库,如果影响到线上数据库怎么办?**
+
+A: 目前有两种解法,第一种配置 pre 语句,该 sql 可以清理当天导入数据, DataX 每次导入时候可以把上次清理干净并导入完整数据。第二种,向临时表导入数据,完成后再 rename 到线上表。
+
+***
+
+**Q: 上面第二种方法可以避免对线上数据造成影响,那我具体怎样操作?**
+
+A: 可以配置临时表导入

+ 79 - 0
adbmysqlwriter/pom.xml

@@ -0,0 +1,79 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.alibaba.datax</groupId>
+		<artifactId>datax-all</artifactId>
+		<version>0.0.1-SNAPSHOT</version>
+	</parent>
+	<artifactId>adbmysqlwriter</artifactId>
+	<name>adbmysqlwriter</name>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>com.alibaba.datax</groupId>
+			<artifactId>datax-common</artifactId>
+			<version>${datax-project-version}</version>
+			<exclusions>
+				<exclusion>
+					<artifactId>slf4j-log4j12</artifactId>
+					<groupId>org.slf4j</groupId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.alibaba.datax</groupId>
+			<artifactId>plugin-rdbms-util</artifactId>
+			<version>${datax-project-version}</version>
+		</dependency>
+
+		<dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>5.1.40</version>
+        </dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<!-- compiler plugin -->
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>${jdk-version}</source>
+					<target>${jdk-version}</target>
+					<encoding>${project-sourceEncoding}</encoding>
+				</configuration>
+			</plugin>
+			<!-- assembly plugin -->
+			<plugin>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<configuration>
+					<descriptors>
+						<descriptor>src/main/assembly/package.xml</descriptor>
+					</descriptors>
+					<finalName>datax</finalName>
+				</configuration>
+				<executions>
+					<execution>
+						<id>dwzip</id>
+						<phase>package</phase>
+						<goals>
+							<goal>single</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 35 - 0
adbmysqlwriter/src/main/assembly/package.xml

@@ -0,0 +1,35 @@
+<assembly
+	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+	<id></id>
+	<formats>
+		<format>dir</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+	<fileSets>
+		<fileSet>
+			<directory>src/main/resources</directory>
+			<includes>
+				<include>plugin.json</include>
+				<include>plugin_job_template.json</include>
+			</includes>
+			<outputDirectory>plugin/writer/adbmysqlwriter</outputDirectory>
+		</fileSet>
+		<fileSet>
+			<directory>target/</directory>
+			<includes>
+				<include>adbmysqlwriter-0.0.1-SNAPSHOT.jar</include>
+			</includes>
+			<outputDirectory>plugin/writer/adbmysqlwriter</outputDirectory>
+		</fileSet>
+	</fileSets>
+
+	<dependencySets>
+		<dependencySet>
+			<useProjectArtifact>false</useProjectArtifact>
+			<outputDirectory>plugin/writer/adbmysqlwriter/libs</outputDirectory>
+			<scope>runtime</scope>
+		</dependencySet>
+	</dependencySets>
+</assembly>

+ 138 - 0
adbmysqlwriter/src/main/java/com/alibaba/datax/plugin/writer/adbmysqlwriter/AdbMysqlWriter.java

@@ -0,0 +1,138 @@
+package com.alibaba.datax.plugin.writer.adbmysqlwriter;
+
+import com.alibaba.datax.common.element.Record;
+import com.alibaba.datax.common.plugin.RecordReceiver;
+import com.alibaba.datax.common.spi.Writer;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.plugin.rdbms.util.DataBaseType;
+import com.alibaba.datax.plugin.rdbms.writer.CommonRdbmsWriter;
+import com.alibaba.datax.plugin.rdbms.writer.Key;
+import org.apache.commons.lang3.StringUtils;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+public class AdbMysqlWriter extends Writer {
+    private static final DataBaseType DATABASE_TYPE = DataBaseType.ADB;
+
+    public static class Job extends Writer.Job {
+        private Configuration originalConfig = null;
+        private CommonRdbmsWriter.Job commonRdbmsWriterJob;
+
+        @Override
+        public void preCheck(){
+            this.init();
+            this.commonRdbmsWriterJob.writerPreCheck(this.originalConfig, DATABASE_TYPE);
+        }
+
+        @Override
+        public void init() {
+            this.originalConfig = super.getPluginJobConf();
+            this.commonRdbmsWriterJob = new CommonRdbmsWriter.Job(DATABASE_TYPE);
+            this.commonRdbmsWriterJob.init(this.originalConfig);
+        }
+
+        // 一般来说,是需要推迟到 task 中进行pre 的执行(单表情况例外)
+        @Override
+        public void prepare() {
+            //实跑先不支持 权限 检验
+            //this.commonRdbmsWriterJob.privilegeValid(this.originalConfig, DATABASE_TYPE);
+            this.commonRdbmsWriterJob.prepare(this.originalConfig);
+        }
+
+        @Override
+        public List<Configuration> split(int mandatoryNumber) {
+            return this.commonRdbmsWriterJob.split(this.originalConfig, mandatoryNumber);
+        }
+
+        // 一般来说,是需要推迟到 task 中进行post 的执行(单表情况例外)
+        @Override
+        public void post() {
+            this.commonRdbmsWriterJob.post(this.originalConfig);
+        }
+
+        @Override
+        public void destroy() {
+            this.commonRdbmsWriterJob.destroy(this.originalConfig);
+        }
+
+    }
+
+    public static class Task extends Writer.Task {
+
+        private Configuration writerSliceConfig;
+        private CommonRdbmsWriter.Task commonRdbmsWriterTask;
+
+        public static class DelegateClass extends CommonRdbmsWriter.Task {
+            private long writeTime = 0L;
+            private long writeCount = 0L;
+            private long lastLogTime = 0;
+
+            public DelegateClass(DataBaseType dataBaseType) {
+                super(dataBaseType);
+            }
+
+            @Override
+            protected void doBatchInsert(Connection connection, List<Record> buffer)
+                    throws SQLException {
+                long startTime = System.currentTimeMillis();
+
+                super.doBatchInsert(connection, buffer);
+
+                writeCount = writeCount + buffer.size();
+                writeTime = writeTime + (System.currentTimeMillis() - startTime);
+
+                // log write metrics every 10 seconds
+                if (System.currentTimeMillis() - lastLogTime > 10000) {
+                    lastLogTime = System.currentTimeMillis();
+                    logTotalMetrics();
+                }
+            }
+
+            public void logTotalMetrics() {
+                LOG.info(Thread.currentThread().getName() + ", AdbMySQL writer take " + writeTime + " ms, write " + writeCount + " records.");
+            }
+        }
+
+        @Override
+        public void init() {
+            this.writerSliceConfig = super.getPluginJobConf();
+
+            if (StringUtils.isBlank(this.writerSliceConfig.getString(Key.WRITE_MODE))) {
+                this.writerSliceConfig.set(Key.WRITE_MODE, "REPLACE");
+            }
+
+            this.commonRdbmsWriterTask = new DelegateClass(DATABASE_TYPE);
+            this.commonRdbmsWriterTask.init(this.writerSliceConfig);
+        }
+
+        @Override
+        public void prepare() {
+            this.commonRdbmsWriterTask.prepare(this.writerSliceConfig);
+        }
+
+        //TODO 改用连接池,确保每次获取的连接都是可用的(注意:连接可能需要每次都初始化其 session)
+        public void startWrite(RecordReceiver recordReceiver) {
+            this.commonRdbmsWriterTask.startWrite(recordReceiver, this.writerSliceConfig,
+                    super.getTaskPluginCollector());
+        }
+
+        @Override
+        public void post() {
+            this.commonRdbmsWriterTask.post(this.writerSliceConfig);
+        }
+
+        @Override
+        public void destroy() {
+            this.commonRdbmsWriterTask.destroy(this.writerSliceConfig);
+        }
+
+        @Override
+        public boolean supportFailOver(){
+            String writeMode = writerSliceConfig.getString(Key.WRITE_MODE);
+            return "replace".equalsIgnoreCase(writeMode);
+        }
+
+    }
+}

+ 6 - 0
adbmysqlwriter/src/main/resources/plugin.json

@@ -0,0 +1,6 @@
+{
+    "name": "adbmysqlwriter",
+    "class": "com.alibaba.datax.plugin.writer.adbmysqlwriter.AdbMysqlWriter",
+    "description": "useScene: prod. mechanism: Jdbc connection using the database, execute insert sql. warn: The more you know about the database, the less problems you encounter.",
+    "developer": "alibaba"
+}

+ 20 - 0
adbmysqlwriter/src/main/resources/plugin_job_template.json

@@ -0,0 +1,20 @@
+{
+    "name": "adbmysqlwriter",
+    "parameter": {
+        "username": "username",
+        "password": "password",
+        "column": ["col1", "col2", "col3"],
+        "connection": [
+            {
+                "jdbcUrl": "jdbc:mysql://<host>:<port>[/<database>]",
+                "table": ["table1", "table2"]
+            }
+        ],
+        "preSql": [],
+        "postSql": [],
+        "batchSize": 65536,
+        "batchByteSize": 134217728,
+        "dryRun": false,
+        "writeMode": "insert"
+    }
+}

+ 113 - 0
adbpgwriter/pom.xml

@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>datax-all</artifactId>
+        <groupId>com.alibaba.datax</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>adbpgwriter</artifactId>
+    <name>adbpgwriter</name>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-common</artifactId>
+            <version>${datax-project-version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>slf4j-log4j12</artifactId>
+                    <groupId>org.slf4j</groupId>
+                </exclusion>
+                <exclusion>
+                    <groupId>mysql</groupId>
+                    <artifactId>mysql-connector-java</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-core</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>plugin-rdbms-util</artifactId>
+            <version>${datax-project-version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.alibaba</groupId>
+                    <artifactId>druid</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>1.1.17</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-exec</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-configuration</groupId>
+            <artifactId>commons-configuration</artifactId>
+            <version>1.10</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud.analyticdb</groupId>
+            <artifactId>adb4pgclient</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- compiler plugin -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${jdk-version}</source>
+                    <target>${jdk-version}</target>
+                    <encoding>${project-sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+            <!-- assembly plugin -->
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/package.xml</descriptor>
+                    </descriptors>
+                    <finalName>datax</finalName>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>dwzip</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 35 - 0
adbpgwriter/src/main/assembly/package.xml

@@ -0,0 +1,35 @@
+<assembly
+	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+	<id></id>
+	<formats>
+		<format>dir</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+	<fileSets>
+		<fileSet>
+			<directory>src/main/resources</directory>
+			<includes>
+				<include>plugin.json</include>
+				<include>plugin_job_template.json</include>
+			</includes>
+			<outputDirectory>plugin/writer/adbpgwriter</outputDirectory>
+		</fileSet>
+		<fileSet>
+			<directory>target/</directory>
+			<includes>
+				<include>adbpgwriter-0.0.1-SNAPSHOT.jar</include>
+			</includes>
+			<outputDirectory>plugin/writer/adbpgwriter</outputDirectory>
+		</fileSet>
+	</fileSets>
+
+	<dependencySets>
+		<dependencySet>
+			<useProjectArtifact>false</useProjectArtifact>
+			<outputDirectory>plugin/writer/adbpgwriter/libs</outputDirectory>
+			<scope>runtime</scope>
+		</dependencySet>
+	</dependencySets>
+</assembly>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 233 - 0
adbpgwriter/src/main/doc/adbpgwriter.md


+ 117 - 0
adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/AdbpgWriter.java

@@ -0,0 +1,117 @@
+package com.alibaba.datax.plugin.writer.adbpgwriter;
+
+import com.alibaba.datax.common.plugin.RecordReceiver;
+import com.alibaba.datax.common.plugin.RecordSender;
+import com.alibaba.datax.common.spi.Writer;
+import com.alibaba.datax.common.util.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.alibaba.datax.plugin.rdbms.util.DataBaseType;
+import com.alibaba.datax.plugin.rdbms.writer.CommonRdbmsWriter;
+import com.alibaba.datax.plugin.rdbms.writer.Key;
+import com.alibaba.datax.plugin.rdbms.writer.util.OriginalConfPretreatmentUtil;
+import com.alibaba.datax.plugin.writer.adbpgwriter.copy.Adb4pgClientProxy;
+import com.alibaba.datax.plugin.writer.adbpgwriter.util.Adb4pgUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.alibaba.datax.plugin.rdbms.util.DBUtilErrorCode.*;
+import static com.alibaba.datax.plugin.rdbms.util.DataBaseType.PostgreSQL;
+
+/**
+ * @author yuncheng
+ */
+public class AdbpgWriter extends Writer {
+    private static final DataBaseType DATABASE_TYPE = DataBaseType.PostgreSQL;
+
+    public static class Job extends Writer.Job {
+
+        private Configuration originalConfig;
+        private CommonRdbmsWriter.Job commonRdbmsWriterMaster;
+        private static final Logger LOG = LoggerFactory.getLogger(Writer.Job.class);
+
+        @Override
+        public void init() {
+            this.originalConfig = super.getPluginJobConf();
+            LOG.info("in Job.init(), config is:[\n{}\n]", originalConfig.toJSON());
+            this.commonRdbmsWriterMaster =  new CommonRdbmsWriter.Job(DATABASE_TYPE);
+            //convert to DatabaseConfig, use DatabaseConfig to check user configuration
+            Adb4pgUtil.checkConfig(originalConfig);
+        }
+
+        @Override
+        public void prepare() {
+
+            Adb4pgUtil.prepare(originalConfig);
+        }
+
+        @Override
+        public List<Configuration> split(int adviceNumber) {
+            List<Configuration> splitResult = new ArrayList<Configuration>();
+            for(int i = 0; i < adviceNumber; i++) {
+                splitResult.add(this.originalConfig.clone());
+            }
+            return splitResult;
+        }
+
+        @Override
+        public void post() {
+
+            Adb4pgUtil.post(originalConfig);
+        }
+
+        @Override
+        public void destroy() {
+
+        }
+
+
+
+    }
+
+    public static class Task extends Writer.Task {
+        private Configuration writerSliceConfig;
+        private CommonRdbmsWriter.Task commonRdbmsWriterSlave;
+        private Adb4pgClientProxy adb4pgClientProxy;
+        //Adb4pgClient client;
+        @Override
+        public void init() {
+            this.writerSliceConfig = super.getPluginJobConf();
+            this.adb4pgClientProxy = new Adb4pgClientProxy(writerSliceConfig, super.getTaskPluginCollector());
+            this.commonRdbmsWriterSlave = new CommonRdbmsWriter.Task(DATABASE_TYPE){
+                @Override
+                public String calcValueHolder(String columnType){
+                    if("serial".equalsIgnoreCase(columnType)){
+                        return "?::int";
+                    }else if("bit".equalsIgnoreCase(columnType)){
+                        return "?::bit varying";
+                    }
+                    return "?::" + columnType;
+                }
+            };
+        }
+
+        @Override
+        public void prepare() {
+
+        }
+
+        @Override
+        public void startWrite(RecordReceiver recordReceiver) {
+            this.adb4pgClientProxy.startWriteWithConnection(recordReceiver, Adb4pgUtil.getAdbpgConnect(writerSliceConfig));
+        }
+
+        @Override
+        public void post() {
+
+        }
+
+        @Override
+        public void destroy() {
+
+        }
+
+    }
+}

+ 182 - 0
adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/copy/Adb4pgClientProxy.java

@@ -0,0 +1,182 @@
+package com.alibaba.datax.plugin.writer.adbpgwriter.copy;
+
+import com.alibaba.cloud.analyticdb.adb4pgclient.*;
+import com.alibaba.datax.common.element.Column;
+import com.alibaba.datax.common.element.Record;
+import com.alibaba.datax.common.element.StringColumn;
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.plugin.RecordReceiver;
+import com.alibaba.datax.common.plugin.TaskPluginCollector;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.core.transport.record.DefaultRecord;
+import com.alibaba.datax.plugin.rdbms.util.DBUtil;
+import com.alibaba.datax.plugin.rdbms.util.DBUtilErrorCode;
+import com.alibaba.datax.plugin.writer.adbpgwriter.util.Adb4pgUtil;
+import com.alibaba.datax.plugin.writer.adbpgwriter.util.Constant;
+import com.alibaba.datax.plugin.writer.adbpgwriter.util.Key;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+/**
+ * @author yuncheng
+ */
+public class Adb4pgClientProxy implements AdbProxy {
+    private static final Logger LOG = LoggerFactory.getLogger(Adb4pgClientProxy.class);
+
+    private Adb4pgClient adb4pgClient;
+    private String table;
+    private String schema;
+    List<String> columns;
+    private TableInfo tableInfo;
+    private TaskPluginCollector taskPluginCollector;
+    private boolean useRawData[];
+    public Adb4pgClientProxy(Configuration configuration,TaskPluginCollector  taskPluginCollector) {
+        this.taskPluginCollector = taskPluginCollector;
+
+        DatabaseConfig databaseConfig = Adb4pgUtil.convertConfiguration(configuration);
+
+        // If the value of column is empty, set null
+        boolean emptyAsNull = configuration.getBool(Key.EMPTY_AS_NULL, false);
+        databaseConfig.setEmptyAsNull(emptyAsNull);
+
+        // 使用insert ignore into方式进行插入
+        boolean ignoreInsert = configuration.getBool(Key.IGNORE_INSERT, false);
+        databaseConfig.setInsertIgnore(ignoreInsert);
+
+        // commit时,写入ADB出现异常时重试的3次
+        int retryTimes = configuration.getInt(Key.RETRY_CONNECTION_TIME, Constant.DEFAULT_RETRY_TIMES);
+        databaseConfig.setRetryTimes(retryTimes);
+
+        // 重试间隔的时间为1s,单位是ms
+        int retryIntervalTime = configuration.getInt(Key.RETRY_INTERVAL_TIME, 1000);
+        databaseConfig.setRetryIntervalTime(retryIntervalTime);
+
+        // 设置自动提交的SQL长度(单位Byte),默认为10MB,一般不建议设置
+        int commitSize = configuration.getInt("commitSize", 10 * 1024 * 1024);
+        databaseConfig.setCommitSize(commitSize);
+
+
+        // 设置写入adb时的并发线程数,默认4,针对配置的所有表
+        int parallelNumber = configuration.getInt("parallelNumber", 4);
+        databaseConfig.setParallelNumber(parallelNumber);
+
+        // 设置client中使用的logger对象,此处使用slf4j.Logger
+        databaseConfig.setLogger(Adb4pgClientProxy.LOG);
+
+        // sdk 默认值为true
+        boolean shareDataSource = configuration.getBool("shareDataSource", true);
+        databaseConfig.setShareDataSource(shareDataSource);
+
+        //List<String> columns = configuration.getList(Key.COLUMN, String.class);
+
+        this.table = configuration.getString(com.alibaba.datax.plugin.rdbms.writer.Key.TABLE);
+        this.schema = configuration.getString(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.SCHEMA);
+        this.adb4pgClient = new Adb4pgClient(databaseConfig);
+        this.columns = databaseConfig.getColumns(table,schema);
+        this.tableInfo = adb4pgClient.getTableInfo(table, schema);
+
+
+        this.useRawData = new boolean[this.columns.size()];
+        List<ColumnInfo> columnInfos = tableInfo.getColumns();
+        for (int i = 0; i < this.columns.size(); i++) {
+            String oriEachColumn = columns.get(i);
+            String eachColumn = oriEachColumn;
+            // 防御性保留字
+            if (eachColumn.startsWith(Constant.COLUMN_QUOTE_CHARACTER)
+                    && eachColumn.endsWith(Constant.COLUMN_QUOTE_CHARACTER)) {
+                eachColumn = eachColumn.substring(1, eachColumn.length() - 1);
+            }
+            for (ColumnInfo eachAdsColumn : columnInfos) {
+                if (eachColumn.equals(eachAdsColumn.getName())) {
+
+                    int columnSqltype = eachAdsColumn.getDataType().sqlType;
+                    switch (columnSqltype) {
+                        case Types.DATE:
+                        case Types.TIME:
+                        case Types.TIMESTAMP:
+                            this.useRawData[i] = false;
+                            break;
+                        default:
+                            this.useRawData[i] = true;
+                            break;
+                    }
+                }
+            }
+        }
+
+    }
+    @Override
+    public void startWriteWithConnection(RecordReceiver recordReceiver, Connection connection) {
+        try {
+            Record record;
+            while ((record = recordReceiver.getFromReader()) != null) {
+                Row row = new Row();
+                List<Object> values = new ArrayList<Object>();
+                this.prepareColumnTypeValue(record, values);
+                row.setColumnValues(values);
+
+                try {
+                    this.adb4pgClient.addRow(row,this.table, this.schema);
+                } catch (Adb4pgClientException e) {
+                    if (101 == e.getCode()) {
+                        for (String each : e.getErrData()) {
+                            Record dirtyData = new DefaultRecord();
+                            dirtyData.addColumn(new StringColumn(each));
+                            this.taskPluginCollector.collectDirtyRecord(dirtyData, e.getMessage());
+                        }
+                    } else {
+                        throw e;
+                    }
+                }
+
+            }
+
+            try {
+                this.adb4pgClient.commit();
+            } catch (Adb4pgClientException e) {
+                if (101 == e.getCode()) {
+                    for (String each : e.getErrData()) {
+                        Record dirtyData = new DefaultRecord();
+                        dirtyData.addColumn(new StringColumn(each));
+                        this.taskPluginCollector.collectDirtyRecord(dirtyData, e.getMessage());
+                    }
+                } else {
+                    throw e;
+                }
+            }
+
+        }catch (Exception e) {
+            throw DataXException.asDataXException(DBUtilErrorCode.WRITE_DATA_ERROR, e);
+        }finally {
+            DBUtil.closeDBResources(null, null, connection);
+        }
+        return;
+    }
+
+    private void prepareColumnTypeValue(Record record, List<Object> values) {
+        for (int i = 0; i < this.columns.size(); i++) {
+            Column column = record.getColumn(i);
+            if (this.useRawData[i]) {
+                values.add(column.getRawData());
+            } else {
+                values.add(column.asString());
+            }
+
+        }
+    }
+
+    @Override
+    public void closeResource() {
+        try {
+            LOG.info("stop the adb4pgClient");
+            this.adb4pgClient.stop();
+        } catch (Exception e) {
+            LOG.warn("stop adbClient meet a exception, ignore it: {}", e.getMessage(), e);
+        }
+    }
+}

+ 13 - 0
adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/copy/AdbProxy.java

@@ -0,0 +1,13 @@
+package com.alibaba.datax.plugin.writer.adbpgwriter.copy;
+
+import com.alibaba.datax.common.plugin.RecordReceiver;
+
+import java.sql.Connection;
+/**
+ * @author yuncheng
+ */
+public interface AdbProxy {
+    public abstract void startWriteWithConnection(RecordReceiver recordReceiver, Connection connection);
+
+    public void closeResource();
+}

+ 8 - 0
adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/package-info.java

@@ -0,0 +1,8 @@
+/**
+ * Greenplum Writer.
+ *
+ * @since 0.0.1
+ */
+package com.alibaba.datax.plugin.writer.adbpgwriter;
+
+

+ 146 - 0
adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/util/Adb4pgUtil.java

@@ -0,0 +1,146 @@
+package com.alibaba.datax.plugin.writer.adbpgwriter.util;
+
+import com.alibaba.cloud.analyticdb.adb4pgclient.Adb4pgClient;
+import com.alibaba.cloud.analyticdb.adb4pgclient.Adb4pgClientException;
+import com.alibaba.cloud.analyticdb.adb4pgclient.DatabaseConfig;
+import com.alibaba.datax.common.element.Column;
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.spi.ErrorCode;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.plugin.rdbms.util.DBUtil;
+import com.alibaba.datax.plugin.rdbms.util.DBUtilErrorCode;
+import com.alibaba.datax.plugin.rdbms.util.DataBaseType;
+import com.alibaba.datax.plugin.rdbms.writer.Constant;
+import com.alibaba.datax.plugin.rdbms.writer.Key;
+import com.alibaba.datax.plugin.rdbms.writer.util.WriterUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.util.*;
+
+import static com.alibaba.datax.plugin.rdbms.util.DBUtilErrorCode.COLUMN_SPLIT_ERROR;
+
+/**
+ * @author yuncheng
+ */
+public class Adb4pgUtil {
+
+    private static final Logger LOG = LoggerFactory.getLogger(Adb4pgUtil.class);
+    private static final DataBaseType DATABASE_TYPE = DataBaseType.PostgreSQL;
+    public static void checkConfig(Configuration originalConfig) {
+        try {
+
+            DatabaseConfig databaseConfig = convertConfiguration(originalConfig);
+
+            Adb4pgClient testConfigClient = new Adb4pgClient(databaseConfig);
+        } catch (Exception e) {
+            throw new Adb4pgClientException(Adb4pgClientException.CONFIG_ERROR, "Check config exception: " + e.getMessage(), null);
+        }
+    }
+
+    public static DatabaseConfig convertConfiguration(Configuration originalConfig) {
+        originalConfig.getNecessaryValue(Key.USERNAME, COLUMN_SPLIT_ERROR);
+        originalConfig.getNecessaryValue(Key.PASSWORD, COLUMN_SPLIT_ERROR);
+
+
+        String userName = originalConfig.getString(Key.USERNAME);
+        String passWord = originalConfig.getString(Key.PASSWORD);
+        String tableName = originalConfig.getString(Key.TABLE);
+        String schemaName = originalConfig.getString(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.SCHEMA);
+        String host = originalConfig.getString(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.HOST);
+        String port = originalConfig.getString(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.PORT);
+        String databseName = originalConfig.getString(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.DATABASE);
+
+        List<String> columns = originalConfig.getList(Key.COLUMN, String.class);
+        DatabaseConfig databaseConfig = new DatabaseConfig();
+        databaseConfig.setHost(host);
+        databaseConfig.setPort(Integer.valueOf(port));
+        databaseConfig.setDatabase(databseName);
+
+        databaseConfig.setUser(userName);
+
+        databaseConfig.setPassword(passWord);
+        databaseConfig.setLogger(LOG);
+
+        databaseConfig.setInsertIgnore(originalConfig.getBool(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.IS_INSERTINGORE, true));
+        databaseConfig.addTable(Collections.singletonList(tableName), schemaName);
+        databaseConfig.setColumns(columns, tableName, schemaName);
+
+        return databaseConfig;
+    }
+
+    private static Map<String, List<String>> splitBySchemaName(List<String> tables) {
+        HashMap<String, List<String>> res = new HashMap<String, List<String>>(16);
+
+        for (String schemaNameTableName: tables) {
+            String[] s = schemaNameTableName.split("\\.");
+            if (!res.containsKey(s[0])) {
+                res.put(s[0], new ArrayList<String>());
+            }
+            res.get(s[0]).add(s[1]);
+
+        }
+
+        return res;
+    }
+
+    public static Connection getAdbpgConnect(Configuration conf) {
+        String userName = conf.getString(Key.USERNAME);
+        String passWord = conf.getString(Key.PASSWORD);
+
+        return DBUtil.getConnection(DataBaseType.PostgreSQL, generateJdbcUrl(conf), userName, passWord);
+
+    }
+
+    private static String generateJdbcUrl(Configuration configuration) {
+        String host = configuration.getString(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.HOST);
+        String port = configuration.getString(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.PORT);
+        String databseName = configuration.getString(com.alibaba.datax.plugin.writer.adbpgwriter.util.Key.DATABASE);
+        String jdbcUrl = "jdbc:postgresql://" + host + ":" + port + "/" + databseName;
+        return jdbcUrl;
+
+    }
+    public static void prepare(Configuration originalConfig) {
+        List<String> preSqls = originalConfig.getList(Key.PRE_SQL,
+                String.class);
+
+        String tableName = originalConfig.getString(Key.TABLE);
+        List<String> renderedPreSqls = WriterUtil.renderPreOrPostSqls(
+                preSqls, tableName);
+
+        if (renderedPreSqls.size() == 0) {
+            return;
+        }
+
+        originalConfig.remove(Key.PRE_SQL);
+
+        Connection conn = getAdbpgConnect(originalConfig);
+        WriterUtil.executeSqls(conn, renderedPreSqls, generateJdbcUrl(originalConfig), DATABASE_TYPE);
+        DBUtil.closeDBResources(null, null, conn);
+
+
+    }
+
+    public static void post(Configuration configuration) {
+        List<String> postSqls = configuration.getList(Key.POST_SQL,
+                String.class);
+        String tableName = configuration.getString(Key.TABLE);
+        List<String> renderedPostSqls = WriterUtil.renderPreOrPostSqls(
+                postSqls, tableName);
+
+        if (renderedPostSqls.size() == 0) {
+            return;
+        }
+
+        configuration.remove(Key.POST_SQL);
+
+        Connection conn =  getAdbpgConnect(configuration);
+
+        WriterUtil.executeSqls(conn, renderedPostSqls, generateJdbcUrl(configuration), DATABASE_TYPE);
+        DBUtil.closeDBResources(null, null, conn);
+    }
+
+
+}

+ 12 - 0
adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/util/Constant.java

@@ -0,0 +1,12 @@
+package com.alibaba.datax.plugin.writer.adbpgwriter.util;
+/**
+ * @author yuncheng
+ */
+public class Constant {
+    public static final int DEFAULT_RETRY_TIMES = 3;
+
+    public static final String COLUMN_QUOTE_CHARACTER = "\"";
+
+
+
+}

+ 26 - 0
adbpgwriter/src/main/java/com/alibaba/datax/plugin/writer/adbpgwriter/util/Key.java

@@ -0,0 +1,26 @@
+package com.alibaba.datax.plugin.writer.adbpgwriter.util;
+/**
+ * @author yuncheng
+ */
+public class Key {
+
+    public final static String COLUMN = "column";
+    public final static String IS_INSERTINGORE = "insertIgnore";
+    public final static String HOST = "host";
+    public final static String PORT = "port";
+    public final static String DATABASE = "database";
+    public final static String SCHEMA = "schema";
+    public final static String EMPTY_AS_NULL = "emptyAsNull";
+
+    public final static String IGNORE_INSERT = "ignoreInsert";
+
+    public final static String RETRY_CONNECTION_TIME = "retryTimes";
+
+    public final static String RETRY_INTERVAL_TIME = "retryIntervalTime";
+
+    public final static String COMMIT_SIZE = "commitSize";
+
+    public final static String PARALLEL_NUMBER = "parallelNumber";
+
+    public final static String SHARED_DATASOURCE  = "shareDataSource";
+}

+ 6 - 0
adbpgwriter/src/main/resources/plugin.json

@@ -0,0 +1,6 @@
+{
+  "name": "adbpgwriter",
+  "class": "com.alibaba.datax.plugin.writer.adbpgwriter.AdbpgWriter",
+  "description": "",
+  "developer": "alibaba"
+}

+ 13 - 0
adbpgwriter/src/main/resources/plugin_job_template.json

@@ -0,0 +1,13 @@
+{
+  "name": "adbpgwriter",
+  "parameter": {
+    "username": "",
+    "password": "",
+    "host": "",
+    "port": "",
+    "database": "",
+    "schema": "",
+    "table": "",
+    "column": ["*"]
+  }
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 313 - 0
adswriter/doc/adswriter.md


+ 123 - 0
adswriter/pom.xml

@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.alibaba.datax</groupId>
+        <artifactId>datax-all</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>adswriter</artifactId>
+    <name>adswriter</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-common</artifactId>
+            <version>${datax-project-version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>slf4j-log4j12</artifactId>
+                    <groupId>org.slf4j</groupId>
+                </exclusion>
+                <exclusion>
+                    <groupId>mysql</groupId>
+                    <artifactId>mysql-connector-java</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-core</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>plugin-rdbms-util</artifactId>
+            <version>${datax-project-version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.alibaba</groupId>
+                    <artifactId>druid</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud.analyticdb</groupId>
+            <artifactId>adbclient</artifactId>
+            <version>1.0.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>1.1.12</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-exec</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>odpswriter</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>5.1.31</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-configuration</groupId>
+            <artifactId>commons-configuration</artifactId>
+            <version>1.10</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- compiler plugin -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${jdk-version}</source>
+                    <target>${jdk-version}</target>
+                    <encoding>${project-sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+            <!-- assembly plugin -->
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/package.xml</descriptor>
+                    </descriptors>
+                    <finalName>datax</finalName>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>dwzip</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 36 - 0
adswriter/src/main/assembly/package.xml

@@ -0,0 +1,36 @@
+<assembly
+	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+	<id></id>
+	<formats>
+		<format>dir</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+	<fileSets>
+		<fileSet>
+			<directory>src/main/resources</directory>
+			<includes>
+				<include>plugin.json</include>
+                <include>config.properties</include>
+				<include>plugin_job_template.json</include>
+			</includes>
+			<outputDirectory>plugin/writer/adswriter</outputDirectory>
+		</fileSet>
+		<fileSet>
+			<directory>target/</directory>
+			<includes>
+				<include>adswriter-0.0.1-SNAPSHOT.jar</include>
+			</includes>
+			<outputDirectory>plugin/writer/adswriter</outputDirectory>
+		</fileSet>
+	</fileSets>
+
+	<dependencySets>
+		<dependencySet>
+			<useProjectArtifact>false</useProjectArtifact>
+			<outputDirectory>plugin/writer/adswriter/libs</outputDirectory>
+			<scope>runtime</scope>
+		</dependencySet>
+	</dependencySets>
+</assembly>

+ 40 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/AdsException.java

@@ -0,0 +1,40 @@
+package com.alibaba.datax.plugin.writer.adswriter;
+
+public class AdsException extends Exception {
+
+    private static final long serialVersionUID = 1080618043484079794L;
+
+    public final static int ADS_CONN_URL_NOT_SET = -100;
+    public final static int ADS_CONN_USERNAME_NOT_SET = -101;
+    public final static int ADS_CONN_PASSWORD_NOT_SET = -102;
+    public final static int ADS_CONN_SCHEMA_NOT_SET = -103;
+
+    public final static int JOB_NOT_EXIST = -200;
+    public final static int JOB_FAILED = -201;
+
+    public final static int ADS_LOADDATA_SCHEMA_NULL = -300;
+    public final static int ADS_LOADDATA_TABLE_NULL = -301;
+    public final static int ADS_LOADDATA_SOURCEPATH_NULL = -302;
+    public final static int ADS_LOADDATA_JOBID_NOT_AVAIL = -303;
+    public final static int ADS_LOADDATA_FAILED = -304;
+
+    public final static int ADS_TABLEMETA_SCHEMA_NULL = -404;
+    public final static int ADS_TABLEMETA_TABLE_NULL = -405;
+
+    public final static int OTHER = -999;
+
+    private int code = OTHER;
+    private String message;
+
+    public AdsException(int code, String message, Throwable e) {
+        super(message, e);
+        this.code = code;
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage() {
+        return "Code=" + this.code + " Message=" + this.message;
+    }
+
+}

+ 410 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/AdsWriter.java

@@ -0,0 +1,410 @@
+package com.alibaba.datax.plugin.writer.adswriter;
+
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.plugin.RecordReceiver;
+import com.alibaba.datax.common.plugin.TaskPluginCollector;
+import com.alibaba.datax.common.spi.Writer;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.plugin.rdbms.util.DBUtil;
+import com.alibaba.datax.plugin.rdbms.util.DataBaseType;
+import com.alibaba.datax.plugin.rdbms.writer.util.WriterUtil;
+import com.alibaba.datax.plugin.writer.adswriter.ads.ColumnInfo;
+import com.alibaba.datax.plugin.writer.adswriter.ads.TableInfo;
+import com.alibaba.datax.plugin.writer.adswriter.insert.AdsClientProxy;
+import com.alibaba.datax.plugin.writer.adswriter.insert.AdsInsertProxy;
+import com.alibaba.datax.plugin.writer.adswriter.insert.AdsInsertUtil;
+import com.alibaba.datax.plugin.writer.adswriter.insert.AdsProxy;
+import com.alibaba.datax.plugin.writer.adswriter.load.AdsHelper;
+import com.alibaba.datax.plugin.writer.adswriter.load.TableMetaHelper;
+import com.alibaba.datax.plugin.writer.adswriter.load.TransferProjectConf;
+import com.alibaba.datax.plugin.writer.adswriter.odps.TableMeta;
+import com.alibaba.datax.plugin.writer.adswriter.util.AdsUtil;
+import com.alibaba.datax.plugin.writer.adswriter.util.Constant;
+import com.alibaba.datax.plugin.writer.adswriter.util.Key;
+import com.alibaba.datax.plugin.writer.odpswriter.OdpsWriter;
+import com.aliyun.odps.Instance;
+import com.aliyun.odps.Odps;
+import com.aliyun.odps.OdpsException;
+import com.aliyun.odps.account.Account;
+import com.aliyun.odps.account.AliyunAccount;
+import com.aliyun.odps.task.SQLTask;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class AdsWriter extends Writer {
+
+    public static class Job extends Writer.Job {
+        private static final Logger LOG = LoggerFactory.getLogger(Writer.Job.class);
+        public final static String ODPS_READER = "odpsreader";
+
+        private OdpsWriter.Job odpsWriterJobProxy = new OdpsWriter.Job();
+        private Configuration originalConfig;
+        private Configuration readerConfig;
+
+        /**
+         * 持有ads账号的ads helper
+         */
+        private AdsHelper adsHelper;
+        /**
+         * 持有odps账号的ads helper
+         */
+        private AdsHelper odpsAdsHelper;
+        /**
+         * 中转odps的配置,对应到writer配置的parameter.odps部分
+         */
+        private TransferProjectConf transProjConf;
+        private final int ODPSOVERTIME = 120000;
+        private String odpsTransTableName;
+
+        private String writeMode;
+        private long startTime;
+
+        @Override
+        public void init() {
+            startTime = System.currentTimeMillis();
+            this.originalConfig = super.getPluginJobConf();
+            this.writeMode = this.originalConfig.getString(Key.WRITE_MODE);
+            if(null == this.writeMode) {
+                LOG.warn("您未指定[writeMode]参数,  默认采用load模式, load模式只能用于离线表");
+                this.writeMode = Constant.LOADMODE;
+                this.originalConfig.set(Key.WRITE_MODE, "load");
+            }
+
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                AdsUtil.checkNecessaryConfig(this.originalConfig, this.writeMode);
+                loadModeInit();
+            } else if(Constant.INSERTMODE.equalsIgnoreCase(this.writeMode) || Constant.STREAMMODE.equalsIgnoreCase(this.writeMode)) {
+                AdsUtil.checkNecessaryConfig(this.originalConfig, this.writeMode);
+                List<String> allColumns = AdsInsertUtil.getAdsTableColumnNames(originalConfig);
+                AdsInsertUtil.dealColumnConf(originalConfig, allColumns);
+
+                LOG.debug("After job init(), originalConfig now is:[\n{}\n]",
+                        originalConfig.toJSON());
+            } else {
+                throw DataXException.asDataXException(AdsWriterErrorCode.INVALID_CONFIG_VALUE, "writeMode 必须为 'load' 或者 'insert' 或者 'stream'");
+            }
+        }
+
+        private void loadModeInit() {
+            this.adsHelper = AdsUtil.createAdsHelper(this.originalConfig);
+            this.odpsAdsHelper = AdsUtil.createAdsHelperWithOdpsAccount(this.originalConfig);
+            this.transProjConf = TransferProjectConf.create(this.originalConfig);
+            // 打印权限申请流程到日志中
+            LOG.info(String
+                    .format("%s%n%s%n%s",
+                            "如果您直接是odps->ads数据同步, 需要做2方面授权:",
+                            "[1] ads官方账号至少需要有待同步表的describe和select权限, 因为ads系统需要获取odps待同步表的结构和数据信息",
+                            "[2] 您配置的ads数据源访问账号ak, 需要有向指定的ads数据库发起load data的权限, 您可以在ads系统中添加授权"));
+            LOG.info(String
+                    .format("%s%s%n%s%n%s",
+                            "如果您直接是rds(或其它非odps数据源)->ads数据同步, 流程是先将数据装载如odps临时表,再从odps临时表->ads, ",
+                            String.format("中转odps项目为%s,中转项目账号为%s, 权限方面:",
+                                    this.transProjConf.getProject(),
+                                    this.transProjConf.getAccount()),
+                            "[1] ads官方账号至少需要有待同步表(这里是odps临时表)的describe和select权限, 因为ads系统需要获取odps待同步表的结构和数据信息,此部分部署时已经完成授权",
+                            String.format("[2] 中转odps对应的账号%s, 需要有向指定的ads数据库发起load data的权限, 您可以在ads系统中添加授权", this.transProjConf.getAccount())));
+
+            /**
+             * 如果是从odps导入到ads,直接load data然后System.exit()
+             */
+            if (super.getPeerPluginName().equals(ODPS_READER)) {
+                transferFromOdpsAndExit();
+            }
+            Account odpsAccount;
+            odpsAccount = new AliyunAccount(transProjConf.getAccessId(), transProjConf.getAccessKey());
+
+            Odps odps = new Odps(odpsAccount);
+            odps.setEndpoint(transProjConf.getOdpsServer());
+            odps.setDefaultProject(transProjConf.getProject());
+
+            TableMeta tableMeta;
+            try {
+                String adsTable = this.originalConfig.getString(Key.ADS_TABLE);
+                TableInfo tableInfo = adsHelper.getTableInfo(adsTable);
+                int lifeCycle = this.originalConfig.getInt(Key.Life_CYCLE);
+                tableMeta = TableMetaHelper.createTempODPSTable(tableInfo, lifeCycle);
+                this.odpsTransTableName = tableMeta.getTableName();
+                String sql = tableMeta.toDDL();
+                LOG.info("正在创建ODPS临时表: "+sql);
+                Instance instance = SQLTask.run(odps, transProjConf.getProject(), sql, null, null);
+                boolean terminated = false;
+                int time = 0;
+                while (!terminated && time < ODPSOVERTIME) {
+                    Thread.sleep(1000);
+                    terminated = instance.isTerminated();
+                    time += 1000;
+                }
+                LOG.info("正在创建ODPS临时表成功");
+            } catch (AdsException e) {
+                throw DataXException.asDataXException(AdsWriterErrorCode.ODPS_CREATETABLE_FAILED, e);
+            }catch (OdpsException e) {
+                throw DataXException.asDataXException(AdsWriterErrorCode.ODPS_CREATETABLE_FAILED,e);
+            } catch (InterruptedException e) {
+                throw DataXException.asDataXException(AdsWriterErrorCode.ODPS_CREATETABLE_FAILED,e);
+            }
+
+            Configuration newConf = AdsUtil.generateConf(this.originalConfig, this.odpsTransTableName,
+                    tableMeta, this.transProjConf);
+            odpsWriterJobProxy.setPluginJobConf(newConf);
+            odpsWriterJobProxy.init();
+        }
+
+        /**
+         * 当reader是odps的时候,直接call ads的load接口,完成后退出。
+         * 这种情况下,用户在odps reader里头填写的参数只有部分有效。
+         * 其中accessId、accessKey是忽略掉iao的。
+         */
+        private void transferFromOdpsAndExit() {
+            this.readerConfig = super.getPeerPluginJobConf();
+            String odpsTableName = this.readerConfig.getString(Key.ODPSTABLENAME);
+            List<String> userConfiguredPartitions = this.readerConfig.getList(Key.PARTITION, String.class);
+
+            if (userConfiguredPartitions == null) {
+                userConfiguredPartitions = Collections.emptyList();
+            }
+
+            if(userConfiguredPartitions.size() > 1) {
+                throw DataXException.asDataXException(AdsWriterErrorCode.ODPS_PARTITION_FAILED, "");
+            }
+
+            if(userConfiguredPartitions.size() == 0) {
+                loadAdsData(adsHelper, odpsTableName,null);
+            }else {
+                loadAdsData(adsHelper, odpsTableName,userConfiguredPartitions.get(0));
+            }
+            System.exit(0);
+        }
+
+        // 一般来说,是需要推迟到 task 中进行pre 的执行(单表情况例外)
+        @Override
+        public void prepare() {
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                //导数据到odps表中
+                this.odpsWriterJobProxy.prepare();
+            } else {
+                // 实时表模式非分库分表
+                String adsTable = this.originalConfig.getString(Key.ADS_TABLE);
+                List<String> preSqls = this.originalConfig.getList(Key.PRE_SQL,
+                        String.class);
+                List<String> renderedPreSqls = WriterUtil.renderPreOrPostSqls(
+                        preSqls, adsTable);
+                if (null != renderedPreSqls && !renderedPreSqls.isEmpty()) {
+                    // 说明有 preSql 配置,则此处删除掉
+                    this.originalConfig.remove(Key.PRE_SQL);
+                    Connection preConn = AdsUtil.getAdsConnect(this.originalConfig);
+                    LOG.info("Begin to execute preSqls:[{}]. context info:{}.",
+                            StringUtils.join(renderedPreSqls, ";"),
+                            this.originalConfig.getString(Key.ADS_URL));
+                    WriterUtil.executeSqls(preConn, renderedPreSqls,
+                            this.originalConfig.getString(Key.ADS_URL),
+                            DataBaseType.ADS);
+                    DBUtil.closeDBResources(null, null, preConn);
+                }
+            }
+        }
+
+        @Override
+        public List<Configuration> split(int mandatoryNumber) {
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                return this.odpsWriterJobProxy.split(mandatoryNumber);
+            } else {
+                List<Configuration> splitResult = new ArrayList<Configuration>();
+                for(int i = 0; i < mandatoryNumber; i++) {
+                    splitResult.add(this.originalConfig.clone());
+                }
+                return splitResult;
+            }
+        }
+
+        // 一般来说,是需要推迟到 task 中进行post 的执行(单表情况例外)
+        @Override
+        public void post() {
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                loadAdsData(odpsAdsHelper, this.odpsTransTableName, null);
+                this.odpsWriterJobProxy.post();
+            } else {
+                // 实时表模式非分库分表
+                String adsTable = this.originalConfig.getString(Key.ADS_TABLE);
+                List<String> postSqls = this.originalConfig.getList(
+                        Key.POST_SQL, String.class);
+                List<String> renderedPostSqls = WriterUtil.renderPreOrPostSqls(
+                        postSqls, adsTable);
+                if (null != renderedPostSqls && !renderedPostSqls.isEmpty()) {
+                    // 说明有 preSql 配置,则此处删除掉
+                    this.originalConfig.remove(Key.POST_SQL);
+                    Connection postConn = AdsUtil.getAdsConnect(this.originalConfig);
+                    LOG.info(
+                            "Begin to execute postSqls:[{}]. context info:{}.",
+                            StringUtils.join(renderedPostSqls, ";"),
+                            this.originalConfig.getString(Key.ADS_URL));
+                    WriterUtil.executeSqls(postConn, renderedPostSqls,
+                            this.originalConfig.getString(Key.ADS_URL),
+                            DataBaseType.ADS);
+                    DBUtil.closeDBResources(null, null, postConn);
+                }
+            }
+        }
+
+        @Override
+        public void destroy() {
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                this.odpsWriterJobProxy.destroy();
+            } else {
+                //insert mode do noting
+            }
+        }
+
+        private void loadAdsData(AdsHelper helper, String odpsTableName, String odpsPartition) {
+
+            String table = this.originalConfig.getString(Key.ADS_TABLE);
+            String project;
+            if (super.getPeerPluginName().equals(ODPS_READER)) {
+                project = this.readerConfig.getString(Key.PROJECT);
+            } else {
+                project = this.transProjConf.getProject();
+            }
+            String partition = this.originalConfig.getString(Key.PARTITION);
+            String sourcePath = AdsUtil.generateSourcePath(project,odpsTableName,odpsPartition);
+            /**
+             * 因为之前检查过,所以不用担心unbox的时候NPE
+             */
+            boolean overwrite = this.originalConfig.getBool(Key.OVER_WRITE);
+            try {
+                String id = helper.loadData(table,partition,sourcePath,overwrite);
+                LOG.info("ADS Load Data任务已经提交,job id: " + id);
+                boolean terminated = false;
+                int time = 0;
+                while(!terminated) {
+                    Thread.sleep(120000);
+                    terminated = helper.checkLoadDataJobStatus(id);
+                    time += 2;
+                    LOG.info("ADS 正在导数据中,整个过程需要20分钟以上,请耐心等待,目前已执行 "+ time+" 分钟");
+                }
+                LOG.info("ADS 导数据已成功");
+            } catch (AdsException e) {
+                if (super.getPeerPluginName().equals(ODPS_READER)) {
+                    // TODO 使用云账号
+                    AdsWriterErrorCode.ADS_LOAD_ODPS_FAILED.setAdsAccount(helper.getUserName());
+                    throw DataXException.asDataXException(AdsWriterErrorCode.ADS_LOAD_ODPS_FAILED,e);
+                } else {
+                    throw DataXException.asDataXException(AdsWriterErrorCode.ADS_LOAD_TEMP_ODPS_FAILED,e);
+                }
+            } catch (InterruptedException e) {
+                throw DataXException.asDataXException(AdsWriterErrorCode.ODPS_CREATETABLE_FAILED,e);
+            }
+        }
+    }
+
+    public static class Task extends Writer.Task {
+        private static final Logger LOG = LoggerFactory.getLogger(Writer.Task.class);
+        private Configuration writerSliceConfig;
+        private OdpsWriter.Task odpsWriterTaskProxy = new OdpsWriter.Task();
+
+
+        private String writeMode;
+        private String schema;
+        private String table;
+        private int columnNumber;
+        // warn: 只有在insert, stream模式才有, 对于load模式表明为odps临时表了
+        private TableInfo tableInfo;
+
+        private String writeProxy;
+        AdsProxy proxy = null;
+
+        @Override
+        public void init() {
+            writerSliceConfig = super.getPluginJobConf();
+            this.writeMode = this.writerSliceConfig.getString(Key.WRITE_MODE);
+            this.schema = writerSliceConfig.getString(Key.SCHEMA);
+            this.table =  writerSliceConfig.getString(Key.ADS_TABLE);
+
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                odpsWriterTaskProxy.setPluginJobConf(writerSliceConfig);
+                odpsWriterTaskProxy.init();
+            } else if(Constant.INSERTMODE.equalsIgnoreCase(this.writeMode) || Constant.STREAMMODE.equalsIgnoreCase(this.writeMode)) {
+
+                if (Constant.STREAMMODE.equalsIgnoreCase(this.writeMode)) {
+                    this.writeProxy = "datax";
+                } else {
+                    this.writeProxy = this.writerSliceConfig.getString("writeProxy", "adbClient");
+                }
+                this.writerSliceConfig.set("writeProxy", this.writeProxy);
+
+                try {
+                    this.tableInfo = AdsUtil.createAdsHelper(this.writerSliceConfig).getTableInfo(this.table);
+                } catch (AdsException e) {
+                    throw DataXException.asDataXException(AdsWriterErrorCode.CREATE_ADS_HELPER_FAILED, e);
+                }
+                List<String> allColumns = new ArrayList<String>();
+                List<ColumnInfo> columnInfo =  this.tableInfo.getColumns();
+                for (ColumnInfo eachColumn : columnInfo) {
+                    allColumns.add(eachColumn.getName());
+                }
+                LOG.info("table:[{}] all columns:[\n{}\n].", this.writerSliceConfig.get(Key.ADS_TABLE), StringUtils.join(allColumns, ","));
+                AdsInsertUtil.dealColumnConf(writerSliceConfig, allColumns);
+                List<String> userColumns = writerSliceConfig.getList(Key.COLUMN, String.class);
+                this.columnNumber = userColumns.size();
+            } else {
+                throw DataXException.asDataXException(AdsWriterErrorCode.INVALID_CONFIG_VALUE, "writeMode 必须为 'load' 或者 'insert' 或者 'stream'");
+            }
+        }
+
+        @Override
+        public void prepare() {
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                odpsWriterTaskProxy.prepare();
+            } else {
+                //do nothing
+            }
+        }
+
+        public void startWrite(RecordReceiver recordReceiver) {
+            // 这里的是非odps数据源->odps中转临时表数据同步, load操作在job post阶段完成
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                odpsWriterTaskProxy.setTaskPluginCollector(super.getTaskPluginCollector());
+                odpsWriterTaskProxy.startWrite(recordReceiver);
+            } else {
+                // insert 模式
+                List<String> columns = writerSliceConfig.getList(Key.COLUMN, String.class);
+                Connection connection = AdsUtil.getAdsConnect(this.writerSliceConfig);
+                TaskPluginCollector taskPluginCollector = super.getTaskPluginCollector();
+
+                if (StringUtils.equalsIgnoreCase(this.writeProxy, "adbClient")) {
+                    this.proxy = new AdsClientProxy(table, columns, writerSliceConfig, taskPluginCollector, this.tableInfo);
+                } else {
+                    this.proxy = new AdsInsertProxy(schema + "." + table, columns, writerSliceConfig, taskPluginCollector, this.tableInfo);
+                }
+                proxy.startWriteWithConnection(recordReceiver, connection, columnNumber);
+            }
+        }
+
+        @Override
+        public void post() {
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                odpsWriterTaskProxy.post();
+            } else {
+                //do noting until now
+            }
+        }
+
+        @Override
+        public void destroy() {
+            if(Constant.LOADMODE.equalsIgnoreCase(this.writeMode)) {
+                odpsWriterTaskProxy.destroy();
+            } else {
+                //do noting until now
+                if (null != this.proxy) {
+                    this.proxy.closeResource();
+                }
+            }
+        }
+    }
+
+}

+ 54 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/AdsWriterErrorCode.java

@@ -0,0 +1,54 @@
+package com.alibaba.datax.plugin.writer.adswriter;
+
+import com.alibaba.datax.common.spi.ErrorCode;
+
+public enum AdsWriterErrorCode implements ErrorCode {
+    REQUIRED_VALUE("AdsWriter-00", "您缺失了必须填写的参数值."),
+    NO_ADS_TABLE("AdsWriter-01", "ADS表不存在."),
+    ODPS_CREATETABLE_FAILED("AdsWriter-02", "创建ODPS临时表失败,请联系ADS 技术支持"),
+    ADS_LOAD_TEMP_ODPS_FAILED("AdsWriter-03", "ADS从ODPS临时表导数据失败,请联系ADS 技术支持"),
+    TABLE_TRUNCATE_ERROR("AdsWriter-04", "清空 ODPS 目的表时出错."),
+    CREATE_ADS_HELPER_FAILED("AdsWriter-05", "创建ADSHelper对象出错,请联系ADS 技术支持"),
+    ODPS_PARTITION_FAILED("AdsWriter-06", "ODPS Reader不允许配置多个partition,目前只支持三种配置方式,\"partition\":[\"pt=*,ds=*\"](读取test表所有分区的数据); \n" +
+            "\"partition\":[\"pt=1,ds=*\"](读取test表下面,一级分区pt=1下面的所有二级分区); \n" +
+            "\"partition\":[\"pt=1,ds=hangzhou\"](读取test表下面,一级分区pt=1下面,二级分区ds=hz的数据)"),
+    ADS_LOAD_ODPS_FAILED("AdsWriter-07", "ADS从ODPS导数据失败,请联系ADS 技术支持,先检查ADS账号是否已加到该ODPS Project中。ADS账号为:"),
+    INVALID_CONFIG_VALUE("AdsWriter-08", "不合法的配置值."),
+
+    GET_ADS_TABLE_MEATA_FAILED("AdsWriter-11", "获取ADS table原信息失败");
+
+    private final String code;
+    private final String description;
+    private String adsAccount;
+
+
+    private AdsWriterErrorCode(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public void setAdsAccount(String adsAccount) {
+        this.adsAccount = adsAccount;
+    }
+
+    @Override
+    public String getCode() {
+        return this.code;
+    }
+
+    @Override
+    public String getDescription() {
+        return this.description;
+    }
+
+    @Override
+    public String toString() {
+        if (this.code.equals("AdsWriter-07")){
+            return String.format("Code:[%s], Description:[%s][%s]. ", this.code,
+                    this.description,adsAccount);
+        }else{
+            return String.format("Code:[%s], Description:[%s]. ", this.code,
+                    this.description);
+        }
+    }
+}

+ 406 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/ads/ColumnDataType.java

@@ -0,0 +1,406 @@
+package com.alibaba.datax.plugin.writer.adswriter.ads;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * ADS column data type.
+ *
+ * @since 0.0.1
+ */
+public class ColumnDataType {
+
+    // public static final int NULL = 0;
+    public static final int BOOLEAN = 1;
+    public static final int BYTE = 2;
+    public static final int SHORT = 3;
+    public static final int INT = 4;
+    public static final int LONG = 5;
+    public static final int DECIMAL = 6;
+    public static final int DOUBLE = 7;
+    public static final int FLOAT = 8;
+    public static final int TIME = 9;
+    public static final int DATE = 10;
+    public static final int TIMESTAMP = 11;
+    public static final int STRING = 13;
+    // public static final int STRING_IGNORECASE = 14;
+    // public static final int STRING_FIXED = 21;
+
+    public static final int MULTI_VALUE = 22;
+
+    public static final int TYPE_COUNT = MULTI_VALUE + 1;
+
+    /**
+     * The list of types. An ArrayList so that Tomcat doesn't set it to null when clearing references.
+     */
+    private static final ArrayList<ColumnDataType> TYPES = new ArrayList<ColumnDataType>();
+    private static final HashMap<String, ColumnDataType> TYPES_BY_NAME = new HashMap<String, ColumnDataType>();
+    private static final ArrayList<ColumnDataType> TYPES_BY_VALUE_TYPE = new ArrayList<ColumnDataType>();
+
+    /**
+     * @param dataTypes
+     * @return
+     */
+    public static String getNames(int[] dataTypes) {
+        List<String> names = new ArrayList<String>(dataTypes.length);
+        for (final int dataType : dataTypes) {
+            names.add(ColumnDataType.getDataType(dataType).name);
+        }
+        return names.toString();
+    }
+
+    public int type;
+    public String name;
+    public int sqlType;
+    public String jdbc;
+
+    /**
+     * How closely the data type maps to the corresponding JDBC SQL type (low is best).
+     */
+    public int sqlTypePos;
+
+    static {
+        for (int i = 0; i < TYPE_COUNT; i++) {
+            TYPES_BY_VALUE_TYPE.add(null);
+        }
+        // add(NULL, Types.NULL, "Null", new String[] { "NULL" });
+        add(STRING, Types.VARCHAR, "String", new String[] { "VARCHAR", "VARCHAR2", "NVARCHAR", "NVARCHAR2",
+                "VARCHAR_CASESENSITIVE", "CHARACTER VARYING", "TID" });
+        add(STRING, Types.LONGVARCHAR, "String", new String[] { "LONGVARCHAR", "LONGNVARCHAR" });
+        // add(STRING_FIXED, Types.CHAR, "String", new String[] { "CHAR", "CHARACTER", "NCHAR" });
+        // add(STRING_IGNORECASE, Types.VARCHAR, "String", new String[] { "VARCHAR_IGNORECASE" });
+        add(BOOLEAN, Types.BOOLEAN, "Boolean", new String[] { "BOOLEAN", "BIT", "BOOL" });
+        add(BYTE, Types.TINYINT, "Byte", new String[] { "TINYINT" });
+        add(SHORT, Types.SMALLINT, "Short", new String[] { "SMALLINT", "YEAR", "INT2" });
+        add(INT, Types.INTEGER, "Int", new String[] { "INTEGER", "INT", "MEDIUMINT", "INT4", "SIGNED" });
+        add(INT, Types.INTEGER, "Int", new String[] { "SERIAL" });
+        add(LONG, Types.BIGINT, "Long", new String[] { "BIGINT", "INT8", "LONG" });
+        add(LONG, Types.BIGINT, "Long", new String[] { "IDENTITY", "BIGSERIAL" });
+        add(DECIMAL, Types.DECIMAL, "BigDecimal", new String[] { "DECIMAL", "DEC" });
+        add(DECIMAL, Types.NUMERIC, "BigDecimal", new String[] { "NUMERIC", "NUMBER" });
+        add(FLOAT, Types.REAL, "Float", new String[] { "REAL", "FLOAT4" });
+        add(DOUBLE, Types.DOUBLE, "Double", new String[] { "DOUBLE", "DOUBLE PRECISION" });
+        add(DOUBLE, Types.FLOAT, "Double", new String[] { "FLOAT", "FLOAT8" });
+        add(TIME, Types.TIME, "Time", new String[] { "TIME" });
+        add(DATE, Types.DATE, "Date", new String[] { "DATE" });
+        add(TIMESTAMP, Types.TIMESTAMP, "Timestamp", new String[] { "TIMESTAMP", "DATETIME", "SMALLDATETIME" });
+        add(MULTI_VALUE, Types.VARCHAR, "String", new String[] { "MULTIVALUE" });
+    }
+
+    private static void add(int type, int sqlType, String jdbc, String[] names) {
+        for (int i = 0; i < names.length; i++) {
+            ColumnDataType dt = new ColumnDataType();
+            dt.type = type;
+            dt.sqlType = sqlType;
+            dt.jdbc = jdbc;
+            dt.name = names[i];
+            for (ColumnDataType t2 : TYPES) {
+                if (t2.sqlType == dt.sqlType) {
+                    dt.sqlTypePos++;
+                }
+            }
+            TYPES_BY_NAME.put(dt.name, dt);
+            if (TYPES_BY_VALUE_TYPE.get(type) == null) {
+                TYPES_BY_VALUE_TYPE.set(type, dt);
+            }
+            TYPES.add(dt);
+        }
+    }
+
+    /**
+     * Get the list of data types.
+     * 
+     * @return the list
+     */
+    public static ArrayList<ColumnDataType> getTypes() {
+        return TYPES;
+    }
+
+    /**
+     * Get the name of the Java class for the given value type.
+     * 
+     * @param type the value type
+     * @return the class name
+     */
+    public static String getTypeClassName(int type) {
+        switch (type) {
+            case BOOLEAN:
+                // "java.lang.Boolean";
+                return Boolean.class.getName();
+            case BYTE:
+                // "java.lang.Byte";
+                return Byte.class.getName();
+            case SHORT:
+                // "java.lang.Short";
+                return Short.class.getName();
+            case INT:
+                // "java.lang.Integer";
+                return Integer.class.getName();
+            case LONG:
+                // "java.lang.Long";
+                return Long.class.getName();
+            case DECIMAL:
+                // "java.math.BigDecimal";
+                return BigDecimal.class.getName();
+            case TIME:
+                // "java.sql.Time";
+                return Time.class.getName();
+            case DATE:
+                // "java.sql.Date";
+                return Date.class.getName();
+            case TIMESTAMP:
+                // "java.sql.Timestamp";
+                return Timestamp.class.getName();
+            case STRING:
+                // case STRING_IGNORECASE:
+                // case STRING_FIXED:
+            case MULTI_VALUE:
+                // "java.lang.String";
+                return String.class.getName();
+            case DOUBLE:
+                // "java.lang.Double";
+                return Double.class.getName();
+            case FLOAT:
+                // "java.lang.Float";
+                return Float.class.getName();
+                // case NULL:
+                // return null;
+            default:
+                throw new IllegalArgumentException("type=" + type);
+        }
+    }
+
+    /**
+     * Get the data type object for the given value type.
+     * 
+     * @param type the value type
+     * @return the data type object
+     */
+    public static ColumnDataType getDataType(int type) {
+        if (type < 0 || type >= TYPE_COUNT) {
+            throw new IllegalArgumentException("type=" + type);
+        }
+        ColumnDataType dt = TYPES_BY_VALUE_TYPE.get(type);
+        // if (dt == null) {
+        // dt = TYPES_BY_VALUE_TYPE.get(NULL);
+        // }
+        return dt;
+    }
+
+    /**
+     * Convert a value type to a SQL type.
+     * 
+     * @param type the value type
+     * @return the SQL type
+     */
+    public static int convertTypeToSQLType(int type) {
+        return getDataType(type).sqlType;
+    }
+
+    /**
+     * Convert a SQL type to a value type.
+     * 
+     * @param sqlType the SQL type
+     * @return the value type
+     */
+    public static int convertSQLTypeToValueType(int sqlType) {
+        switch (sqlType) {
+        // case Types.CHAR:
+        // case Types.NCHAR:
+        // return STRING_FIXED;
+            case Types.VARCHAR:
+            case Types.LONGVARCHAR:
+            case Types.NVARCHAR:
+            case Types.LONGNVARCHAR:
+                return STRING;
+            case Types.NUMERIC:
+            case Types.DECIMAL:
+                return DECIMAL;
+            case Types.BIT:
+            case Types.BOOLEAN:
+                return BOOLEAN;
+            case Types.INTEGER:
+                return INT;
+            case Types.SMALLINT:
+                return SHORT;
+            case Types.TINYINT:
+                return BYTE;
+            case Types.BIGINT:
+                return LONG;
+            case Types.REAL:
+                return FLOAT;
+            case Types.DOUBLE:
+            case Types.FLOAT:
+                return DOUBLE;
+            case Types.DATE:
+                return DATE;
+            case Types.TIME:
+                return TIME;
+            case Types.TIMESTAMP:
+                return TIMESTAMP;
+                // case Types.NULL:
+                // return NULL;
+            default:
+                throw new IllegalArgumentException("JDBC Type: " + sqlType);
+        }
+    }
+
+    /**
+     * Get the value type for the given Java class.
+     * 
+     * @param x the Java class
+     * @return the value type
+     */
+    public static int getTypeFromClass(Class<?> x) {
+        // if (x == null || Void.TYPE == x) {
+        // return NULL;
+        // }
+        if (x.isPrimitive()) {
+            x = getNonPrimitiveClass(x);
+        }
+        if (String.class == x) {
+            return STRING;
+        } else if (Integer.class == x) {
+            return INT;
+        } else if (Long.class == x) {
+            return LONG;
+        } else if (Boolean.class == x) {
+            return BOOLEAN;
+        } else if (Double.class == x) {
+            return DOUBLE;
+        } else if (Byte.class == x) {
+            return BYTE;
+        } else if (Short.class == x) {
+            return SHORT;
+        } else if (Float.class == x) {
+            return FLOAT;
+            // } else if (Void.class == x) {
+            // return NULL;
+        } else if (BigDecimal.class.isAssignableFrom(x)) {
+            return DECIMAL;
+        } else if (Date.class.isAssignableFrom(x)) {
+            return DATE;
+        } else if (Time.class.isAssignableFrom(x)) {
+            return TIME;
+        } else if (Timestamp.class.isAssignableFrom(x)) {
+            return TIMESTAMP;
+        } else if (java.util.Date.class.isAssignableFrom(x)) {
+            return TIMESTAMP;
+        } else {
+            throw new IllegalArgumentException("class=" + x);
+        }
+    }
+
+    /**
+     * Convert primitive class names to java.lang.* class names.
+     * 
+     * @param clazz the class (for example: int)
+     * @return the non-primitive class (for example: java.lang.Integer)
+     */
+    public static Class<?> getNonPrimitiveClass(Class<?> clazz) {
+        if (!clazz.isPrimitive()) {
+            return clazz;
+        } else if (clazz == boolean.class) {
+            return Boolean.class;
+        } else if (clazz == byte.class) {
+            return Byte.class;
+        } else if (clazz == char.class) {
+            return Character.class;
+        } else if (clazz == double.class) {
+            return Double.class;
+        } else if (clazz == float.class) {
+            return Float.class;
+        } else if (clazz == int.class) {
+            return Integer.class;
+        } else if (clazz == long.class) {
+            return Long.class;
+        } else if (clazz == short.class) {
+            return Short.class;
+        } else if (clazz == void.class) {
+            return Void.class;
+        }
+        return clazz;
+    }
+
+    /**
+     * Get a data type object from a type name.
+     * 
+     * @param s the type name
+     * @return the data type object
+     */
+    public static ColumnDataType getTypeByName(String s) {
+        return TYPES_BY_NAME.get(s);
+    }
+
+    /**
+     * Check if the given value type is a String (VARCHAR,...).
+     * 
+     * @param type the value type
+     * @return true if the value type is a String type
+     */
+    public static boolean isStringType(int type) {
+        if (type == STRING /* || type == STRING_FIXED || type == STRING_IGNORECASE */
+                || type == MULTI_VALUE) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return
+     */
+    public boolean supportsAdd() {
+        return supportsAdd(type);
+    }
+
+    /**
+     * Check if the given value type supports the add operation.
+     * 
+     * @param type the value type
+     * @return true if add is supported
+     */
+    public static boolean supportsAdd(int type) {
+        switch (type) {
+            case BYTE:
+            case DECIMAL:
+            case DOUBLE:
+            case FLOAT:
+            case INT:
+            case LONG:
+            case SHORT:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Get the data type that will not overflow when calling 'add' 2 billion times.
+     * 
+     * @param type the value type
+     * @return the data type that supports adding
+     */
+    public static int getAddProofType(int type) {
+        switch (type) {
+            case BYTE:
+                return LONG;
+            case FLOAT:
+                return DOUBLE;
+            case INT:
+                return LONG;
+            case LONG:
+                return DECIMAL;
+            case SHORT:
+                return LONG;
+            default:
+                return type;
+        }
+    }
+
+}

+ 72 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/ads/ColumnInfo.java

@@ -0,0 +1,72 @@
+package com.alibaba.datax.plugin.writer.adswriter.ads;
+
+/**
+ * ADS column meta.<br>
+ * <p>
+ * select ordinal_position,column_name,data_type,type_name,column_comment <br>
+ * from information_schema.columns <br>
+ * where table_schema='db_name' and table_name='table_name' <br>
+ * and is_deleted=0 <br>
+ * order by ordinal_position limit 1000 <br>
+ * </p>
+ *
+ * @since 0.0.1
+ */
+public class ColumnInfo {
+
+    private int ordinal;
+    private String name;
+    private ColumnDataType dataType;
+    private boolean isDeleted;
+    private String comment;
+
+    public int getOrdinal() {
+        return ordinal;
+    }
+
+    public void setOrdinal(int ordinal) {
+        this.ordinal = ordinal;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public ColumnDataType getDataType() {
+        return dataType;
+    }
+
+    public void setDataType(ColumnDataType dataType) {
+        this.dataType = dataType;
+    }
+
+    public boolean isDeleted() {
+        return isDeleted;
+    }
+
+    public void setDeleted(boolean isDeleted) {
+        this.isDeleted = isDeleted;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("ColumnInfo [ordinal=").append(ordinal).append(", name=").append(name).append(", dataType=")
+                .append(dataType).append(", isDeleted=").append(isDeleted).append(", comment=").append(comment)
+                .append("]");
+        return builder.toString();
+    }
+
+}

+ 135 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/ads/TableInfo.java

@@ -0,0 +1,135 @@
+package com.alibaba.datax.plugin.writer.adswriter.ads;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ADS table meta.<br>
+ * <p>
+ * select table_schema, table_name,comments <br>
+ * from information_schema.tables <br>
+ * where table_schema='alimama' and table_name='click_af' limit 1 <br>
+ * </p>
+ * <p>
+ * select ordinal_position,column_name,data_type,type_name,column_comment <br>
+ * from information_schema.columns <br>
+ * where table_schema='db_name' and table_name='table_name' <br>
+ * and is_deleted=0 <br>
+ * order by ordinal_position limit 1000 <br>
+ * </p>
+ *
+ * @since 0.0.1
+ */
+public class TableInfo {
+
+    private String tableSchema;
+    private String tableName;
+    private List<ColumnInfo> columns;
+    private String comments;
+    private String tableType;
+
+    private String updateType;
+    private String partitionType;
+    private String partitionColumn;
+    private int partitionCount;
+    private List<String> primaryKeyColumns;
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("TableInfo [tableSchema=").append(tableSchema).append(", tableName=").append(tableName)
+                .append(", columns=").append(columns).append(", comments=").append(comments).append(",updateType=").append(updateType)
+                .append(",partitionType=").append(partitionType).append(",partitionColumn=").append(partitionColumn).append(",partitionCount=").append(partitionCount)
+                .append(",primaryKeyColumns=").append(primaryKeyColumns).append("]");
+        return builder.toString();
+    }
+
+    public String getTableSchema() {
+        return tableSchema;
+    }
+
+    public void setTableSchema(String tableSchema) {
+        this.tableSchema = tableSchema;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+
+    public List<ColumnInfo> getColumns() {
+        return columns;
+    }
+    
+    public List<String> getColumnsNames() {
+        List<String> columnNames = new ArrayList<String>();
+        for (ColumnInfo column : this.getColumns()) {
+            columnNames.add(column.getName());
+        }
+        return columnNames;
+    }
+
+    public void setColumns(List<ColumnInfo> columns) {
+        this.columns = columns;
+    }
+
+    public String getComments() {
+        return comments;
+    }
+
+    public void setComments(String comments) {
+        this.comments = comments;
+    }
+    
+    public String getTableType() {
+        return tableType;
+    }
+
+    public void setTableType(String tableType) {
+        this.tableType = tableType;
+    }
+
+    public String getUpdateType() {
+        return updateType;
+    }
+
+    public void setUpdateType(String updateType) {
+        this.updateType = updateType;
+    }
+
+    public String getPartitionType() {
+        return partitionType;
+    }
+
+    public void setPartitionType(String partitionType) {
+        this.partitionType = partitionType;
+    }
+
+    public String getPartitionColumn() {
+        return partitionColumn;
+    }
+
+    public void setPartitionColumn(String partitionColumn) {
+        this.partitionColumn = partitionColumn;
+    }
+
+    public int getPartitionCount() {
+        return partitionCount;
+    }
+
+    public void setPartitionCount(int partitionCount) {
+        this.partitionCount = partitionCount;
+    }
+
+    public List<String> getPrimaryKeyColumns() {
+        return primaryKeyColumns;
+    }
+
+    public void setPrimaryKeyColumns(List<String> primaryKeyColumns) {
+        this.primaryKeyColumns = primaryKeyColumns;
+    }
+
+}

+ 6 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/ads/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * ADS meta and service.
+ *
+ * @since 0.0.1
+ */
+package com.alibaba.datax.plugin.writer.adswriter.ads;

+ 222 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/AdsClientProxy.java

@@ -0,0 +1,222 @@
+package com.alibaba.datax.plugin.writer.adswriter.insert;
+
+import com.alibaba.cloud.analyticdb.adbclient.AdbClient;
+import com.alibaba.cloud.analyticdb.adbclient.AdbClientException;
+import com.alibaba.cloud.analyticdb.adbclient.DatabaseConfig;
+import com.alibaba.cloud.analyticdb.adbclient.Row;
+import com.alibaba.datax.common.element.Column;
+import com.alibaba.datax.common.element.Record;
+import com.alibaba.datax.common.element.StringColumn;
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.plugin.RecordReceiver;
+import com.alibaba.datax.common.plugin.TaskPluginCollector;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.core.transport.record.DefaultRecord;
+import com.alibaba.datax.plugin.rdbms.util.DBUtil;
+import com.alibaba.datax.plugin.rdbms.util.DBUtilErrorCode;
+import com.alibaba.datax.plugin.writer.adswriter.AdsWriterErrorCode;
+import com.alibaba.datax.plugin.writer.adswriter.ads.TableInfo;
+import com.alibaba.datax.plugin.writer.adswriter.util.Constant;
+import com.alibaba.datax.plugin.writer.adswriter.util.Key;
+import com.alibaba.fastjson2.JSON;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.Types;
+import java.util.*;
+
+public class AdsClientProxy implements AdsProxy {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AdsClientProxy.class);
+
+    private String table;
+    private TaskPluginCollector taskPluginCollector;
+    public Configuration configuration;
+
+    // columnName: <java sql type, ads type name>
+    private Map<String, Pair<Integer, String>> adsTableColumnsMetaData;
+    private Map<String, Pair<Integer, String>> userConfigColumnsMetaData;
+    private boolean useRawData[];
+
+    private AdbClient adbClient;
+
+    /**
+     * warn: not support columns as *
+     */
+    public AdsClientProxy(String table, List<String> columns, Configuration configuration,
+                          TaskPluginCollector taskPluginCollector, TableInfo tableInfo) {
+        this.configuration = configuration;
+        this.taskPluginCollector = taskPluginCollector;
+
+        this.adsTableColumnsMetaData = AdsInsertUtil.getColumnMetaData(tableInfo, columns);
+        this.userConfigColumnsMetaData = new HashMap<String, Pair<Integer, String>>();
+        List<String> adsColumnsNames = tableInfo.getColumnsNames();
+        // 要使用用户配置的column顺序
+        this.useRawData = new boolean[columns.size()];
+        for (int i = 0; i < columns.size(); i++) {
+            String oriEachColumn = columns.get(i);
+            String eachColumn = oriEachColumn;
+            // 防御性保留字
+            if (eachColumn.startsWith(Constant.ADS_QUOTE_CHARACTER)
+                    && eachColumn.endsWith(Constant.ADS_QUOTE_CHARACTER)) {
+                eachColumn = eachColumn.substring(1, eachColumn.length() - 1);
+            }
+            for (String eachAdsColumn : adsColumnsNames) {
+                if (eachColumn.equalsIgnoreCase(eachAdsColumn)) {
+                    Pair<Integer, String> eachMeta = this.adsTableColumnsMetaData.get(eachAdsColumn);
+                    this.userConfigColumnsMetaData.put(oriEachColumn, eachMeta);
+                    int columnSqltype = eachMeta.getLeft();
+                    switch (columnSqltype) {
+                    case Types.DATE:
+                    case Types.TIME:
+                    case Types.TIMESTAMP:
+                        this.useRawData[i] = false;
+                        break;
+                    default:
+                        this.useRawData[i] = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        DatabaseConfig databaseConfig = new DatabaseConfig();
+        String url = configuration.getString(Key.ADS_URL);
+        String[] hostAndPort = StringUtils.split(url, ":");
+        if (hostAndPort.length != 2) {
+            throw DataXException.asDataXException(AdsWriterErrorCode.INVALID_CONFIG_VALUE,
+                    "url should be in host:port format!");
+        }
+        this.table = table.toLowerCase();
+        databaseConfig.setHost(hostAndPort[0]);
+        databaseConfig.setPort(Integer.parseInt(hostAndPort[1]));
+        databaseConfig.setUser(configuration.getString(Key.USERNAME));
+        databaseConfig.setPassword(configuration.getString(Key.PASSWORD));
+        databaseConfig.setDatabase(configuration.getString(Key.SCHEMA));
+        databaseConfig.setTable(Collections.singletonList(this.table));
+        databaseConfig.setColumns(this.table, columns);
+
+        // 如果出现insert失败,是否跳过
+        boolean ignoreInsertError = configuration.getBool("ignoreInsertError", false);
+        databaseConfig.setIgnoreInsertError(ignoreInsertError);
+
+        // If the value of column is empty, set null
+        boolean emptyAsNull = configuration.getBool(Key.EMPTY_AS_NULL, false);
+        databaseConfig.setEmptyAsNull(emptyAsNull);
+
+        // 使用insert ignore into方式进行插入
+        boolean ignoreInsert = configuration.getBool(Key.IGNORE_INSERT, false);
+        databaseConfig.setInsertIgnore(ignoreInsert);
+
+        // commit时,写入ADB出现异常时重试的3次
+        int retryTimes = configuration.getInt(Key.RETRY_CONNECTION_TIME, Constant.DEFAULT_RETRY_TIMES);
+        databaseConfig.setRetryTimes(retryTimes);
+
+        // 重试间隔的时间为1s,单位是ms
+        int retryIntervalTime = configuration.getInt(Key.RETRY_INTERVAL_TIME, 1000);
+        databaseConfig.setRetryIntervalTime(retryIntervalTime);
+
+        // 设置自动提交的SQL长度(单位Byte),默认为32KB,一般不建议设置
+        int commitSize = configuration.getInt("commitSize", 32768);
+        databaseConfig.setCommitSize(commitSize);
+
+        // sdk默认为true
+        boolean partitionBatch = configuration.getBool("partitionBatch", true);
+        databaseConfig.setPartitionBatch(partitionBatch);
+
+        // 设置写入adb时的并发线程数,默认4,针对配置的所有表
+        int parallelNumber = configuration.getInt("parallelNumber", 4);
+        databaseConfig.setParallelNumber(parallelNumber);
+
+        // 设置client中使用的logger对象,此处使用slf4j.Logger
+        databaseConfig.setLogger(AdsClientProxy.LOG);
+
+        // 设置在拼接insert sql时是否需要带上字段名,默认为true
+        boolean insertWithColumnName = configuration.getBool("insertWithColumnName", true);
+        databaseConfig.setInsertWithColumnName(insertWithColumnName);
+
+        // sdk 默认值为true
+        boolean shareDataSource = configuration.getBool("shareDataSource", true);
+        databaseConfig.setShareDataSource(shareDataSource);
+
+        String password = databaseConfig.getPassword();
+        databaseConfig.setPassword(password.replaceAll(".", "*"));
+        // 避免敏感信息直接打印
+        LOG.info("Adb database config is : {}", JSON.toJSONString(databaseConfig));
+        databaseConfig.setPassword(password);
+
+        // Initialize AdbClient,初始化实例之后,databaseConfig的配置信息不能再修改
+        this.adbClient = new AdbClient(databaseConfig);
+    }
+
+    @Override
+    public void startWriteWithConnection(RecordReceiver recordReceiver, Connection connection, int columnNumber) {
+        try {
+            Record record;
+            while ((record = recordReceiver.getFromReader()) != null) {
+
+                Row row = new Row();
+                List<Object> values = new ArrayList<Object>();
+                this.prepareColumnTypeValue(record, values);
+                row.setColumnValues(values);
+
+                try {
+                    this.adbClient.addRow(this.table, row);
+                } catch (AdbClientException e) {
+                    if (101 == e.getCode()) {
+                        for (String each : e.getErrData()) {
+                            Record dirtyData = new DefaultRecord();
+                            dirtyData.addColumn(new StringColumn(each));
+                            this.taskPluginCollector.collectDirtyRecord(dirtyData, e.getMessage());
+                        }
+                    } else {
+                        throw e;
+                    }
+                }
+            }
+
+            try {
+                this.adbClient.commit();
+            } catch (AdbClientException e) {
+                if (101 == e.getCode()) {
+                    for (String each : e.getErrData()) {
+                        Record dirtyData = new DefaultRecord();
+                        dirtyData.addColumn(new StringColumn(each));
+                        this.taskPluginCollector.collectDirtyRecord(dirtyData, e.getMessage());
+                    }
+                } else {
+                    throw e;
+                }
+            }
+
+        } catch (Exception e) {
+            throw DataXException.asDataXException(DBUtilErrorCode.WRITE_DATA_ERROR, e);
+        } finally {
+            DBUtil.closeDBResources(null, null, connection);
+        }
+    }
+
+    private void prepareColumnTypeValue(Record record, List<Object> values) {
+        for (int i = 0; i < this.useRawData.length; i++) {
+            Column column = record.getColumn(i);
+            if (this.useRawData[i]) {
+                values.add(column.getRawData());
+            } else {
+                values.add(column.asString());
+            }
+        }
+    }
+
+    @Override
+    public void closeResource() {
+        try {
+            LOG.info("stop the adbClient");
+            this.adbClient.stop();
+        } catch (Exception e) {
+            LOG.warn("stop adbClient meet a exception, ignore it: {}", e.getMessage(), e);
+        }
+    }
+}

+ 635 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/AdsInsertProxy.java

@@ -0,0 +1,635 @@
+package com.alibaba.datax.plugin.writer.adswriter.insert;
+
+import com.alibaba.datax.common.element.Column;
+import com.alibaba.datax.common.element.Record;
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.plugin.RecordReceiver;
+import com.alibaba.datax.common.plugin.TaskPluginCollector;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.common.util.RetryUtil;
+import com.alibaba.datax.plugin.rdbms.util.DBUtil;
+import com.alibaba.datax.plugin.rdbms.util.DBUtilErrorCode;
+import com.alibaba.datax.plugin.writer.adswriter.ads.TableInfo;
+import com.alibaba.datax.plugin.writer.adswriter.util.AdsUtil;
+import com.alibaba.datax.plugin.writer.adswriter.util.Constant;
+import com.alibaba.datax.plugin.writer.adswriter.util.Key;
+import com.mysql.jdbc.JDBC4PreparedStatement;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+
+
+public class AdsInsertProxy implements AdsProxy {
+
+    private static final Logger LOG = LoggerFactory
+            .getLogger(AdsInsertProxy.class);
+    private static final boolean IS_DEBUG_ENABLE = LOG.isDebugEnabled();
+    private static final int MAX_EXCEPTION_CAUSE_ITER = 100;
+
+    private String table;
+    private List<String> columns;
+    private TaskPluginCollector taskPluginCollector;
+    private Configuration configuration;
+    private Boolean emptyAsNull;
+
+    private String writeMode;
+
+    private String insertSqlPrefix;
+    private String deleteSqlPrefix;
+    private int opColumnIndex;
+    private String lastDmlMode;
+    // columnName: <java sql type, ads type name>
+    private Map<String, Pair<Integer, String>> adsTableColumnsMetaData;
+    private Map<String, Pair<Integer, String>> userConfigColumnsMetaData;
+    // columnName: index @ ads column
+    private Map<String, Integer> primaryKeyNameIndexMap;
+
+    private int retryTimeUpperLimit;
+    private Connection currentConnection;
+
+    private String partitionColumn;
+    private int partitionColumnIndex = -1;
+    private int partitionCount;
+
+    public AdsInsertProxy(String table, List<String> columns, Configuration configuration, TaskPluginCollector taskPluginCollector, TableInfo tableInfo) {
+        this.table = table;
+        this.columns = columns;
+        this.configuration = configuration;
+        this.taskPluginCollector = taskPluginCollector;
+        this.emptyAsNull = configuration.getBool(Key.EMPTY_AS_NULL, false);
+        this.writeMode = configuration.getString(Key.WRITE_MODE);
+        this.insertSqlPrefix = String.format(Constant.INSERT_TEMPLATE, this.table, StringUtils.join(columns, ","));
+        this.deleteSqlPrefix = String.format(Constant.DELETE_TEMPLATE, this.table);
+        this.opColumnIndex = configuration.getInt(Key.OPIndex, 0);
+        this.retryTimeUpperLimit = configuration.getInt(
+                Key.RETRY_CONNECTION_TIME, Constant.DEFAULT_RETRY_TIMES);
+        this.partitionCount = tableInfo.getPartitionCount();
+        this.partitionColumn = tableInfo.getPartitionColumn();
+
+        //目前ads新建的表如果未插入数据不能通过select colums from table where 1=2,获取列信息,需要读取ads数据字典
+        //not this: this.resultSetMetaData = DBUtil.getColumnMetaData(connection, this.table, StringUtils.join(this.columns, ","));
+        //no retry here(fetch meta data) 注意实时表列换序的可能
+        this.adsTableColumnsMetaData = AdsInsertUtil.getColumnMetaData(tableInfo, this.columns);
+        this.userConfigColumnsMetaData = new HashMap<String, Pair<Integer, String>>();
+
+        List<String> primaryKeyColumnName = tableInfo.getPrimaryKeyColumns();
+        List<String> adsColumnsNames = tableInfo.getColumnsNames();
+        this.primaryKeyNameIndexMap = new HashMap<String, Integer>();
+        //warn: 要使用用户配置的column顺序, 不要使用从ads元数据获取的column顺序, 原来复用load列顺序其实有问题的
+        for (int i = 0; i < this.columns.size(); i++) {
+            String oriEachColumn = this.columns.get(i);
+            String eachColumn = oriEachColumn;
+            // 防御性保留字
+            if (eachColumn.startsWith(Constant.ADS_QUOTE_CHARACTER) && eachColumn.endsWith(Constant.ADS_QUOTE_CHARACTER)) {
+                eachColumn = eachColumn.substring(1, eachColumn.length() - 1);
+            }
+            for (String eachPrimary : primaryKeyColumnName) {
+                if (eachColumn.equalsIgnoreCase(eachPrimary)) {
+                    this.primaryKeyNameIndexMap.put(oriEachColumn, i);
+                }
+            }
+            for (String eachAdsColumn : adsColumnsNames) {
+                if (eachColumn.equalsIgnoreCase(eachAdsColumn)) {
+                    this.userConfigColumnsMetaData.put(oriEachColumn, this.adsTableColumnsMetaData.get(eachAdsColumn));
+                }
+            }
+
+            // 根据第几个column分区列排序,ads实时表只有一级分区、最多256个分区
+            if (eachColumn.equalsIgnoreCase(this.partitionColumn)) {
+                this.partitionColumnIndex = i;
+            }
+        }
+    }
+
+    public void startWriteWithConnection(RecordReceiver recordReceiver,
+                                         Connection connection,
+                                         int columnNumber) {
+        this.currentConnection = connection;
+        int batchSize = this.configuration.getInt(Key.BATCH_SIZE, Constant.DEFAULT_BATCH_SIZE);
+        // 默认情况下bufferSize需要和batchSize一致
+        int bufferSize = this.configuration.getInt(Key.BUFFER_SIZE, batchSize);
+        // insert缓冲,多个分区排序后insert合并发送到ads
+        List<Record> writeBuffer = new ArrayList<Record>(bufferSize);
+        List<Record> deleteBuffer = null;
+        if (this.writeMode.equalsIgnoreCase(Constant.STREAMMODE)) {
+            // delete缓冲,多个分区排序后delete合并发送到ads
+            deleteBuffer = new ArrayList<Record>(bufferSize);
+        }
+        try {
+            Record record;
+            while ((record = recordReceiver.getFromReader()) != null) {
+                if (this.writeMode.equalsIgnoreCase(Constant.INSERTMODE)) {
+                    if (record.getColumnNumber() != columnNumber) {
+                        // 源头读取字段列数与目的表字段写入列数不相等,直接报错
+                        throw DataXException
+                                .asDataXException(
+                                        DBUtilErrorCode.CONF_ERROR,
+                                        String.format(
+                                                "列配置信息有错误. 因为您配置的任务中,源头读取字段数:%s 与 目的表要写入的字段数:%s 不相等. 请检查您的配置并作出修改.",
+                                                record.getColumnNumber(),
+                                                columnNumber));
+                    }
+                    writeBuffer.add(record);
+                    if (writeBuffer.size() >= bufferSize) {
+                        this.doBatchRecordWithPartitionSort(writeBuffer, Constant.INSERTMODE, bufferSize, batchSize);
+                        writeBuffer.clear();
+                    }
+                } else {
+                    if (record.getColumnNumber() != columnNumber + 1) {
+                        // 源头读取字段列数需要为目的表字段写入列数+1, 直接报错, 源头多了一列OP
+                        throw DataXException
+                                .asDataXException(
+                                        DBUtilErrorCode.CONF_ERROR,
+                                        String.format(
+                                                "列配置信息有错误. 因为您配置的任务中,源头读取字段数:%s 与 目的表要写入的字段数:%s 不满足源头多1列操作类型列. 请检查您的配置并作出修改.",
+                                                record.getColumnNumber(),
+                                                columnNumber));
+                    }
+                    String optionColumnValue = record.getColumn(this.opColumnIndex).asString();
+                    OperationType operationType = OperationType.asOperationType(optionColumnValue);
+                    if (operationType.isInsertTemplate()) {
+                        writeBuffer.add(record);
+                        if (this.lastDmlMode == null || this.lastDmlMode == Constant.INSERTMODE) {
+                            this.lastDmlMode = Constant.INSERTMODE;
+                            if (writeBuffer.size() >= bufferSize) {
+                                this.doBatchRecordWithPartitionSort(writeBuffer, Constant.INSERTMODE, bufferSize, batchSize);
+                                writeBuffer.clear();
+                            }
+                        } else {
+                            this.lastDmlMode = Constant.INSERTMODE;
+                            // 模式变换触发一次提交ads delete, 并进入insert模式
+                            this.doBatchRecordWithPartitionSort(deleteBuffer, Constant.DELETEMODE, bufferSize, batchSize);
+                            deleteBuffer.clear();
+                        }
+                    } else if (operationType.isDeleteTemplate()) {
+                        deleteBuffer.add(record);
+                        if (this.lastDmlMode == null || this.lastDmlMode == Constant.DELETEMODE) {
+                            this.lastDmlMode = Constant.DELETEMODE;
+                            if (deleteBuffer.size() >= bufferSize) {
+                                this.doBatchRecordWithPartitionSort(deleteBuffer, Constant.DELETEMODE, bufferSize, batchSize);
+                                deleteBuffer.clear();
+                            }
+                        } else {
+                            this.lastDmlMode = Constant.DELETEMODE;
+                            // 模式变换触发一次提交ads insert, 并进入delete模式
+                            this.doBatchRecordWithPartitionSort(writeBuffer, Constant.INSERTMODE, bufferSize, batchSize);
+                            writeBuffer.clear();
+                        }
+                    } else {
+                        // 注意OP操作类型的脏数据, 这里不需要重试
+                        this.taskPluginCollector.collectDirtyRecord(record, String.format("不支持您的更新类型:%s", optionColumnValue));
+                    }
+                }
+            }
+
+            if (!writeBuffer.isEmpty()) {
+                //doOneRecord(writeBuffer, Constant.INSERTMODE);
+                this.doBatchRecordWithPartitionSort(writeBuffer, Constant.INSERTMODE, bufferSize, batchSize);
+                writeBuffer.clear();
+            }
+            // 2个缓冲最多一个不为空同时
+            if (null != deleteBuffer && !deleteBuffer.isEmpty()) {
+                //doOneRecord(deleteBuffer, Constant.DELETEMODE);
+                this.doBatchRecordWithPartitionSort(deleteBuffer, Constant.DELETEMODE, bufferSize, batchSize);
+                deleteBuffer.clear();
+            }
+        } catch (Exception e) {
+            throw DataXException.asDataXException(
+                    DBUtilErrorCode.WRITE_DATA_ERROR, e);
+        } finally {
+            writeBuffer.clear();
+            DBUtil.closeDBResources(null, null, connection);
+        }
+    }
+
+    /**
+     * @param bufferSize datax缓冲记录条数
+     * @param batchSize  datax向ads系统一次发送数据条数
+     * @param buffer     datax缓冲区
+     * @param mode       实时表模式insert 或者 stream
+     */
+    private void doBatchRecordWithPartitionSort(List<Record> buffer, String mode, int bufferSize, int batchSize) throws SQLException {
+        //warn: 排序会影响数据插入顺序, 如果源头没有数据约束, 排序可能造成数据不一致, 快速排序是一种不稳定的排序算法
+        //warn: 不明确配置bufferSize或者小于batchSize的情况下,不要进行排序;如果缓冲区实际内容条数少于batchSize也不排序了,最后一次的余量
+        int recordBufferedNumber = buffer.size();
+        if (bufferSize > batchSize && recordBufferedNumber > batchSize && this.partitionColumnIndex >= 0) {
+            final int partitionColumnIndex = this.partitionColumnIndex;
+            final int partitionCount = this.partitionCount;
+            Collections.sort(buffer, new Comparator<Record>() {
+                @Override
+                public int compare(Record record1, Record record2) {
+                    int hashPartition1 = AdsInsertProxy.getHashPartition(record1.getColumn(partitionColumnIndex).asString(), partitionCount);
+                    int hashPartition2 = AdsInsertProxy.getHashPartition(record2.getColumn(partitionColumnIndex).asString(), partitionCount);
+                    return hashPartition1 - hashPartition2;
+                }
+            });
+        }
+        // 将缓冲区的Record输出到ads, 使用recordBufferedNumber哦
+        for (int i = 0; i < recordBufferedNumber; i += batchSize) {
+            int toIndex = i + batchSize;
+            if (toIndex > recordBufferedNumber) {
+                toIndex = recordBufferedNumber;
+            }
+            this.doBatchRecord(buffer.subList(i, toIndex), mode);
+        }
+    }
+
+    private void doBatchRecord(final List<Record> buffer, final String mode) throws SQLException {
+        List<Class<?>> retryExceptionClasss = new ArrayList<Class<?>>();
+        retryExceptionClasss.add(com.mysql.jdbc.exceptions.jdbc4.CommunicationsException.class);
+        retryExceptionClasss.add(java.net.SocketException.class);
+        try {
+            RetryUtil.executeWithRetry(new Callable<Boolean>() {
+                @Override
+                public Boolean call() throws Exception {
+                    doBatchRecordDml(buffer, mode);
+                    return true;
+                }
+            }, this.retryTimeUpperLimit, 2000L, true, retryExceptionClasss);
+        } catch (SQLException e) {
+            LOG.warn(String.format("after retry %s times, doBatchRecord meet a exception: ", this.retryTimeUpperLimit), e);
+            LOG.info("try to re execute for each record...");
+            doOneRecord(buffer, mode);
+            // below is the old way
+            // for (Record eachRecord : buffer) {
+            // this.taskPluginCollector.collectDirtyRecord(eachRecord, e);
+            // }
+        } catch (Exception e) {
+            throw DataXException.asDataXException(
+                    DBUtilErrorCode.WRITE_DATA_ERROR, e);
+        }
+    }
+
+    //warn: ADS 无法支持事物roll back都是不管用
+    @SuppressWarnings("resource")
+    private void doBatchRecordDml(List<Record> buffer, String mode) throws Exception {
+        Statement statement = null;
+        String sql = null;
+        try {
+            int bufferSize = buffer.size();
+            if (buffer.isEmpty()) {
+                return;
+            }
+            StringBuilder sqlSb = new StringBuilder();
+            // connection.setAutoCommit(true);
+            //mysql impl warn: if a database access error occurs or this method is called on a closed connection throw SQLException
+            statement = this.currentConnection.createStatement();
+            sqlSb.append(this.generateDmlSql(this.currentConnection, buffer.get(0), mode));
+            for (int i = 1; i < bufferSize; i++) {
+                Record record = buffer.get(i);
+                this.appendDmlSqlValues(this.currentConnection, record, sqlSb, mode);
+            }
+            sql = sqlSb.toString();
+            if (IS_DEBUG_ENABLE) {
+                LOG.debug(sql);
+            }
+            @SuppressWarnings("unused")
+            int status = statement.executeUpdate(sql);
+            sql = null;
+        } catch (SQLException e) {
+            LOG.warn("doBatchRecordDml meet a exception: " + sql, e);
+            Exception eachException = e;
+            int maxIter = 0;// 避免死循环
+            while (null != eachException && maxIter < AdsInsertProxy.MAX_EXCEPTION_CAUSE_ITER) {
+                if (this.isRetryable(eachException)) {
+                    LOG.warn("doBatchRecordDml meet a retry exception: " + e.getMessage());
+                    this.currentConnection = AdsUtil.getAdsConnect(this.configuration);
+                    throw eachException;
+                } else {
+                    try {
+                        Throwable causeThrowable = eachException.getCause();
+                        eachException = causeThrowable == null ? null : (Exception) causeThrowable;
+                    } catch (Exception castException) {
+                        LOG.warn("doBatchRecordDml meet a no! retry exception: " + e.getMessage());
+                        throw e;
+                    }
+                }
+                maxIter++;
+            }
+            throw e;
+        } catch (Exception e) {
+            LOG.error("插入异常, sql: " + sql);
+            throw DataXException.asDataXException(
+                    DBUtilErrorCode.WRITE_DATA_ERROR, e);
+        } finally {
+            DBUtil.closeDBResources(statement, null);
+        }
+    }
+
+    private void doOneRecord(List<Record> buffer, final String mode) {
+        List<Class<?>> retryExceptionClasss = new ArrayList<Class<?>>();
+        retryExceptionClasss.add(com.mysql.jdbc.exceptions.jdbc4.CommunicationsException.class);
+        retryExceptionClasss.add(java.net.SocketException.class);
+        for (final Record record : buffer) {
+            try {
+                RetryUtil.executeWithRetry(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        doOneRecordDml(record, mode);
+                        return true;
+                    }
+                }, this.retryTimeUpperLimit, 2000L, true, retryExceptionClasss);
+            } catch (Exception e) {
+                // 不能重试的一行,记录脏数据
+                this.taskPluginCollector.collectDirtyRecord(record, e);
+            }
+        }
+    }
+
+    @SuppressWarnings("resource")
+    private void doOneRecordDml(Record record, String mode) throws Exception {
+        Statement statement = null;
+        String sql = null;
+        try {
+            // connection.setAutoCommit(true);
+            statement = this.currentConnection.createStatement();
+            sql = generateDmlSql(this.currentConnection, record, mode);
+            if (IS_DEBUG_ENABLE) {
+                LOG.debug(sql);
+            }
+            @SuppressWarnings("unused")
+            int status = statement.executeUpdate(sql);
+            sql = null;
+        } catch (SQLException e) {
+            LOG.error("doOneDml meet a exception: " + sql, e);
+            //need retry before record dirty data
+            //this.taskPluginCollector.collectDirtyRecord(record, e);
+            // 更新当前可用连接
+            Exception eachException = e;
+            int maxIter = 0;// 避免死循环
+            while (null != eachException && maxIter < AdsInsertProxy.MAX_EXCEPTION_CAUSE_ITER) {
+                if (this.isRetryable(eachException)) {
+                    LOG.warn("doOneDml meet a retry exception: " + e.getMessage());
+                    this.currentConnection = AdsUtil.getAdsConnect(this.configuration);
+                    throw eachException;
+                } else {
+                    try {
+                        Throwable causeThrowable = eachException.getCause();
+                        eachException = causeThrowable == null ? null : (Exception) causeThrowable;
+                    } catch (Exception castException) {
+                        LOG.warn("doOneDml meet a no! retry exception: " + e.getMessage());
+                        throw e;
+                    }
+                }
+                maxIter++;
+            }
+            throw e;
+        } catch (Exception e) {
+            LOG.error("插入异常, sql: " + sql);
+            throw DataXException.asDataXException(
+                    DBUtilErrorCode.WRITE_DATA_ERROR, e);
+        } finally {
+            DBUtil.closeDBResources(statement, null);
+        }
+    }
+
+    private boolean isRetryable(Throwable e) {
+        Class<?> meetExceptionClass = e.getClass();
+        if (meetExceptionClass == com.mysql.jdbc.exceptions.jdbc4.CommunicationsException.class) {
+            return true;
+        }
+        if (meetExceptionClass == java.net.SocketException.class) {
+            return true;
+        }
+        return false;
+    }
+
+    private String generateDmlSql(Connection connection, Record record, String mode) throws SQLException {
+        String sql = null;
+        StringBuilder sqlSb = new StringBuilder();
+        if (mode.equalsIgnoreCase(Constant.INSERTMODE)) {
+            sqlSb.append(this.insertSqlPrefix);
+            sqlSb.append("(");
+            int columnsSize = this.columns.size();
+            for (int i = 0; i < columnsSize; i++) {
+                if ((i + 1) != columnsSize) {
+                    sqlSb.append("?,");
+                } else {
+                    sqlSb.append("?");
+                }
+            }
+            sqlSb.append(")");
+            //mysql impl warn: if a database access error occurs or this method is called on a closed connection
+            PreparedStatement statement = connection.prepareStatement(sqlSb.toString());
+            for (int i = 0; i < this.columns.size(); i++) {
+                int preparedParamsIndex = i;
+                if (Constant.STREAMMODE.equalsIgnoreCase(this.writeMode)) {
+                    if (preparedParamsIndex >= this.opColumnIndex) {
+                        preparedParamsIndex = i + 1;
+                    }
+                }
+                String columnName = this.columns.get(i);
+                int columnSqltype = this.userConfigColumnsMetaData.get(columnName).getLeft();
+                prepareColumnTypeValue(statement, columnSqltype, record.getColumn(preparedParamsIndex), i, columnName);
+            }
+            sql = ((JDBC4PreparedStatement) statement).asSql();
+            DBUtil.closeDBResources(statement, null);
+        } else {
+            sqlSb.append(this.deleteSqlPrefix);
+            sqlSb.append("(");
+            Set<Entry<String, Integer>> primaryEntrySet = this.primaryKeyNameIndexMap.entrySet();
+            int entrySetSize = primaryEntrySet.size();
+            int i = 0;
+            for (Entry<String, Integer> eachEntry : primaryEntrySet) {
+                if ((i + 1) != entrySetSize) {
+                    sqlSb.append(String.format(" (%s = ?) and ", eachEntry.getKey()));
+                } else {
+                    sqlSb.append(String.format(" (%s = ?) ", eachEntry.getKey()));
+                }
+                i++;
+            }
+            sqlSb.append(")");
+            //mysql impl warn: if a database access error occurs or this method is called on a closed connection
+            PreparedStatement statement = connection.prepareStatement(sqlSb.toString());
+            i = 0;
+            //ads的real time表只能是1级分区、且分区列类型是long, 但是这里是需要主键删除的
+            for (Entry<String, Integer> each : primaryEntrySet) {
+                String columnName = each.getKey();
+                int columnSqlType = this.userConfigColumnsMetaData.get(columnName).getLeft();
+                int primaryKeyInUserConfigIndex = this.primaryKeyNameIndexMap.get(columnName);
+                if (primaryKeyInUserConfigIndex >= this.opColumnIndex) {
+                    primaryKeyInUserConfigIndex++;
+                }
+                prepareColumnTypeValue(statement, columnSqlType, record.getColumn(primaryKeyInUserConfigIndex), i, columnName);
+                i++;
+            }
+            sql = ((JDBC4PreparedStatement) statement).asSql();
+            DBUtil.closeDBResources(statement, null);
+        }
+        return sql;
+    }
+
+    private void appendDmlSqlValues(Connection connection, Record record, StringBuilder sqlSb, String mode) throws SQLException {
+        String sqlResult = this.generateDmlSql(connection, record, mode);
+        if (mode.equalsIgnoreCase(Constant.INSERTMODE)) {
+            sqlSb.append(",");
+            sqlSb.append(sqlResult.substring(this.insertSqlPrefix.length()));
+        } else {
+            // 之前已经充分增加过括号了
+            sqlSb.append(" or ");
+            sqlSb.append(sqlResult.substring(this.deleteSqlPrefix.length()));
+        }
+    }
+
+    private void prepareColumnTypeValue(PreparedStatement statement, int columnSqltype, Column column, int preparedPatamIndex, String columnName) throws SQLException {
+        java.util.Date utilDate;
+        switch (columnSqltype) {
+            case Types.CHAR:
+            case Types.NCHAR:
+            case Types.CLOB:
+            case Types.NCLOB:
+            case Types.VARCHAR:
+            case Types.LONGVARCHAR:
+            case Types.NVARCHAR:
+            case Types.LONGNVARCHAR:
+                String strValue = column.asString();
+                statement.setString(preparedPatamIndex + 1, strValue);
+                break;
+
+            case Types.SMALLINT:
+            case Types.INTEGER:
+            case Types.BIGINT:
+            case Types.NUMERIC:
+            case Types.DECIMAL:
+            case Types.REAL:
+                String numValue = column.asString();
+                if (emptyAsNull && "".equals(numValue) || numValue == null) {
+                    //statement.setObject(preparedPatamIndex + 1,  null);
+                    statement.setNull(preparedPatamIndex + 1, Types.BIGINT);
+                } else {
+                    statement.setLong(preparedPatamIndex + 1, column.asLong());
+                }
+                break;
+
+            case Types.FLOAT:
+            case Types.DOUBLE:
+                String floatValue = column.asString();
+                if (emptyAsNull && "".equals(floatValue) || floatValue == null) {
+                    //statement.setObject(preparedPatamIndex + 1,  null);
+                    statement.setNull(preparedPatamIndex + 1, Types.DOUBLE);
+                } else {
+                    statement.setDouble(preparedPatamIndex + 1, column.asDouble());
+                }
+                break;
+
+            //tinyint is a little special in some database like mysql {boolean->tinyint(1)}
+            case Types.TINYINT:
+                Long longValue = column.asLong();
+                if (null == longValue) {
+                    statement.setNull(preparedPatamIndex + 1, Types.BIGINT);
+                } else {
+                    statement.setLong(preparedPatamIndex + 1, longValue);
+                }
+
+                break;
+
+            case Types.DATE:
+                java.sql.Date sqlDate = null;
+                try {
+                    if ("".equals(column.getRawData())) {
+                        utilDate = null;
+                    } else {
+                        utilDate = column.asDate();
+                    }
+                } catch (DataXException e) {
+                    throw new SQLException(String.format(
+                            "Date 类型转换错误:[%s]", column));
+                }
+
+                if (null != utilDate) {
+                    sqlDate = new java.sql.Date(utilDate.getTime());
+                }
+                statement.setDate(preparedPatamIndex + 1, sqlDate);
+                break;
+
+            case Types.TIME:
+                java.sql.Time sqlTime = null;
+                try {
+                    if ("".equals(column.getRawData())) {
+                        utilDate = null;
+                    } else {
+                        utilDate = column.asDate();
+                    }
+                } catch (DataXException e) {
+                    throw new SQLException(String.format(
+                            "TIME 类型转换错误:[%s]", column));
+                }
+
+                if (null != utilDate) {
+                    sqlTime = new java.sql.Time(utilDate.getTime());
+                }
+                statement.setTime(preparedPatamIndex + 1, sqlTime);
+                break;
+
+            case Types.TIMESTAMP:
+                java.sql.Timestamp sqlTimestamp = null;
+                try {
+                    if ("".equals(column.getRawData())) {
+                        utilDate = null;
+                    } else {
+                        utilDate = column.asDate();
+                    }
+                } catch (DataXException e) {
+                    throw new SQLException(String.format(
+                            "TIMESTAMP 类型转换错误:[%s]", column));
+                }
+
+                if (null != utilDate) {
+                    sqlTimestamp = new java.sql.Timestamp(
+                            utilDate.getTime());
+                }
+                statement.setTimestamp(preparedPatamIndex + 1, sqlTimestamp);
+                break;
+
+            case Types.BOOLEAN:
+                //case Types.BIT: ads 没有bit
+                Boolean booleanValue = column.asBoolean();
+                if (null == booleanValue) {
+                    statement.setNull(preparedPatamIndex + 1, Types.BOOLEAN);
+                } else {
+                    statement.setBoolean(preparedPatamIndex + 1, booleanValue);
+                }
+
+                break;
+            default:
+                Pair<Integer, String> columnMetaPair = this.userConfigColumnsMetaData.get(columnName);
+                throw DataXException
+                        .asDataXException(
+                                DBUtilErrorCode.UNSUPPORTED_TYPE,
+                                String.format(
+                                        "您的配置文件中的列配置信息有误. 因为DataX 不支持数据库写入这种字段类型. 字段名:[%s], 字段类型:[%s], 字段Java类型:[%s]. 请修改表中该字段的类型或者不同步该字段.",
+                                        columnName, columnMetaPair.getRight(), columnMetaPair.getLeft()));
+        }
+    }
+
+    private static int getHashPartition(String value, int totalHashPartitionNum) {
+        long crc32 = (value == null ? getCRC32("-1") : getCRC32(value));
+        return (int) (crc32 % totalHashPartitionNum);
+    }
+
+    private static long getCRC32(String value) {
+        Checksum checksum = new CRC32();
+        byte[] bytes = value.getBytes();
+        checksum.update(bytes, 0, bytes.length);
+        return checksum.getValue();
+    }
+
+    @Override
+    public void closeResource() {
+    }
+}

+ 153 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/AdsInsertUtil.java

@@ -0,0 +1,153 @@
+package com.alibaba.datax.plugin.writer.adswriter.insert;
+
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.common.util.ListUtil;
+import com.alibaba.datax.plugin.rdbms.util.DBUtilErrorCode;
+import com.alibaba.datax.plugin.writer.adswriter.AdsException;
+import com.alibaba.datax.plugin.writer.adswriter.AdsWriterErrorCode;
+import com.alibaba.datax.plugin.writer.adswriter.ads.ColumnInfo;
+import com.alibaba.datax.plugin.writer.adswriter.ads.TableInfo;
+import com.alibaba.datax.plugin.writer.adswriter.load.AdsHelper;
+import com.alibaba.datax.plugin.writer.adswriter.util.AdsUtil;
+import com.alibaba.datax.plugin.writer.adswriter.util.Constant;
+import com.alibaba.datax.plugin.writer.adswriter.util.Key;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+public class AdsInsertUtil {
+
+    private static final Logger LOG = LoggerFactory
+            .getLogger(AdsInsertUtil.class);
+
+    public static TableInfo getAdsTableInfo(Configuration conf) {
+        AdsHelper adsHelper = AdsUtil.createAdsHelper(conf);
+        TableInfo tableInfo= null;
+        try {
+            tableInfo = adsHelper.getTableInfo(conf.getString(Key.ADS_TABLE));
+        } catch (AdsException e) {
+            throw DataXException.asDataXException(AdsWriterErrorCode.GET_ADS_TABLE_MEATA_FAILED, e);
+        }
+        return tableInfo;
+    }
+
+    /*
+     * 返回列顺序为ads建表列顺序
+     * */
+    public static List<String> getAdsTableColumnNames(Configuration conf) {
+        List<String> tableColumns = new ArrayList<String>();
+        AdsHelper adsHelper = AdsUtil.createAdsHelper(conf);
+        TableInfo tableInfo= null;
+        String adsTable = conf.getString(Key.ADS_TABLE);
+        try {
+            tableInfo = adsHelper.getTableInfo(adsTable);
+        } catch (AdsException e) {
+            throw DataXException.asDataXException(AdsWriterErrorCode.GET_ADS_TABLE_MEATA_FAILED, e);
+        }
+
+        List<ColumnInfo> columnInfos = tableInfo.getColumns();
+        for(ColumnInfo columnInfo: columnInfos) {
+            tableColumns.add(columnInfo.getName());
+        }
+
+        LOG.info("table:[{}] all columns:[\n{}\n].", adsTable, StringUtils.join(tableColumns, ","));
+        return tableColumns;
+    }
+
+    public static Map<String, Pair<Integer,String>> getColumnMetaData
+            (Configuration configuration, List<String> userColumns) {
+        Map<String, Pair<Integer,String>> columnMetaData = new HashMap<String, Pair<Integer,String>>();
+        List<ColumnInfo> columnInfoList = getAdsTableColumns(configuration);
+        for(String column : userColumns) {
+            if (column.startsWith(Constant.ADS_QUOTE_CHARACTER) && column.endsWith(Constant.ADS_QUOTE_CHARACTER)) {
+                column = column.substring(1, column.length() - 1);
+            }
+            for (ColumnInfo columnInfo : columnInfoList) {
+                if(column.equalsIgnoreCase(columnInfo.getName())) {
+                    Pair<Integer,String> eachPair = new ImmutablePair<Integer, String>(columnInfo.getDataType().sqlType, columnInfo.getDataType().name);
+                    columnMetaData.put(columnInfo.getName(), eachPair);
+                }
+            }
+        }
+        return columnMetaData;
+    }
+    
+    public static Map<String, Pair<Integer,String>> getColumnMetaData(TableInfo tableInfo, List<String> userColumns){
+        Map<String, Pair<Integer,String>> columnMetaData = new HashMap<String, Pair<Integer,String>>();
+        List<ColumnInfo> columnInfoList = tableInfo.getColumns();
+        for(String column : userColumns) {
+            if (column.startsWith(Constant.ADS_QUOTE_CHARACTER) && column.endsWith(Constant.ADS_QUOTE_CHARACTER)) {
+                column = column.substring(1, column.length() - 1);
+            }
+            for (ColumnInfo columnInfo : columnInfoList) {
+                if(column.equalsIgnoreCase(columnInfo.getName())) {
+                    Pair<Integer,String> eachPair = new ImmutablePair<Integer, String>(columnInfo.getDataType().sqlType, columnInfo.getDataType().name);
+                    columnMetaData.put(columnInfo.getName(), eachPair);
+                }
+            }
+        }
+        return columnMetaData;
+    }
+
+    /*
+     * 返回列顺序为ads建表列顺序
+     * */
+    public static List<ColumnInfo> getAdsTableColumns(Configuration conf) {
+        AdsHelper adsHelper = AdsUtil.createAdsHelper(conf);
+        TableInfo tableInfo= null;
+        String adsTable = conf.getString(Key.ADS_TABLE);
+        try {
+            tableInfo = adsHelper.getTableInfo(adsTable);
+        } catch (AdsException e) {
+            throw DataXException.asDataXException(AdsWriterErrorCode.GET_ADS_TABLE_MEATA_FAILED, e);
+        }
+
+        List<ColumnInfo> columnInfos = tableInfo.getColumns();
+
+        return columnInfos;
+    }
+
+    public static void dealColumnConf(Configuration originalConfig, List<String> tableColumns) {
+        List<String> userConfiguredColumns = originalConfig.getList(Key.COLUMN, String.class);
+        if (null == userConfiguredColumns || userConfiguredColumns.isEmpty()) {
+            throw DataXException.asDataXException(DBUtilErrorCode.ILLEGAL_VALUE,
+                    "您的配置文件中的列配置信息有误. 因为您未配置写入数据库表的列名称,DataX获取不到列信息. 请检查您的配置并作出修改.");
+        } else {
+            if (1 == userConfiguredColumns.size() && "*".equals(userConfiguredColumns.get(0))) {
+                LOG.warn("您的配置文件中的列配置信息存在风险. 因为您配置的写入数据库表的列为*,当您的表字段个数、类型有变动时,可能影响任务正确性甚至会运行出错。请检查您的配置并作出修改.");
+
+                // 回填其值,需要以 String 的方式转交后续处理
+                originalConfig.set(Key.COLUMN, tableColumns);
+            } else if (userConfiguredColumns.size() > tableColumns.size()) {
+                throw DataXException.asDataXException(DBUtilErrorCode.ILLEGAL_VALUE,
+                        String.format("您的配置文件中的列配置信息有误. 因为您所配置的写入数据库表的字段个数:%s 大于目的表的总字段总个数:%s. 请检查您的配置并作出修改.",
+                                userConfiguredColumns.size(), tableColumns.size()));
+            } else {
+                // 确保用户配置的 column 不重复
+                ListUtil.makeSureNoValueDuplicate(userConfiguredColumns, false);
+                // 检查列是否都为数据库表中正确的列(通过执行一次 select column from table 进行判断)
+                // ListUtil.makeSureBInA(tableColumns, userConfiguredColumns, true);
+                // 支持关键字和保留字, ads列是不区分大小写的
+                List<String> removeQuotedColumns = new ArrayList<String>();
+                for (String each : userConfiguredColumns) {
+                    if (each.startsWith(Constant.ADS_QUOTE_CHARACTER) && each.endsWith(Constant.ADS_QUOTE_CHARACTER)) {
+                        removeQuotedColumns.add(each.substring(1, each.length() - 1));
+                    } else {
+                        removeQuotedColumns.add(each);
+                    }
+                }
+                ListUtil.makeSureBInA(tableColumns, removeQuotedColumns, false);
+            }
+        }
+    }
+}

+ 12 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/AdsProxy.java

@@ -0,0 +1,12 @@
+package com.alibaba.datax.plugin.writer.adswriter.insert;
+
+import com.alibaba.datax.common.plugin.RecordReceiver;
+
+import java.sql.Connection;
+
+public interface AdsProxy {
+    public abstract void startWriteWithConnection(RecordReceiver recordReceiver, Connection connection,
+                                                  int columnNumber);
+
+    public void closeResource();
+}

+ 75 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/insert/OperationType.java

@@ -0,0 +1,75 @@
+package com.alibaba.datax.plugin.writer.adswriter.insert;
+
+public enum OperationType {
+    // i: insert uo:before image uu:before image un: after image d: delete
+    // u:update
+    I("i"), UO("uo"), UU("uu"), UN("un"), D("d"), U("u"), UNKNOWN("unknown"), ;
+    private OperationType(String type) {
+        this.type = type;
+    }
+
+    private String type;
+
+    public String getType() {
+        return this.type;
+    }
+
+    public static OperationType asOperationType(String type) {
+        if ("i".equalsIgnoreCase(type)) {
+            return I;
+        } else if ("uo".equalsIgnoreCase(type)) {
+            return UO;
+        } else if ("uu".equalsIgnoreCase(type)) {
+            return UU;
+        } else if ("un".equalsIgnoreCase(type)) {
+            return UN;
+        } else if ("d".equalsIgnoreCase(type)) {
+            return D;
+        } else if ("u".equalsIgnoreCase(type)) {
+            return U;
+        } else {
+            return UNKNOWN;
+        }
+    }
+
+    public boolean isInsertTemplate() {
+        switch (this) {
+        // 建议merge 过后应该只有I和U两类
+        case I:
+        case UO:
+        case UU:
+        case UN:
+        case U:
+            return true;
+        case D:
+            return false;
+        default:
+            return false;
+        }
+    }
+
+    public boolean isDeleteTemplate() {
+        switch (this) {
+        // 建议merge 过后应该只有I和U两类
+        case I:
+        case UO:
+        case UU:
+        case UN:
+        case U:
+            return false;
+        case D:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    public boolean isLegal() {
+        return this.type != UNKNOWN.getType();
+    }
+
+    @Override
+    public String toString() {
+        return this.name();
+    }
+}

+ 429 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/load/AdsHelper.java

@@ -0,0 +1,429 @@
+/**
+ * 
+ */
+package com.alibaba.datax.plugin.writer.adswriter.load;
+
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.util.RetryUtil;
+import com.alibaba.datax.plugin.rdbms.util.DBUtil;
+import com.alibaba.datax.plugin.writer.adswriter.AdsException;
+import com.alibaba.datax.plugin.writer.adswriter.AdsWriterErrorCode;
+import com.alibaba.datax.plugin.writer.adswriter.ads.ColumnDataType;
+import com.alibaba.datax.plugin.writer.adswriter.ads.ColumnInfo;
+import com.alibaba.datax.plugin.writer.adswriter.ads.TableInfo;
+import com.alibaba.datax.plugin.writer.adswriter.util.AdsUtil;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+
+public class AdsHelper {
+    private static final Logger LOG = LoggerFactory
+            .getLogger(AdsHelper.class);
+
+    private String adsURL;
+    private String userName;
+    private String password;
+    private String schema;
+    private Long socketTimeout;
+    private String suffix;
+
+    public AdsHelper(String adsUrl, String userName, String password, String schema, Long socketTimeout, String suffix) {
+        this.adsURL = adsUrl;
+        this.userName = userName;
+        this.password = password;
+        this.schema = schema;
+        this.socketTimeout = socketTimeout;
+        this.suffix = suffix;
+    }
+
+    public String getAdsURL() {
+        return adsURL;
+    }
+
+    public void setAdsURL(String adsURL) {
+        this.adsURL = adsURL;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getSchema() {
+        return schema;
+    }
+
+    public void setSchema(String schema) {
+        this.schema = schema;
+    }
+
+    /**
+     * Obtain the table meta information.
+     * 
+     * @param table The table
+     * @return The table meta information
+     * @throws com.alibaba.datax.plugin.writer.adswriter.AdsException
+     */
+    public TableInfo getTableInfo(String table) throws AdsException {
+
+        if (table == null) {
+            throw new AdsException(AdsException.ADS_TABLEMETA_TABLE_NULL, "Table is null.", null);
+        }
+
+        if (adsURL == null) {
+            throw new AdsException(AdsException.ADS_CONN_URL_NOT_SET, "ADS JDBC connection URL was not set.", null);
+        }
+
+        if (userName == null) {
+            throw new AdsException(AdsException.ADS_CONN_USERNAME_NOT_SET,
+                    "ADS JDBC connection user name was not set.", null);
+        }
+
+        if (password == null) {
+            throw new AdsException(AdsException.ADS_CONN_PASSWORD_NOT_SET, "ADS JDBC connection password was not set.",
+                    null);
+        }
+
+        if (schema == null) {
+            throw new AdsException(AdsException.ADS_CONN_SCHEMA_NOT_SET, "ADS JDBC connection schema was not set.",
+                    null);
+        }
+
+        Connection connection = null;
+        Statement statement = null;
+        ResultSet rs = null;
+        try {
+            Class.forName("com.mysql.jdbc.Driver");
+            String url = AdsUtil.prepareJdbcUrl(this.adsURL, this.schema, this.socketTimeout, this.suffix);
+
+            Properties connectionProps = new Properties();
+            connectionProps.put("user", userName);
+            connectionProps.put("password", password);
+            connection = DriverManager.getConnection(url, connectionProps);
+            statement = connection.createStatement();
+            // ads 表名、schema名不区分大小写, 提高用户易用性, 注意列顺序性
+            String columnMetaSql = String.format("select ordinal_position,column_name,data_type,type_name,column_comment from information_schema.columns where table_schema = `'%s'` and table_name = `'%s'` order by ordinal_position", schema.toLowerCase(), table.toLowerCase());
+            LOG.info(String.format("检查列信息sql语句:%s", columnMetaSql));
+            rs = statement.executeQuery(columnMetaSql);
+
+            TableInfo tableInfo = new TableInfo();
+            List<ColumnInfo> columnInfoList = new ArrayList<ColumnInfo>();
+            while (DBUtil.asyncResultSetNext(rs)) {
+                ColumnInfo columnInfo = new ColumnInfo();
+                columnInfo.setOrdinal(rs.getInt(1));
+                columnInfo.setName(rs.getString(2));
+                //columnInfo.setDataType(ColumnDataType.getDataType(rs.getInt(3))); //for ads version < 0.7
+                //columnInfo.setDataType(ColumnDataType.getTypeByName(rs.getString(3).toUpperCase())); //for ads version 0.8
+                columnInfo.setDataType(ColumnDataType.getTypeByName(rs.getString(4).toUpperCase())); //for ads version 0.8 & 0.7
+                columnInfo.setComment(rs.getString(5));
+                columnInfoList.add(columnInfo);
+            }
+            if (columnInfoList.isEmpty()) {
+                throw DataXException.asDataXException(AdsWriterErrorCode.NO_ADS_TABLE, table + "不存在或者查询不到列信息. ");
+            }
+            tableInfo.setColumns(columnInfoList);
+            tableInfo.setTableSchema(schema);
+            tableInfo.setTableName(table);
+            DBUtil.closeDBResources(rs, statement, null);
+            
+            String tableMetaSql = String.format("select update_type, partition_type, partition_column, partition_count, primary_key_columns from information_schema.tables where table_schema = `'%s'` and table_name = `'%s'`", schema.toLowerCase(), table.toLowerCase());
+            LOG.info(String.format("检查表信息sql语句:%s", tableMetaSql));
+            statement = connection.createStatement();
+            rs = statement.executeQuery(tableMetaSql);
+            while (DBUtil.asyncResultSetNext(rs)) {
+                tableInfo.setUpdateType(rs.getString(1));
+                tableInfo.setPartitionType(rs.getString(2));
+                tableInfo.setPartitionColumn(rs.getString(3));
+                tableInfo.setPartitionCount(rs.getInt(4));
+                //primary_key_columns  ads主键是逗号分隔的,可以有多个
+                String primaryKeyColumns = rs.getString(5);
+                if (StringUtils.isNotBlank(primaryKeyColumns)) {
+                    tableInfo.setPrimaryKeyColumns(Arrays.asList(StringUtils.split(primaryKeyColumns, ",")));
+                } else {
+                    tableInfo.setPrimaryKeyColumns(null);
+                }
+                break;
+            }
+            DBUtil.closeDBResources(rs, statement, null);
+            return tableInfo;
+
+        } catch (ClassNotFoundException e) {
+            throw new AdsException(AdsException.OTHER, e.getMessage(), e);
+        } catch (SQLException e) {
+            throw new AdsException(AdsException.OTHER, e.getMessage(), e);
+        } catch ( DataXException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new AdsException(AdsException.OTHER, e.getMessage(), e);
+        } finally {
+            if (rs != null) {
+                try {
+                    rs.close();
+                } catch (SQLException e) {
+                    // Ignore exception
+                }
+            }
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (SQLException e) {
+                    // Ignore exception
+                }
+            }
+            if (connection != null) {
+                try {
+                    connection.close();
+                } catch (SQLException e) {
+                    // Ignore exception
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Submit LOAD DATA command.
+     * 
+     * @param table The target ADS table
+     * @param partition The partition option in the form of "(partition_name,...)"
+     * @param sourcePath The source path
+     * @param overwrite
+     * @return
+     * @throws AdsException
+     */
+    public String loadData(String table, String partition, String sourcePath, boolean overwrite) throws AdsException {
+
+        if (table == null) {
+            throw new AdsException(AdsException.ADS_LOADDATA_TABLE_NULL, "ADS LOAD DATA table is null.", null);
+        }
+
+        if (sourcePath == null) {
+            throw new AdsException(AdsException.ADS_LOADDATA_SOURCEPATH_NULL, "ADS LOAD DATA source path is null.",
+                    null);
+        }
+
+        if (adsURL == null) {
+            throw new AdsException(AdsException.ADS_CONN_URL_NOT_SET, "ADS JDBC connection URL was not set.", null);
+        }
+
+        if (userName == null) {
+            throw new AdsException(AdsException.ADS_CONN_USERNAME_NOT_SET,
+                    "ADS JDBC connection user name was not set.", null);
+        }
+
+        if (password == null) {
+            throw new AdsException(AdsException.ADS_CONN_PASSWORD_NOT_SET, "ADS JDBC connection password was not set.",
+                    null);
+        }
+
+        if (schema == null) {
+            throw new AdsException(AdsException.ADS_CONN_SCHEMA_NOT_SET, "ADS JDBC connection schema was not set.",
+                    null);
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("LOAD DATA FROM ");
+        if (sourcePath.startsWith("'") && sourcePath.endsWith("'")) {
+            sb.append(sourcePath);
+        } else {
+            sb.append("'" + sourcePath + "'");
+        }
+        if (overwrite) {
+            sb.append(" OVERWRITE");
+        }
+        sb.append(" INTO TABLE ");
+        sb.append(schema + "." + table);
+        if (partition != null && !partition.trim().equals("")) {
+            String partitionTrim = partition.trim();
+            if(partitionTrim.startsWith("(") && partitionTrim.endsWith(")")) {
+                sb.append(" PARTITION " + partition);
+            } else {
+                sb.append(" PARTITION " + "(" + partition + ")");
+            }
+        }
+
+        Connection connection = null;
+        Statement statement = null;
+        ResultSet rs = null;
+        try {
+            Class.forName("com.mysql.jdbc.Driver");
+            String url = AdsUtil.prepareJdbcUrl(this.adsURL, this.schema, this.socketTimeout, this.suffix);
+            Properties connectionProps = new Properties();
+            connectionProps.put("user", userName);
+            connectionProps.put("password", password);
+            connection = DriverManager.getConnection(url, connectionProps);
+            statement = connection.createStatement();
+            LOG.info("正在从ODPS数据库导数据到ADS中: "+sb.toString());
+            LOG.info("由于ADS的限制,ADS导数据最少需要20分钟,请耐心等待");
+            rs = statement.executeQuery(sb.toString());
+
+            String jobId = null;
+            while (DBUtil.asyncResultSetNext(rs)) {
+                jobId = rs.getString(1);
+            }
+
+            if (jobId == null) {
+                throw new AdsException(AdsException.ADS_LOADDATA_JOBID_NOT_AVAIL,
+                        "Job id is not available for the submitted LOAD DATA." + jobId, null);
+            }
+
+            return jobId;
+
+        } catch (ClassNotFoundException e) {
+            throw new AdsException(AdsException.ADS_LOADDATA_FAILED, e.getMessage(), e);
+        } catch (SQLException e) {
+            throw new AdsException(AdsException.ADS_LOADDATA_FAILED, e.getMessage(), e);
+        } catch (Exception e) {
+            throw new AdsException(AdsException.ADS_LOADDATA_FAILED, e.getMessage(), e);
+        } finally {
+            if (rs != null) {
+                try {
+                    rs.close();
+                } catch (SQLException e) {
+                    // Ignore exception
+                }
+            }
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (SQLException e) {
+                    // Ignore exception
+                }
+            }
+            if (connection != null) {
+                try {
+                    connection.close();
+                } catch (SQLException e) {
+                    // Ignore exception
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Check the load data job status.
+     * 
+     * @param jobId The job id to
+     * @return true if load data job succeeded, false if load data job failed.
+     * @throws AdsException
+     */
+    public boolean checkLoadDataJobStatus(String jobId) throws AdsException {
+
+        if (adsURL == null) {
+            throw new AdsException(AdsException.ADS_CONN_URL_NOT_SET, "ADS JDBC connection URL was not set.", null);
+        }
+
+        if (userName == null) {
+            throw new AdsException(AdsException.ADS_CONN_USERNAME_NOT_SET,
+                    "ADS JDBC connection user name was not set.", null);
+        }
+
+        if (password == null) {
+            throw new AdsException(AdsException.ADS_CONN_PASSWORD_NOT_SET, "ADS JDBC connection password was not set.",
+                    null);
+        }
+
+        if (schema == null) {
+            throw new AdsException(AdsException.ADS_CONN_SCHEMA_NOT_SET, "ADS JDBC connection schema was not set.",
+                    null);
+        }
+
+        try {
+            String state = this.checkLoadDataJobStatusWithRetry(jobId);
+            if (state == null) {
+                throw new AdsException(AdsException.JOB_NOT_EXIST, "Target job does not exist for id: " + jobId, null);
+            }
+            if (state.equals("SUCCEEDED")) {
+                return true;
+            } else if (state.equals("FAILED")) {
+                throw new AdsException(AdsException.JOB_FAILED, "Target job failed for id: " + jobId, null);
+            } else {
+                return false;
+            }
+        } catch (Exception e) {
+            throw new AdsException(AdsException.OTHER, e.getMessage(), e);
+        } 
+    }
+    
+    private String checkLoadDataJobStatusWithRetry(final String jobId)
+            throws AdsException {
+        try {
+            Class.forName("com.mysql.jdbc.Driver");
+            final String finalAdsUrl = this.adsURL;
+            final String finalSchema = this.schema;
+            final Long finalSocketTimeout = this.socketTimeout;
+            final String suffix = this.suffix;
+            return RetryUtil.executeWithRetry(new Callable<String>() {
+                @Override
+                public String call() throws Exception {
+                    Connection connection = null;
+                    Statement statement = null;
+                    ResultSet rs = null;
+                    try {
+                        
+                        String url = AdsUtil.prepareJdbcUrl(finalAdsUrl, finalSchema, finalSocketTimeout, suffix);
+                        Properties connectionProps = new Properties();
+                        connectionProps.put("user", userName);
+                        connectionProps.put("password", password);
+                        connection = DriverManager.getConnection(url,
+                                connectionProps);
+                        statement = connection.createStatement();
+
+                        String sql = "select state from information_schema.job_instances where job_id like '"
+                                + jobId + "'";
+                        rs = statement.executeQuery(sql);
+                        String state = null;
+                        while (DBUtil.asyncResultSetNext(rs)) {
+                            state = rs.getString(1);
+                        }
+                        return state;
+                    } finally {
+                        if (rs != null) {
+                            try {
+                                rs.close();
+                            } catch (SQLException e) {
+                                // Ignore exception
+                            }
+                        }
+                        if (statement != null) {
+                            try {
+                                statement.close();
+                            } catch (SQLException e) {
+                                // Ignore exception
+                            }
+                        }
+                        if (connection != null) {
+                            try {
+                                connection.close();
+                            } catch (SQLException e) {
+                                // Ignore exception
+                            }
+                        }
+                    }
+                }
+            }, 3, 1000L, true);
+        } catch (Exception e) {
+            throw new AdsException(AdsException.OTHER, e.getMessage(), e);
+        }
+    }
+}

+ 87 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/load/TableMetaHelper.java

@@ -0,0 +1,87 @@
+package com.alibaba.datax.plugin.writer.adswriter.load;
+
+import com.alibaba.datax.plugin.writer.adswriter.ads.ColumnDataType;
+import com.alibaba.datax.plugin.writer.adswriter.ads.ColumnInfo;
+import com.alibaba.datax.plugin.writer.adswriter.ads.TableInfo;
+import com.alibaba.datax.plugin.writer.adswriter.odps.DataType;
+import com.alibaba.datax.plugin.writer.adswriter.odps.FieldSchema;
+import com.alibaba.datax.plugin.writer.adswriter.odps.TableMeta;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Table meta helper for ADS writer.
+ *
+ * @since 0.0.1
+ */
+public class TableMetaHelper {
+
+    private TableMetaHelper() {
+    }
+
+    /**
+     * Create temporary ODPS table.
+     * 
+     * @param tableMeta table meta
+     * @param lifeCycle for temporary table
+     * @return ODPS temporary table meta
+     */
+    public static TableMeta createTempODPSTable(TableInfo tableMeta, int lifeCycle) {
+        TableMeta tempTable = new TableMeta();
+        tempTable.setComment(tableMeta.getComments());
+        tempTable.setLifeCycle(lifeCycle);
+        String tableSchema = tableMeta.getTableSchema();
+        String tableName = tableMeta.getTableName();
+        tempTable.setTableName(generateTempTableName(tableSchema, tableName));
+        List<FieldSchema> tempColumns = new ArrayList<FieldSchema>();
+        List<ColumnInfo> columns = tableMeta.getColumns();
+        for (ColumnInfo column : columns) {
+            FieldSchema tempColumn = new FieldSchema();
+            tempColumn.setName(column.getName());
+            tempColumn.setType(toODPSDataType(column.getDataType()));
+            tempColumn.setComment(column.getComment());
+            tempColumns.add(tempColumn);
+        }
+        tempTable.setCols(tempColumns);
+        tempTable.setPartitionKeys(null);
+        return tempTable;
+    }
+
+    private static String toODPSDataType(ColumnDataType columnDataType) {
+        int type;
+        switch (columnDataType.type) {
+            case ColumnDataType.BOOLEAN:
+                type = DataType.STRING;
+                break;
+            case ColumnDataType.BYTE:
+            case ColumnDataType.SHORT:
+            case ColumnDataType.INT:
+            case ColumnDataType.LONG:
+                type = DataType.INTEGER;
+                break;
+            case ColumnDataType.DECIMAL:
+            case ColumnDataType.DOUBLE:
+            case ColumnDataType.FLOAT:
+                type = DataType.DOUBLE;
+                break;
+            case ColumnDataType.DATE:
+            case ColumnDataType.TIME:
+            case ColumnDataType.TIMESTAMP:
+            case ColumnDataType.STRING:
+            case ColumnDataType.MULTI_VALUE:
+                type = DataType.STRING;
+                break;
+            default:
+                throw new IllegalArgumentException("columnDataType=" + columnDataType);
+        }
+        return DataType.toString(type);
+    }
+
+    private static String generateTempTableName(String tableSchema, String tableName) {
+        int randNum = 1000 + new Random(System.currentTimeMillis()).nextInt(1000);
+        return tableSchema + "__" + tableName + "_" + System.currentTimeMillis() + randNum;
+    }
+
+}

+ 59 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/load/TransferProjectConf.java

@@ -0,0 +1,59 @@
+package com.alibaba.datax.plugin.writer.adswriter.load;
+
+import com.alibaba.datax.common.util.Configuration;
+
+/**
+ * Created by xiafei.qiuxf on 15/4/13.
+ */
+public class TransferProjectConf {
+
+    public final static String KEY_ACCESS_ID = "odps.accessId";
+    public final static String KEY_ACCESS_KEY = "odps.accessKey";
+    public final static String KEY_ACCOUNT = "odps.account";
+    public final static String KEY_ODPS_SERVER = "odps.odpsServer";
+    public final static String KEY_ODPS_TUNNEL = "odps.tunnelServer";
+    public final static String KEY_PROJECT = "odps.project";
+
+    private String accessId;
+    private String accessKey;
+    private String account;
+    private String odpsServer;
+    private String odpsTunnel;
+    private String project;
+
+    public static  TransferProjectConf create(Configuration adsWriterConf) {
+        TransferProjectConf res = new TransferProjectConf();
+        res.accessId = adsWriterConf.getString(KEY_ACCESS_ID);
+        res.accessKey = adsWriterConf.getString(KEY_ACCESS_KEY);
+        res.account = adsWriterConf.getString(KEY_ACCOUNT);
+        res.odpsServer = adsWriterConf.getString(KEY_ODPS_SERVER);
+        res.odpsTunnel = adsWriterConf.getString(KEY_ODPS_TUNNEL);
+        res.project = adsWriterConf.getString(KEY_PROJECT);
+        return res;
+    }
+
+    public String getAccessId() {
+        return accessId;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public String getAccount() {
+        return account;
+    }
+
+    public String getOdpsServer() {
+        return odpsServer;
+    }
+
+    public String getOdpsTunnel() {
+        return odpsTunnel;
+    }
+
+
+    public String getProject() {
+        return project;
+    }
+}

+ 77 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/odps/DataType.java

@@ -0,0 +1,77 @@
+package com.alibaba.datax.plugin.writer.adswriter.odps;
+
+/**
+ * ODPS 数据类型.
+ * <p>
+ * 当前定义了如下类型:
+ * <ul>
+ * <li>INTEGER
+ * <li>DOUBLE
+ * <li>BOOLEAN
+ * <li>STRING
+ * <li>DATETIME
+ * </ul>
+ * </p>
+ *
+ * @since 0.0.1
+ */
+public class DataType {
+
+    public final static byte INTEGER = 0;
+    public final static byte DOUBLE = 1;
+    public final static byte BOOLEAN = 2;
+    public final static byte STRING = 3;
+    public final static byte DATETIME = 4;
+
+    public static String toString(int type) {
+        switch (type) {
+            case INTEGER:
+                return "bigint";
+            case DOUBLE:
+                return "double";
+            case BOOLEAN:
+                return "boolean";
+            case STRING:
+                return "string";
+            case DATETIME:
+                return "datetime";
+            default:
+                throw new IllegalArgumentException("type=" + type);
+        }
+    }
+
+    /**
+     * 字符串的数据类型转换为byte常量定义的数据类型.
+     * <p>
+     * 转换规则:
+     * <ul>
+     * <li>tinyint, int, bigint, long - {@link #INTEGER}
+     * <li>double, float - {@link #DOUBLE}
+     * <li>string - {@link #STRING}
+     * <li>boolean, bool - {@link #BOOLEAN}
+     * <li>datetime - {@link #DATETIME}
+     * </ul>
+     * </p>
+     *
+     * @param type 字符串的数据类型
+     * @return byte常量定义的数据类型
+     * @throws IllegalArgumentException
+     */
+    public static byte convertToDataType(String type) throws IllegalArgumentException {
+        type = type.toLowerCase().trim();
+        if ("string".equals(type)) {
+            return STRING;
+        } else if ("bigint".equals(type) || "int".equals(type) || "tinyint".equals(type) || "long".equals(type)) {
+            return INTEGER;
+        } else if ("boolean".equals(type) || "bool".equals(type)) {
+            return BOOLEAN;
+        } else if ("double".equals(type) || "float".equals(type)) {
+            return DOUBLE;
+        } else if ("datetime".equals(type)) {
+            return DATETIME;
+        } else {
+            throw new IllegalArgumentException("unknown type: " + type);
+        }
+    }
+
+}

+ 63 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/odps/FieldSchema.java

@@ -0,0 +1,63 @@
+package com.alibaba.datax.plugin.writer.adswriter.odps;
+
+/**
+ * ODPS列属性,包含列名和类型 列名和类型与SQL的DESC表或分区显示的列名和类型一致
+ *
+ * @since 0.0.1
+ */
+public class FieldSchema {
+
+    /** 列名 */
+    private String name;
+
+    /** 列类型,如:string, bigint, boolean, datetime等等 */
+    private String type;
+
+    private String comment;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("FieldSchema [name=").append(name).append(", type=").append(type).append(", comment=")
+                .append(comment).append("]");
+        return builder.toString();
+    }
+
+    /**
+     * @return "col_name data_type [COMMENT col_comment]"
+     */
+    public String toDDL() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(name).append(" ").append(type);
+        String comment = this.comment;
+        if (comment != null && comment.length() > 0) {
+            builder.append(" ").append("COMMENT \"" + comment + "\"");
+        }
+        return builder.toString();
+    }
+
+}

+ 114 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/odps/TableMeta.java

@@ -0,0 +1,114 @@
+package com.alibaba.datax.plugin.writer.adswriter.odps;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * ODPS table meta.
+ *
+ * @since 0.0.1
+ */
+public class TableMeta {
+
+    private String tableName;
+
+    private List<FieldSchema> cols;
+
+    private List<FieldSchema> partitionKeys;
+
+    private int lifeCycle;
+
+    private String comment;
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+
+    public List<FieldSchema> getCols() {
+        return cols;
+    }
+
+    public void setCols(List<FieldSchema> cols) {
+        this.cols = cols;
+    }
+
+    public List<FieldSchema> getPartitionKeys() {
+        return partitionKeys;
+    }
+
+    public void setPartitionKeys(List<FieldSchema> partitionKeys) {
+        this.partitionKeys = partitionKeys;
+    }
+
+    public int getLifeCycle() {
+        return lifeCycle;
+    }
+
+    public void setLifeCycle(int lifeCycle) {
+        this.lifeCycle = lifeCycle;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("TableMeta [tableName=").append(tableName).append(", cols=").append(cols)
+                .append(", partitionKeys=").append(partitionKeys).append(", lifeCycle=").append(lifeCycle)
+                .append(", comment=").append(comment).append("]");
+        return builder.toString();
+    }
+
+    /**
+     * @return <br>
+     *         "CREATE TABLE [IF NOT EXISTS] table_name <br>
+     *         [(col_name data_type [COMMENT col_comment], ...)] <br>
+     *         [COMMENT table_comment] <br>
+     *         [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] <br>
+     *         [LIFECYCLE days] <br>
+     *         [AS select_statement] " <br>
+     */
+    public String toDDL() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("CREATE TABLE " + tableName).append(" ");
+        List<FieldSchema> cols = this.cols;
+        if (cols != null && cols.size() > 0) {
+            builder.append("(").append(toDDL(cols)).append(")").append(" ");
+        }
+        String comment = this.comment;
+        if (comment != null && comment.length() > 0) {
+            builder.append("COMMENT \"" + comment + "\" ");
+        }
+        List<FieldSchema> partitionKeys = this.partitionKeys;
+        if (partitionKeys != null && partitionKeys.size() > 0) {
+            builder.append("PARTITIONED BY ");
+            builder.append("(").append(toDDL(partitionKeys)).append(")").append(" ");
+        }
+        if (lifeCycle > 0) {
+            builder.append("LIFECYCLE " + lifeCycle).append(" ");
+        }
+        builder.append(";");
+        return builder.toString();
+    }
+
+    private String toDDL(List<FieldSchema> cols) {
+        StringBuilder builder = new StringBuilder();
+        Iterator<FieldSchema> iter = cols.iterator();
+        builder.append(iter.next().toDDL());
+        while (iter.hasNext()) {
+            builder.append(", ").append(iter.next().toDDL());
+        }
+        return builder.toString();
+    }
+
+}

+ 6 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/odps/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * ODPS meta.
+ *
+ * @since 0.0.1
+ */
+package com.alibaba.datax.plugin.writer.adswriter.odps;

+ 6 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * ADS Writer.
+ * 
+ * @since 0.0.1
+ */
+package com.alibaba.datax.plugin.writer.adswriter;

+ 175 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/util/AdsUtil.java

@@ -0,0 +1,175 @@
+package com.alibaba.datax.plugin.writer.adswriter.util;
+
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.plugin.rdbms.util.DBUtil;
+import com.alibaba.datax.plugin.rdbms.util.DataBaseType;
+import com.alibaba.datax.plugin.writer.adswriter.load.AdsHelper;
+import com.alibaba.datax.plugin.writer.adswriter.AdsWriterErrorCode;
+import com.alibaba.datax.plugin.writer.adswriter.load.TransferProjectConf;
+import com.alibaba.datax.plugin.writer.adswriter.odps.FieldSchema;
+import com.alibaba.datax.plugin.writer.adswriter.odps.TableMeta;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AdsUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(AdsUtil.class);
+
+    /*检查配置文件中必填的配置项是否都已填
+    * */
+    public static void checkNecessaryConfig(Configuration originalConfig, String writeMode) {
+        //检查ADS必要参数
+        originalConfig.getNecessaryValue(Key.ADS_URL,
+                AdsWriterErrorCode.REQUIRED_VALUE);
+        originalConfig.getNecessaryValue(Key.USERNAME,
+                AdsWriterErrorCode.REQUIRED_VALUE);
+        originalConfig.getNecessaryValue(Key.PASSWORD,
+                AdsWriterErrorCode.REQUIRED_VALUE);
+        originalConfig.getNecessaryValue(Key.SCHEMA,
+                AdsWriterErrorCode.REQUIRED_VALUE);
+        if(Constant.LOADMODE.equals(writeMode)) {
+            originalConfig.getNecessaryValue(Key.Life_CYCLE,
+                    AdsWriterErrorCode.REQUIRED_VALUE);
+            Integer lifeCycle = originalConfig.getInt(Key.Life_CYCLE);
+            if (lifeCycle <= 0) {
+                throw DataXException.asDataXException(AdsWriterErrorCode.INVALID_CONFIG_VALUE, "配置项[lifeCycle]的值必须大于零.");
+            }
+            originalConfig.getNecessaryValue(Key.ADS_TABLE,
+                    AdsWriterErrorCode.REQUIRED_VALUE);
+            Boolean overwrite = originalConfig.getBool(Key.OVER_WRITE);
+            if (overwrite == null) {
+                throw DataXException.asDataXException(AdsWriterErrorCode.REQUIRED_VALUE, "配置项[overWrite]是必填项.");
+            }
+        }
+        if (Constant.STREAMMODE.equalsIgnoreCase(writeMode)) {
+            originalConfig.getNecessaryValue(Key.OPIndex, AdsWriterErrorCode.REQUIRED_VALUE);
+        }
+    }
+
+    /*生成AdsHelp实例
+    * */
+    public static AdsHelper createAdsHelper(Configuration originalConfig){
+        //Get adsUrl,userName,password,schema等参数,创建AdsHelp实例
+        String adsUrl = originalConfig.getString(Key.ADS_URL);
+        String userName = originalConfig.getString(Key.USERNAME);
+        String password = originalConfig.getString(Key.PASSWORD);
+        String schema = originalConfig.getString(Key.SCHEMA);
+        Long socketTimeout = originalConfig.getLong(Key.SOCKET_TIMEOUT, Constant.DEFAULT_SOCKET_TIMEOUT);
+        String suffix = originalConfig.getString(Key.JDBC_URL_SUFFIX, "");
+        return new AdsHelper(adsUrl,userName,password,schema,socketTimeout,suffix);
+    }
+
+    public static AdsHelper createAdsHelperWithOdpsAccount(Configuration originalConfig) {
+        String adsUrl = originalConfig.getString(Key.ADS_URL);
+        String userName = originalConfig.getString(TransferProjectConf.KEY_ACCESS_ID);
+        String password = originalConfig.getString(TransferProjectConf.KEY_ACCESS_KEY);
+        String schema = originalConfig.getString(Key.SCHEMA);
+        Long socketTimeout = originalConfig.getLong(Key.SOCKET_TIMEOUT, Constant.DEFAULT_SOCKET_TIMEOUT);
+        String suffix = originalConfig.getString(Key.JDBC_URL_SUFFIX, "");
+        return new AdsHelper(adsUrl, userName, password, schema,socketTimeout,suffix);
+    }
+
+    /*生成ODPSWriter Plugin所需要的配置文件
+    * */
+    public static Configuration generateConf(Configuration originalConfig, String odpsTableName, TableMeta tableMeta, TransferProjectConf transConf){
+        Configuration newConfig = originalConfig.clone();
+        newConfig.set(Key.ODPSTABLENAME, odpsTableName);
+        newConfig.set(Key.ODPS_SERVER, transConf.getOdpsServer());
+        newConfig.set(Key.TUNNEL_SERVER,transConf.getOdpsTunnel());
+        newConfig.set(Key.ACCESS_ID,transConf.getAccessId());
+        newConfig.set(Key.ACCESS_KEY,transConf.getAccessKey());
+        newConfig.set(Key.PROJECT,transConf.getProject());
+        newConfig.set(Key.TRUNCATE, true);
+        newConfig.set(Key.PARTITION,null);
+//        newConfig.remove(Key.PARTITION);
+        List<FieldSchema> cols = tableMeta.getCols();
+        List<String> allColumns = new ArrayList<String>();
+        if(cols != null && !cols.isEmpty()){
+            for(FieldSchema col:cols){
+                allColumns.add(col.getName());
+            }
+        }
+        newConfig.set(Key.COLUMN,allColumns);
+        return newConfig;
+    }
+
+    /*生成ADS数据导入时的source_path
+    * */
+    public static String generateSourcePath(String project, String tmpOdpsTableName, String odpsPartition){
+        StringBuilder builder = new StringBuilder();
+        String partition = transferOdpsPartitionToAds(odpsPartition);
+        builder.append("odps://").append(project).append("/").append(tmpOdpsTableName);
+        if(odpsPartition != null && !odpsPartition.isEmpty()){
+            builder.append("/").append(partition);
+        }
+        return builder.toString();
+    }
+
+    public static String transferOdpsPartitionToAds(String odpsPartition){
+        if(odpsPartition == null || odpsPartition.isEmpty())
+            return null;
+        String adsPartition = formatPartition(odpsPartition);;
+        String[] partitions = adsPartition.split("/");
+        for(int last = partitions.length; last > 0; last--){
+
+            String partitionPart = partitions[last-1];
+            String newPart = partitionPart.replace(".*", "*").replace("*", ".*");
+            if(newPart.split("=")[1].equals(".*")){
+                adsPartition = adsPartition.substring(0,adsPartition.length()-partitionPart.length());
+            }else{
+                break;
+            }
+            if(adsPartition.endsWith("/")){
+                adsPartition = adsPartition.substring(0,adsPartition.length()-1);
+            }
+        }
+        if (adsPartition.contains("*"))
+            throw DataXException.asDataXException(AdsWriterErrorCode.ODPS_PARTITION_FAILED, "");
+        return adsPartition;
+    }
+
+    public static String formatPartition(String partition) {
+        return partition.trim().replaceAll(" *= *", "=")
+                .replaceAll(" */ *", ",").replaceAll(" *, *", ",")
+                .replaceAll("'", "").replaceAll(",", "/");
+    }
+    
+    public static String prepareJdbcUrl(Configuration conf) {
+        String adsURL = conf.getString(Key.ADS_URL);
+        String schema = conf.getString(Key.SCHEMA);
+        Long socketTimeout = conf.getLong(Key.SOCKET_TIMEOUT,
+                Constant.DEFAULT_SOCKET_TIMEOUT);
+        String suffix = conf.getString(Key.JDBC_URL_SUFFIX, "");
+        return AdsUtil.prepareJdbcUrl(adsURL, schema, socketTimeout, suffix);
+    }
+
+    public static String prepareJdbcUrl(String adsURL, String schema,
+            Long socketTimeout, String suffix) {
+        String jdbcUrl = null;
+        // like autoReconnect=true&failOverReadOnly=false&maxReconnects=10
+        if (StringUtils.isNotBlank(suffix)) {
+            jdbcUrl = String
+                    .format("jdbc:mysql://%s/%s?useUnicode=true&characterEncoding=UTF-8&socketTimeout=%s&%s",
+                            adsURL, schema, socketTimeout, suffix);
+        } else {
+            jdbcUrl = String
+                    .format("jdbc:mysql://%s/%s?useUnicode=true&characterEncoding=UTF-8&socketTimeout=%s",
+                            adsURL, schema, socketTimeout);
+        }
+        return jdbcUrl;
+    }
+    
+    public static Connection getAdsConnect(Configuration conf) {
+        String userName = conf.getString(Key.USERNAME);
+        String passWord = conf.getString(Key.PASSWORD);
+        String jdbcUrl = AdsUtil.prepareJdbcUrl(conf);
+        Connection connection = DBUtil.getConnection(DataBaseType.ADS, jdbcUrl, userName, passWord);
+        return connection;
+    }
+}

+ 29 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/util/Constant.java

@@ -0,0 +1,29 @@
+package com.alibaba.datax.plugin.writer.adswriter.util;
+
+public class Constant {
+
+    public static final String LOADMODE = "load";
+
+    public static final String INSERTMODE = "insert";
+    
+    public static final String DELETEMODE = "delete";
+
+    public static final String REPLACEMODE = "replace";
+    
+    public static final String STREAMMODE = "stream";
+
+    public static final int DEFAULT_BATCH_SIZE = 32;
+    
+    public static final long DEFAULT_SOCKET_TIMEOUT = 3600000L;
+    
+    public static final int DEFAULT_RETRY_TIMES = 3;
+    
+    public static final String INSERT_TEMPLATE = "insert into %s ( %s ) values ";
+    
+    public static final String DELETE_TEMPLATE = "delete from %s where ";
+    
+    public static final String ADS_TABLE_INFO = "adsTableInfo";
+    
+    public static final String ADS_QUOTE_CHARACTER = "`";
+    
+}

+ 66 - 0
adswriter/src/main/java/com/alibaba/datax/plugin/writer/adswriter/util/Key.java

@@ -0,0 +1,66 @@
+package com.alibaba.datax.plugin.writer.adswriter.util;
+
+
+public final class Key {
+
+    public final static String ADS_URL = "url";
+
+    public final static String USERNAME = "username";
+
+    public final static String PASSWORD = "password";
+
+    public final static String SCHEMA = "schema";
+
+    public final static String ADS_TABLE = "table";
+
+    public final static String Life_CYCLE = "lifeCycle";
+
+    public final static String OVER_WRITE = "overWrite";
+
+    public final static String WRITE_MODE = "writeMode";
+
+
+    public final static String COLUMN = "column";
+    
+    public final static String OPIndex = "opIndex";
+
+    public final static String EMPTY_AS_NULL = "emptyAsNull";
+
+    public final static String BATCH_SIZE = "batchSize";
+    
+    public final static String BUFFER_SIZE = "bufferSize";
+
+    public final static String IGNORE_INSERT = "ignoreInsert";
+
+    public final static String PRE_SQL = "preSql";
+
+    public final static String POST_SQL = "postSql";
+    
+    public final static String SOCKET_TIMEOUT = "socketTimeout";
+    
+    public final static String RETRY_CONNECTION_TIME = "retryTimes";
+
+    public final static String RETRY_INTERVAL_TIME = "retryIntervalTime";
+
+    public final static String JDBC_URL_SUFFIX = "urlSuffix";
+
+    /**
+     * 以下是odps writer的key
+     */
+    public final static String PARTITION = "partition";
+
+    public final static String ODPSTABLENAME = "table";
+
+    public final static String ODPS_SERVER = "odpsServer";
+
+    public final static String TUNNEL_SERVER = "tunnelServer";
+
+    public final static String ACCESS_ID = "accessId";
+
+    public final static String ACCESS_KEY = "accessKey";
+
+    public final static String PROJECT = "project";
+
+    public final static String TRUNCATE = "truncate";
+
+}

+ 6 - 0
adswriter/src/main/resources/plugin.json

@@ -0,0 +1,6 @@
+{
+    "name": "adswriter",
+    "class": "com.alibaba.datax.plugin.writer.adswriter.AdsWriter",
+    "description": "",
+    "developer": "alibaba"
+}

+ 13 - 0
adswriter/src/main/resources/plugin_job_template.json

@@ -0,0 +1,13 @@
+{
+    "name": "adswriter",
+    "parameter": {
+        "url": "",
+        "username": "",
+        "password": "",
+        "schema": "",
+        "table": "",
+        "partition": "",
+        "overWrite": "",
+        "lifeCycle": 2
+    }
+}

+ 217 - 0
cassandrareader/doc/cassandrareader.md

@@ -0,0 +1,217 @@
+
+# CassandraReader 插件文档
+
+
+___
+
+
+
+## 1 快速介绍
+
+CassandraReader插件实现了从Cassandra读取数据。在底层实现上,CassandraReader通过datastax的java driver连接Cassandra实例,并执行相应的cql语句将数据从cassandra中SELECT出来。
+
+
+## 2 实现原理
+
+简而言之,CassandraReader通过java driver连接到Cassandra实例,并根据用户配置的信息生成查询SELECT CQL语句,然后发送到Cassandra,并将该CQL执行返回结果使用DataX自定义的数据类型拼装为抽象的数据集,并传递给下游Writer处理。
+
+对于用户配置Table、Column的信息,CassandraReader将其拼接为CQL语句发送到Cassandra。
+
+
+## 3 功能说明
+
+### 3.1 配置样例
+
+* 配置一个从Cassandra同步抽取数据到本地的作业:
+
+```
+{
+    "job": {
+        "setting": {
+            "speed": {
+                 "channel": 3
+            }
+        },
+        "content": [
+            {
+               "reader": {
+                    "name": "cassandrareader",
+                    "parameter": {
+                        "host": "localhost",
+                        "port": 9042,
+                        "useSSL": false,
+                        "keyspace": "test",
+                        "table": "datax_src",
+                        "column": [
+                            "textCol",
+                            "blobCol",
+                            "writetime(blobCol)",
+                            "boolCol",
+                            "smallintCol",
+                            "tinyintCol",
+                            "intCol",
+                            "bigintCol",
+                            "varintCol",
+                            "floatCol",
+                            "doubleCol",
+                            "decimalCol",
+                            "dateCol",
+                            "timeCol",
+                            "timeStampCol",
+                            "uuidCol",
+                            "inetCol",
+                            "durationCol",
+                            "listCol",
+                            "mapCol",
+                            "setCol"
+                            "tupleCol"
+                            "udtCol",
+                        ]
+                    }
+               },
+               "writer": {
+                    "name": "streamwriter",
+                    "parameter": {
+                        "print":true
+                    }
+                }
+            }
+        ]
+    }
+}
+
+```
+
+
+### 3.2 参数说明
+
+* **host**
+
+	* 描述:Cassandra连接点的域名或ip,多个node之间用逗号分隔。 <br />
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **port**
+
+	* 描述:Cassandra端口。 <br />
+
+	* 必选:是 <br />
+
+	* 默认值:9042 <br />
+
+* **username**
+
+	* 描述:数据源的用户名 <br />
+
+	* 必选:否 <br />
+
+	* 默认值:无 <br />
+
+* **password**
+
+	* 描述:数据源指定用户名的密码 <br />
+
+	* 必选:否 <br />
+
+	* 默认值:无 <br />
+
+* **useSSL**
+
+	* 描述:是否使用SSL连接。<br />
+
+	* 必选:否 <br />
+
+	* 默认值:false <br />
+
+* **keyspace**
+
+	* 描述:需要同步的表所在的keyspace。<br />
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **table**
+
+	* 描述:所选取的需要同步的表。<br />
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **column**
+
+	* 描述:所配置的表中需要同步的列集合。<br />
+	  其中的元素可以指定列的名称或writetime(column_name),后一种形式会读取column_name列的时间戳而不是数据。
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+
+* **where**
+
+	* 描述:数据筛选条件的cql表达式,例如:<br />
+	```
+	"where":"textcol='a'"
+	```
+
+	* 必选:否 <br />
+
+	* 默认值:无 <br />
+
+* **allowFiltering**
+
+	* 描述:是否在服务端过滤数据。参考cassandra文档中ALLOW FILTERING关键字的相关描述。<br />
+
+	* 必选:否 <br />
+
+	* 默认值:无 <br />
+
+* **consistancyLevel**
+
+	* 描述:数据一致性级别。可选ONE|QUORUM|LOCAL_QUORUM|EACH_QUORUM|ALL|ANY|TWO|THREE|LOCAL_ONE<br />
+
+	* 必选:否 <br />
+
+	* 默认值:LOCAL_QUORUM <br />
+
+
+### 3.3 类型转换
+
+目前CassandraReader支持除counter和Custom类型之外的所有类型。
+
+下面列出CassandraReader针对Cassandra类型转换列表:
+
+
+| DataX 内部类型| Cassandra 数据类型    |
+| -------- | -----  |
+| Long     |int, tinyint, smallint,varint,bigint,time|
+| Double   |float, double, decimal|
+| String   |ascii,varchar, text,uuid,timeuuid,duration,list,map,set,tuple,udt,inet   |
+| Date     |date, timestamp   |
+| Boolean  |bool   |
+| Bytes    |blob   |
+
+
+
+请注意:
+
+* 目前不支持counter类型和custom类型。
+
+## 4 性能报告
+
+略
+
+## 5 约束限制
+
+### 5.1 主备同步数据恢复问题
+
+略
+
+## 6 FAQ
+
+
+

+ 133 - 0
cassandrareader/pom.xml

@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>com.alibaba.datax</groupId>
+		<artifactId>datax-all</artifactId>
+		<version>0.0.1-SNAPSHOT</version>
+	</parent>
+	<artifactId>cassandrareader</artifactId>
+	<name>cassandrareader</name>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>com.alibaba.datax</groupId>
+			<artifactId>datax-common</artifactId>
+			<version>${datax-project-version}</version>
+			<exclusions>
+				<exclusion>
+					<artifactId>slf4j-log4j12</artifactId>
+					<groupId>org.slf4j</groupId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.datastax.cassandra</groupId>
+			<artifactId>cassandra-driver-core</artifactId>
+			<version>3.7.2</version>
+			<classifier>shaded</classifier>
+			<exclusions>
+				<exclusion>
+					<groupId>com.google.guava</groupId>
+					<artifactId>guava</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+			<version>16.0.1</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+			<version>1.9</version>
+		</dependency>
+
+		<!-- for test -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba.datax</groupId>
+			<artifactId>datax-core</artifactId>
+			<version>${datax-project-version}</version>
+			<exclusions>
+				<exclusion>
+					<groupId>com.alibaba.datax</groupId>
+					<artifactId>datax-service-face</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>org.apache.hadoop</groupId>
+					<artifactId>hadoop-common</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>org.apache.hive</groupId>
+					<artifactId>hive-exec</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>org.apache.hive</groupId>
+					<artifactId>hive-serde</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>javolution</groupId>
+					<artifactId>javolution</artifactId>
+				</exclusion>
+			</exclusions>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-all</artifactId>
+			<version>1.9.5</version>
+			<scope>test</scope>
+		</dependency>
+
+	</dependencies>
+
+	<build>
+		<plugins>
+			<!-- compiler plugin -->
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>${jdk-version}</source>
+					<target>${jdk-version}</target>
+					<encoding>${project-sourceEncoding}</encoding>
+				</configuration>
+			</plugin>
+			<!-- assembly plugin -->
+			<plugin>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<configuration>
+					<descriptors>
+						<descriptor>src/main/assembly/package.xml</descriptor>
+					</descriptors>
+					<finalName>datax</finalName>
+				</configuration>
+				<executions>
+					<execution>
+						<id>dwzip</id>
+						<phase>package</phase>
+						<goals>
+							<goal>single</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 35 - 0
cassandrareader/src/main/assembly/package.xml

@@ -0,0 +1,35 @@
+<assembly
+        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+    <id></id>
+    <formats>
+        <format>dir</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <fileSets>
+        <fileSet>
+            <directory>src/main/resources</directory>
+            <includes>
+                <include>plugin.json</include>
+                <include>plugin_job_template.json</include>
+            </includes>
+            <outputDirectory>plugin/reader/cassandrareader</outputDirectory>
+        </fileSet>
+        <fileSet>
+            <directory>target/</directory>
+            <includes>
+                <include>cassandrareader-0.0.1-SNAPSHOT.jar</include>
+            </includes>
+            <outputDirectory>plugin/reader/cassandrareader</outputDirectory>
+        </fileSet>
+    </fileSets>
+
+    <dependencySets>
+        <dependencySet>
+            <useProjectArtifact>false</useProjectArtifact>
+            <outputDirectory>plugin/reader/cassandrareader/libs</outputDirectory>
+            <scope>runtime</scope>
+        </dependencySet>
+    </dependencySets>
+</assembly>

+ 123 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/CassandraReader.java

@@ -0,0 +1,123 @@
+package com.alibaba.datax.plugin.reader.cassandrareader;
+
+import com.alibaba.datax.common.element.Record;
+import com.alibaba.datax.common.plugin.RecordSender;
+import com.alibaba.datax.common.spi.Reader;
+import com.alibaba.datax.common.util.Configuration;
+
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.ConsistencyLevel;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.SimpleStatement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class CassandraReader extends Reader {
+  private static final Logger LOG = LoggerFactory
+      .getLogger(CassandraReader.class);
+
+  public static class Job extends Reader.Job {
+
+    private Configuration jobConfig = null;
+    private Cluster cluster = null;
+
+    @Override public void init() {
+      this.jobConfig = super.getPluginJobConf();
+      this.jobConfig = super.getPluginJobConf();
+      String username = jobConfig.getString(Key.USERNAME);
+      String password = jobConfig.getString(Key.PASSWORD);
+      String hosts = jobConfig.getString(Key.HOST);
+      Integer port = jobConfig.getInt(Key.PORT,9042);
+      boolean useSSL = jobConfig.getBool(Key.USESSL);
+
+      if ((username != null) && !username.isEmpty()) {
+        Cluster.Builder clusterBuilder = Cluster.builder().withCredentials(username, password)
+            .withPort(Integer.valueOf(port)).addContactPoints(hosts.split(","));
+        if (useSSL) {
+          clusterBuilder = clusterBuilder.withSSL();
+        }
+        cluster = clusterBuilder.build();
+      } else {
+        cluster = Cluster.builder().withPort(Integer.valueOf(port))
+            .addContactPoints(hosts.split(",")).build();
+      }
+      CassandraReaderHelper.checkConfig(jobConfig,cluster);
+    }
+
+    @Override public void destroy() {
+
+    }
+
+    @Override public List<Configuration> split(int adviceNumber) {
+      List<Configuration> splittedConfigs = CassandraReaderHelper.splitJob(adviceNumber,jobConfig,cluster);
+      return splittedConfigs;
+    }
+
+  }
+
+  public static class Task extends Reader.Task {
+    private Configuration taskConfig;
+    private Cluster cluster = null;
+    private Session session = null;
+    private String queryString = null;
+    private ConsistencyLevel consistencyLevel;
+    private int columnNumber = 0;
+    private List<String> columnMeta = null;
+
+    @Override public void init() {
+      this.taskConfig = super.getPluginJobConf();
+      String username = taskConfig.getString(Key.USERNAME);
+      String password = taskConfig.getString(Key.PASSWORD);
+      String hosts = taskConfig.getString(Key.HOST);
+      Integer port = taskConfig.getInt(Key.PORT);
+      boolean useSSL = taskConfig.getBool(Key.USESSL);
+      String keyspace = taskConfig.getString(Key.KEYSPACE);
+      this.columnMeta = taskConfig.getList(Key.COLUMN,String.class);
+      columnNumber = columnMeta.size();
+
+      if ((username != null) && !username.isEmpty()) {
+        Cluster.Builder clusterBuilder = Cluster.builder().withCredentials(username, password)
+            .withPort(Integer.valueOf(port)).addContactPoints(hosts.split(","));
+        if (useSSL) {
+          clusterBuilder = clusterBuilder.withSSL();
+        }
+        cluster = clusterBuilder.build();
+      } else {
+        cluster = Cluster.builder().withPort(Integer.valueOf(port))
+            .addContactPoints(hosts.split(",")).build();
+      }
+      session = cluster.connect(keyspace);
+      String cl = taskConfig.getString(Key.CONSITANCY_LEVEL);
+      if( cl != null && !cl.isEmpty() ) {
+        consistencyLevel = ConsistencyLevel.valueOf(cl);
+      } else {
+        consistencyLevel = ConsistencyLevel.LOCAL_QUORUM;
+      }
+
+      queryString = CassandraReaderHelper.getQueryString(taskConfig,cluster);
+      LOG.info("query = " + queryString);
+
+    }
+
+    @Override public void startRead(RecordSender recordSender) {
+      ResultSet r = session.execute(new SimpleStatement(queryString).setConsistencyLevel(consistencyLevel));
+      for (Row row : r ) {
+        Record record = recordSender.createRecord();
+        record = CassandraReaderHelper.buildRecord(record,row,r.getColumnDefinitions(),columnNumber,
+            super.getTaskPluginCollector());
+        if( record != null )
+          recordSender.sendToWriter(record);
+      }
+    }
+
+    @Override public void destroy() {
+
+    }
+
+  }
+
+}

+ 32 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/CassandraReaderErrorCode.java

@@ -0,0 +1,32 @@
+package com.alibaba.datax.plugin.reader.cassandrareader;
+
+import com.alibaba.datax.common.spi.ErrorCode;
+
+public enum CassandraReaderErrorCode implements ErrorCode {
+    CONF_ERROR("CassandraReader-00", "配置错误."),
+    ;
+
+    private final String code;
+    private final String description;
+
+    private CassandraReaderErrorCode(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    @Override
+    public String getCode() {
+        return this.code;
+    }
+
+    @Override
+    public String getDescription() {
+        return this.description;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Code:[%s], Description:[%s]. ", this.code,
+                this.description);
+    }
+}

+ 588 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/CassandraReaderHelper.java

@@ -0,0 +1,588 @@
+package com.alibaba.datax.plugin.reader.cassandrareader;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.alibaba.datax.common.element.BoolColumn;
+import com.alibaba.datax.common.element.BytesColumn;
+import com.alibaba.datax.common.element.DateColumn;
+import com.alibaba.datax.common.element.DoubleColumn;
+import com.alibaba.datax.common.element.LongColumn;
+import com.alibaba.datax.common.element.Record;
+import com.alibaba.datax.common.element.StringColumn;
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.plugin.TaskPluginCollector;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.fastjson2.JSON;
+
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.CodecRegistry;
+import com.datastax.driver.core.ColumnDefinitions;
+import com.datastax.driver.core.ColumnMetadata;
+import com.datastax.driver.core.DataType;
+import com.datastax.driver.core.Duration;
+import com.datastax.driver.core.LocalDate;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.TableMetadata;
+import com.datastax.driver.core.TupleType;
+import com.datastax.driver.core.TupleValue;
+import com.datastax.driver.core.UDTValue;
+import com.datastax.driver.core.UserType;
+import com.google.common.reflect.TypeToken;
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Created by mazhenlin on 2019/8/21.
+ */
+public class CassandraReaderHelper {
+  static CodecRegistry registry = new CodecRegistry();
+  private static final Logger LOG = LoggerFactory
+      .getLogger(CassandraReader.class);
+
+  static class TypeNotSupported extends Exception{}
+
+  static String toJSonString(Object o, DataType type ) throws Exception{
+    if( o == null ) return JSON.toJSONString(null);
+    switch (type.getName()) {
+    case LIST:
+    case MAP:
+    case SET:
+    case TUPLE:
+    case UDT:
+      return JSON.toJSONString(transferObjectForJson(o,type));
+
+    default:
+      return JSON.toJSONString(o);
+    }
+  }
+
+  static Object transferObjectForJson(Object o,DataType type) throws TypeNotSupported{
+    if( o == null ) return o;
+    switch (type.getName()) {
+    case ASCII:
+    case TEXT:
+    case VARCHAR:
+    case BOOLEAN:
+    case SMALLINT:
+    case TINYINT:
+    case INT:
+    case BIGINT:
+    case VARINT:
+    case FLOAT:
+    case DOUBLE:
+    case DECIMAL:
+    case UUID:
+    case TIMEUUID:
+    case TIME:
+      return o;
+
+    case BLOB:
+      ByteBuffer byteBuffer = (ByteBuffer)o;
+      String s = Base64.encodeBase64String(
+            Arrays.copyOfRange(byteBuffer.array(),byteBuffer.position(),
+                byteBuffer.limit()));
+      return s;
+
+    case DATE:
+      return ((LocalDate)o).getMillisSinceEpoch();
+
+    case TIMESTAMP:
+      return ((Date)o).getTime();
+
+    case DURATION:
+      return o.toString();
+
+    case INET:
+      return ((InetAddress)o).getHostAddress();
+
+    case LIST: {
+      return transferListForJson((List)o,type.getTypeArguments().get(0));
+    }
+
+    case MAP: {
+      DataType keyType = type.getTypeArguments().get(0);
+      DataType valType = type.getTypeArguments().get(1);
+      return transferMapForJson((Map)o,keyType,valType);
+    }
+
+    case SET: {
+      return transferSetForJson((Set)o, type.getTypeArguments().get(0));
+    }
+
+    case TUPLE: {
+      return transferTupleForJson((TupleValue)o,((TupleType)type).getComponentTypes());
+    }
+
+    case UDT: {
+      return transferUDTForJson((UDTValue)o);
+    }
+
+    default:
+      throw new TypeNotSupported();
+    }
+
+  }
+
+  static List transferListForJson(List clist, DataType eleType) throws TypeNotSupported {
+    List result = new ArrayList();
+    switch (eleType.getName()) {
+    case ASCII:
+    case TEXT:
+    case VARCHAR:
+    case BOOLEAN:
+    case SMALLINT:
+    case TINYINT:
+    case INT:
+    case BIGINT:
+    case VARINT:
+    case FLOAT:
+    case DOUBLE:
+    case DECIMAL:
+    case TIME:
+    case UUID:
+    case TIMEUUID:
+      return clist;
+
+    case BLOB:
+    case DATE:
+    case TIMESTAMP:
+    case DURATION:
+    case INET:
+    case LIST:
+    case MAP:
+    case SET:
+    case TUPLE:
+    case UDT:
+      for (Object item : clist) {
+        Object newItem = transferObjectForJson(item, eleType);
+        result.add(newItem);
+      }
+      break;
+
+    default:
+      throw new TypeNotSupported();
+    }
+
+    return result;
+  }
+
+  static Set transferSetForJson(Set cset,DataType eleType) throws TypeNotSupported{
+    Set result = new HashSet();
+    switch (eleType.getName()) {
+    case ASCII:
+    case TEXT:
+    case VARCHAR:
+    case BOOLEAN:
+    case SMALLINT:
+    case TINYINT:
+    case INT:
+    case BIGINT:
+    case VARINT:
+    case FLOAT:
+    case DOUBLE:
+    case DECIMAL:
+    case TIME:
+    case UUID:
+    case TIMEUUID:
+      return cset;
+
+    case BLOB:
+    case DATE:
+    case TIMESTAMP:
+    case DURATION:
+    case INET:
+    case LIST:
+    case MAP:
+    case SET:
+    case TUPLE:
+    case UDT:
+      for (Object item : cset) {
+        Object newItem = transferObjectForJson(item,eleType);
+        result.add(newItem);
+      }
+      break;
+
+    default:
+      throw new TypeNotSupported();
+    }
+
+    return result;
+  }
+
+  static Map transferMapForJson(Map cmap,DataType keyType,DataType valueType) throws TypeNotSupported {
+    Map newMap = new HashMap();
+    for( Object e : cmap.entrySet() ) {
+      Object k = ((Map.Entry)e).getKey();
+      Object v = ((Map.Entry)e).getValue();
+      Object newKey = transferObjectForJson(k,keyType);
+      Object newValue = transferObjectForJson(v,valueType);
+      if( !(newKey instanceof String) ) {
+        newKey = JSON.toJSONString(newKey);
+      }
+      newMap.put(newKey,newValue);
+    }
+    return newMap;
+  }
+
+  static List transferTupleForJson(TupleValue tupleValue,List<DataType> componentTypes) throws TypeNotSupported {
+    List l = new ArrayList();
+    for (int j = 0; j < componentTypes.size(); j++ ) {
+      DataType dataType = componentTypes.get(j);
+      TypeToken<?> eltClass = registry.codecFor(dataType).getJavaType();
+      Object ele = tupleValue.get(j,eltClass);
+      l.add(transferObjectForJson(ele,dataType));
+    }
+    return l;
+  }
+
+  static Map transferUDTForJson(UDTValue udtValue) throws TypeNotSupported {
+    Map<String,Object> newMap = new HashMap();
+    int j = 0;
+    for (UserType.Field f : udtValue.getType()) {
+      DataType dataType = f.getType();
+      TypeToken<?> eltClass = registry.codecFor(dataType).getJavaType();
+      Object ele = udtValue.get(j, eltClass);
+      newMap.put(f.getName(),transferObjectForJson(ele,dataType));
+      j++;
+    }
+    return newMap;
+  }
+
+  static Record buildRecord(Record record, Row rs, ColumnDefinitions metaData, int columnNumber,
+      TaskPluginCollector taskPluginCollector) {
+
+    try {
+      for (int i = 0; i < columnNumber; i++)
+        try {
+          if (rs.isNull(i)) {
+            record.addColumn(new StringColumn());
+          continue;
+        }
+        switch (metaData.getType(i).getName()) {
+
+        case ASCII:
+        case TEXT:
+        case VARCHAR:
+          record.addColumn(new StringColumn(rs.getString(i)));
+          break;
+
+        case BLOB:
+          record.addColumn(new BytesColumn(rs.getBytes(i).array()));
+          break;
+
+        case BOOLEAN:
+          record.addColumn(new BoolColumn(rs.getBool(i)));
+          break;
+
+        case SMALLINT:
+          record.addColumn(new LongColumn((int)rs.getShort(i)));
+          break;
+
+        case TINYINT:
+          record.addColumn(new LongColumn((int)rs.getByte(i)));
+          break;
+
+        case INT:
+          record.addColumn(new LongColumn(rs.getInt(i)));
+          break;
+
+        case COUNTER:
+        case BIGINT:
+          record.addColumn(new LongColumn(rs.getLong(i)));
+          break;
+
+        case VARINT:
+          record.addColumn(new LongColumn(rs.getVarint(i)));
+          break;
+
+        case FLOAT:
+          record.addColumn(new DoubleColumn(rs.getFloat(i)));
+          break;
+
+        case DOUBLE:
+          record.addColumn(new DoubleColumn(rs.getDouble(i)));
+          break;
+
+        case DECIMAL:
+          record.addColumn(new DoubleColumn(rs.getDecimal(i)));
+          break;
+
+        case DATE:
+          record.addColumn(new DateColumn(rs.getDate(i).getMillisSinceEpoch()));
+          break;
+
+        case TIME:
+          record.addColumn(new LongColumn(rs.getTime(i)));
+          break;
+
+        case TIMESTAMP:
+          record.addColumn(new DateColumn(rs.getTimestamp(i)));
+          break;
+
+        case UUID:
+        case TIMEUUID:
+          record.addColumn(new StringColumn(rs.getUUID(i).toString()));
+          break;
+
+        case INET:
+          record.addColumn(new StringColumn(rs.getInet(i).getHostAddress()));
+          break;
+
+        case DURATION:
+          record.addColumn(new StringColumn(rs.get(i,Duration.class).toString()));
+          break;
+
+        case LIST: {
+          TypeToken listEltClass = registry.codecFor(metaData.getType(i).getTypeArguments().get(0)).getJavaType();
+          List<?> l = rs.getList(i, listEltClass);
+          record.addColumn(new StringColumn(toJSonString(l,metaData.getType(i))));
+        }
+        break;
+
+        case MAP: {
+          DataType keyType = metaData.getType(i).getTypeArguments().get(0);
+          DataType valType = metaData.getType(i).getTypeArguments().get(1);
+          TypeToken<?> keyEltClass = registry.codecFor(keyType).getJavaType();
+          TypeToken<?> valEltClass = registry.codecFor(valType).getJavaType();
+          Map<?,?> m = rs.getMap(i, keyEltClass, valEltClass);
+          record.addColumn(new StringColumn(toJSonString(m,metaData.getType(i))));
+        }
+        break;
+
+        case SET: {
+          TypeToken<?> setEltClass = registry.codecFor(metaData.getType(i).getTypeArguments().get(0))
+              .getJavaType();
+          Set<?> set = rs.getSet(i, setEltClass);
+          record.addColumn(new StringColumn(toJSonString(set,metaData.getType(i))));
+        }
+        break;
+
+        case TUPLE: {
+          TupleValue t = rs.getTupleValue(i);
+          record.addColumn(new StringColumn(toJSonString(t,metaData.getType(i))));
+        }
+        break;
+
+        case UDT: {
+          UDTValue t = rs.getUDTValue(i);
+          record.addColumn(new StringColumn(toJSonString(t,metaData.getType(i))));
+        }
+        break;
+
+        default:
+          throw DataXException
+              .asDataXException(
+                  CassandraReaderErrorCode.CONF_ERROR,
+                  String.format(
+                      "您的配置文件中的列配置信息有误. 因为DataX 不支持数据库读取这种字段类型. 字段名:[%s], "
+                          + "字段类型:[%s]. ",
+                      metaData.getName(i),
+                      metaData.getType(i)));
+        }
+      } catch (TypeNotSupported t) {
+        throw DataXException
+            .asDataXException(
+                CassandraReaderErrorCode.CONF_ERROR,
+                String.format(
+                    "您的配置文件中的列配置信息有误. 因为DataX 不支持数据库读取这种字段类型. 字段名:[%s], "
+                        + "字段类型:[%s]. ",
+                    metaData.getName(i),
+                    metaData.getType(i)));
+
+      }
+    } catch (Exception e) {
+      //TODO 这里识别为脏数据靠谱吗?
+      taskPluginCollector.collectDirtyRecord(record, e);
+      if (e instanceof DataXException) {
+        throw (DataXException) e;
+      }
+      return null;
+    }
+    return record;
+  }
+
+  public static List<Configuration> splitJob(int adviceNumber,Configuration jobConfig,Cluster cluster) {
+    List<Configuration> splittedConfigs = new ArrayList<Configuration>();
+    if( adviceNumber <= 1 ) {
+      splittedConfigs.add(jobConfig);
+      return splittedConfigs;
+    }
+    String where = jobConfig.getString(Key.WHERE);
+    if(where != null && where.toLowerCase().contains("token(")) {
+      splittedConfigs.add(jobConfig);
+      return splittedConfigs;
+    }
+    String partitioner = cluster.getMetadata().getPartitioner();
+    if( partitioner.endsWith("RandomPartitioner")) {
+      BigDecimal minToken = BigDecimal.valueOf(-1);
+      BigDecimal maxToken = new BigDecimal(new BigInteger("2").pow(127));
+      BigDecimal step = maxToken.subtract(minToken)
+          .divide(BigDecimal.valueOf(adviceNumber),2, BigDecimal.ROUND_HALF_EVEN);
+      for ( int i = 0; i < adviceNumber; i++ ) {
+        BigInteger l = minToken.add(step.multiply(BigDecimal.valueOf(i))).toBigInteger();
+        BigInteger r = minToken.add(step.multiply(BigDecimal.valueOf(i+1))).toBigInteger();
+        if( i == adviceNumber - 1 ) {
+          r = maxToken.toBigInteger();
+        }
+        Configuration taskConfig = jobConfig.clone();
+        taskConfig.set(Key.MIN_TOKEN,l.toString());
+        taskConfig.set(Key.MAX_TOKEN,r.toString());
+        splittedConfigs.add(taskConfig);
+      }
+    }
+    else if( partitioner.endsWith("Murmur3Partitioner") ) {
+      BigDecimal minToken = BigDecimal.valueOf(Long.MIN_VALUE);
+      BigDecimal maxToken = BigDecimal.valueOf(Long.MAX_VALUE);
+      BigDecimal step = maxToken.subtract(minToken)
+          .divide(BigDecimal.valueOf(adviceNumber),2, BigDecimal.ROUND_HALF_EVEN);
+      for ( int i = 0; i < adviceNumber; i++ ) {
+        long l = minToken.add(step.multiply(BigDecimal.valueOf(i))).longValue();
+        long r = minToken.add(step.multiply(BigDecimal.valueOf(i+1))).longValue();
+        if( i == adviceNumber - 1 ) {
+          r = maxToken.longValue();
+        }
+        Configuration taskConfig = jobConfig.clone();
+        taskConfig.set(Key.MIN_TOKEN,String.valueOf(l));
+        taskConfig.set(Key.MAX_TOKEN,String.valueOf(r));
+        splittedConfigs.add(taskConfig);
+      }
+    }
+    else {
+      splittedConfigs.add(jobConfig);
+    }
+    return splittedConfigs;
+  }
+
+  public static String getQueryString(Configuration taskConfig,Cluster cluster) {
+    List<String> columnMeta = taskConfig.getList(Key.COLUMN,String.class);
+    String keyspace = taskConfig.getString(Key.KEYSPACE);
+    String table = taskConfig.getString(Key.TABLE);
+
+    StringBuilder columns = new StringBuilder();
+    for( String column : columnMeta ) {
+      if(columns.length() > 0 ) {
+        columns.append(",");
+      }
+      columns.append(column);
+    }
+
+    StringBuilder where = new StringBuilder();
+    String whereString = taskConfig.getString(Key.WHERE);
+    if( whereString != null && !whereString.isEmpty() ) {
+      where.append(whereString);
+    }
+    String minToken = taskConfig.getString(Key.MIN_TOKEN);
+    String maxToken = taskConfig.getString(Key.MAX_TOKEN);
+    if( minToken !=null || maxToken !=null ) {
+      LOG.info("range:" + minToken + "~" + maxToken);
+      List<ColumnMetadata> pks = cluster.getMetadata().getKeyspace(keyspace).getTable(table).getPartitionKey();
+      StringBuilder sb = new StringBuilder();
+      for( ColumnMetadata pk : pks ) {
+        if( sb.length() > 0 ) {
+          sb.append(",");
+        }
+        sb.append(pk.getName());
+      }
+      String s = sb.toString();
+      if (minToken != null && !minToken.isEmpty()) {
+        if( where.length() > 0 ){
+          where.append(" AND ");
+        }
+        where.append("token(").append(s).append(")").append(" > ").append(minToken);
+      }
+      if (maxToken != null && !maxToken.isEmpty()) {
+        if( where.length() > 0 ){
+          where.append(" AND ");
+        }
+        where.append("token(").append(s).append(")").append(" <= ").append(maxToken);
+      }
+    }
+
+    boolean allowFiltering = taskConfig.getBool(Key.ALLOW_FILTERING,false);
+
+    StringBuilder select = new StringBuilder();
+    select.append("SELECT ").append(columns.toString()).append(" FROM ").append(table);
+    if( where.length() > 0 ){
+      select.append(" where ").append(where.toString());
+    }
+    if( allowFiltering ) {
+      select.append(" ALLOW FILTERING");
+    }
+    select.append(";");
+    return select.toString();
+  }
+
+  public static void checkConfig(Configuration jobConfig,Cluster cluster) {
+    ensureStringExists(jobConfig,Key.HOST);
+    ensureStringExists(jobConfig,Key.KEYSPACE);
+    ensureStringExists(jobConfig,Key.TABLE);
+    ensureExists(jobConfig,Key.COLUMN);
+
+    ///keyspace,table是否存在
+    String keyspace = jobConfig.getString(Key.KEYSPACE);
+    if( cluster.getMetadata().getKeyspace(keyspace) == null ) {
+      throw DataXException
+          .asDataXException(
+              CassandraReaderErrorCode.CONF_ERROR,
+              String.format(
+                  "配置信息有错误.keyspace'%s'不存在 .",
+                  keyspace));
+    }
+    String table = jobConfig.getString(Key.TABLE);
+    TableMetadata tableMetadata = cluster.getMetadata().getKeyspace(keyspace).getTable(table);
+    if( tableMetadata == null ) {
+      throw DataXException
+          .asDataXException(
+              CassandraReaderErrorCode.CONF_ERROR,
+              String.format(
+                  "配置信息有错误.表'%s'不存在 .",
+                  table));
+    }
+    List<String> columns = jobConfig.getList(Key.COLUMN,String.class);
+    for( String name : columns ) {
+      if( name == null || name.isEmpty() ) {
+        throw DataXException
+            .asDataXException(
+                CassandraReaderErrorCode.CONF_ERROR,
+                String.format(
+                    "配置信息有错误.列信息中需要包含'%s'字段 .",Key.COLUMN_NAME));
+      }
+    }
+  }
+
+  static void ensureExists(Configuration jobConfig,String keyword) {
+    if( jobConfig.get(keyword) == null ) {
+      throw DataXException
+          .asDataXException(
+              CassandraReaderErrorCode.CONF_ERROR,
+              String.format(
+                  "配置信息有错误.参数'%s'为必填项 .",
+                  keyword));
+    }
+  }
+
+  static void ensureStringExists(Configuration jobConfig,String keyword) {
+    ensureExists(jobConfig,keyword);
+    if( jobConfig.getString(keyword).isEmpty() ) {
+      throw DataXException
+          .asDataXException(
+              CassandraReaderErrorCode.CONF_ERROR,
+              String.format(
+                  "配置信息有错误.参数'%s'不能为空 .",
+                  keyword));
+    }
+  }
+
+}

+ 39 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/Key.java

@@ -0,0 +1,39 @@
+package com.alibaba.datax.plugin.reader.cassandrareader;
+
+/**
+ * Created by mazhenlin on 2019/8/19.
+ */
+public class Key {
+  public final static String USERNAME = "username";
+  public final static String PASSWORD = "password";
+
+  public final static String HOST = "host";
+  public final static String PORT = "port";
+  public final static String USESSL = "useSSL";
+
+  public final static String KEYSPACE = "keyspace";
+  public final static String TABLE = "table";
+  public final static String COLUMN = "column";
+  public final static String WHERE = "where";
+  public final static String ALLOW_FILTERING = "allowFiltering";
+  public final static String CONSITANCY_LEVEL = "consistancyLevel";
+  public final static String MIN_TOKEN = "minToken";
+  public final static String MAX_TOKEN = "maxToken";
+
+  /**
+   * 每个列的名字
+   */
+  public static final String COLUMN_NAME = "name";
+  /**
+   * 列分隔符
+   */
+  public static final String COLUMN_SPLITTER = "format";
+  public static final String WRITE_TIME = "writetime(";
+  public static final String ELEMENT_SPLITTER = "splitter";
+  public static final String ENTRY_SPLITTER = "entrySplitter";
+  public static final String KV_SPLITTER = "kvSplitter";
+  public static final String ELEMENT_CONFIG = "element";
+  public static final String TUPLE_CONNECTOR = "_";
+  public static final String KEY_CONFIG = "key";
+  public static final String VALUE_CONFIG = "value";
+}

+ 1 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings.properties

@@ -0,0 +1 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF

+ 0 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_en_US.properties


+ 1 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_ja_JP.properties

@@ -0,0 +1 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF

+ 1 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_zh_CN.properties

@@ -0,0 +1 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF

+ 1 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_zh_HK.properties

@@ -0,0 +1 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF

+ 1 - 0
cassandrareader/src/main/java/com/alibaba/datax/plugin/reader/cassandrareader/LocalStrings_zh_TW.properties

@@ -0,0 +1 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF

+ 6 - 0
cassandrareader/src/main/resources/plugin.json

@@ -0,0 +1,6 @@
+{
+    "name": "cassandrareader",
+    "class": "com.alibaba.datax.plugin.reader.cassandrareader.CassandraReader",
+    "description": "useScene: prod. mechanism: execute select cql, retrieve data from the ResultSet. warn: The more you know about the database, the less problems you encounter.",
+    "developer": "alibaba"
+}

+ 15 - 0
cassandrareader/src/main/resources/plugin_job_template.json

@@ -0,0 +1,15 @@
+{
+    "name": "cassandrareader",
+    "parameter": {
+        "username": "",
+        "password": "",
+        "host": "",
+        "port": "",
+        "useSSL": false,
+        "keyspace": "",
+        "table": "",
+        "column": [
+            "c1","c2","c3"
+        ]
+    }
+}

+ 227 - 0
cassandrawriter/doc/cassandrawriter.md

@@ -0,0 +1,227 @@
+
+# CassandraWriter 插件文档
+
+
+___
+
+
+
+## 1 快速介绍
+
+CassandraWriter插件实现了向Cassandra写入数据。在底层实现上,CassandraWriter通过datastax的java driver连接Cassandra实例,并执行相应的cql语句将数据写入cassandra中。
+
+
+## 2 实现原理
+
+简而言之,CassandraWriter通过java driver连接到Cassandra实例,并根据用户配置的信息生成INSERT CQL语句,然后发送到Cassandra。
+
+对于用户配置Table、Column的信息,CassandraReader将其拼接为CQL语句发送到Cassandra。
+
+
+## 3 功能说明
+
+### 3.1 配置样例
+
+* 配置一个从内存产生到Cassandra导入的作业:
+
+```
+{
+  "job": {
+    "setting": {
+      "speed": {
+        "channel": 5
+      }
+    },
+    "content": [
+      {
+        "reader": {
+          "name": "streamreader",
+          "parameter": {
+            "column": [
+              {"value":"name","type": "string"},
+              {"value":"false","type":"bool"},
+              {"value":"1988-08-08 08:08:08","type":"date"},
+              {"value":"addr","type":"bytes"},
+              {"value":1.234,"type":"double"},
+              {"value":12345678,"type":"long"},
+              {"value":2.345,"type":"double"},
+              {"value":3456789,"type":"long"},
+              {"value":"4a0ef8c0-4d97-11d0-db82-ebecdb03ffa5","type":"string"},
+              {"value":"value","type":"bytes"},
+              {"value":"-838383838,37377373,-383883838,27272772,393993939,-38383883,83883838,-1350403181,817650816,1630642337,251398784,-622020148","type":"string"},
+            ],
+           "sliceRecordCount": 10000000
+          }
+        },
+        "writer": {
+          "name": "cassandrawriter",
+          "parameter": {
+            "host": "localhost",
+            "port": 9042,
+            "useSSL": false,
+            "keyspace": "stresscql",
+            "table": "dst",
+            "batchSize":10,
+            "column": [
+              "name",
+              "choice",
+              "date",
+              "address",
+              "dbl",
+              "lval",
+              "fval",
+              "ival",
+              "uid",
+              "value",
+              "listval"
+            ]
+          }
+        }
+      }
+    ]
+  }
+}
+```
+
+
+### 3.2 参数说明
+
+* **host**
+
+	* 描述:Cassandra连接点的域名或ip,多个node之间用逗号分隔。 <br />
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **port**
+
+	* 描述:Cassandra端口。 <br />
+
+	* 必选:是 <br />
+
+	* 默认值:9042 <br />
+
+* **username**
+
+	* 描述:数据源的用户名 <br />
+
+	* 必选:否 <br />
+
+	* 默认值:无 <br />
+
+* **password**
+
+	* 描述:数据源指定用户名的密码 <br />
+
+	* 必选:否 <br />
+
+	* 默认值:无 <br />
+
+* **useSSL**
+
+	* 描述:是否使用SSL连接。<br />
+
+	* 必选:否 <br />
+
+	* 默认值:false <br />
+
+* **connectionsPerHost**
+
+	* 描述:客户端连接池配置:与服务器每个节点建多少个连接。<br />
+
+	* 必选:否 <br />
+
+	* 默认值:8 <br />
+
+* **maxPendingPerConnection**
+
+	* 描述:客户端连接池配置:每个连接最大请求数。<br />
+
+	* 必选:否 <br />
+
+	* 默认值:128 <br />
+
+* **keyspace**
+
+	* 描述:需要同步的表所在的keyspace。<br />
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **table**
+
+	* 描述:所选取的需要同步的表。<br />
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+* **column**
+
+	* 描述:所配置的表中需要同步的列集合。<br />
+	  内容可以是列的名称或"writetime()"。如果将列名配置为writetime(),会将这一列的内容作为时间戳。
+
+	* 必选:是 <br />
+
+	* 默认值:无 <br />
+
+
+* **consistancyLevel**
+
+	* 描述:数据一致性级别。可选ONE|QUORUM|LOCAL_QUORUM|EACH_QUORUM|ALL|ANY|TWO|THREE|LOCAL_ONE<br />
+
+	* 必选:否 <br />
+
+	* 默认值:LOCAL_QUORUM <br />
+
+* **batchSize**
+
+	* 描述:一次批量提交(UNLOGGED BATCH)的记录数大小(条数)。注意batch的大小有如下限制:<br />
+	  (1)不能超过65535。<br />
+	   (2) batch中的内容大小受到服务器端batch_size_fail_threshold_in_kb的限制。<br />
+	   (3) 如果batch中的内容超过了batch_size_warn_threshold_in_kb的限制,会打出warn日志,但并不影响写入,忽略即可。<br />
+	   如果批量提交失败,会把这个批量的所有内容重新逐条写入一遍。
+
+	* 必选:否 <br />
+
+	* 默认值:1 <br />
+
+
+### 3.3 类型转换
+
+目前CassandraReader支持除counter和Custom类型之外的所有类型。
+
+下面列出CassandraReader针对Cassandra类型转换列表:
+
+
+| DataX 内部类型| Cassandra 数据类型    |
+| -------- | -----  |
+| Long     |int, tinyint, smallint,varint,bigint,time|
+| Double   |float, double, decimal|
+| String   |ascii,varchar, text,uuid,timeuuid,duration,list,map,set,tuple,udt,inet   |
+| Date     |date, timestamp   |
+| Boolean  |bool   |
+| Bytes    |blob    |
+
+
+
+请注意:
+
+* 目前不支持counter类型和custom类型。
+
+## 4 性能报告
+
+略
+
+## 5 约束限制
+
+### 5.1 主备同步数据恢复问题
+
+略
+
+## 6 FAQ
+
+
+

+ 125 - 0
cassandrawriter/pom.xml

@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>datax-all</artifactId>
+        <groupId>com.alibaba.datax</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>cassandrawriter</artifactId>
+    <name>cassandrawriter</name>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <properties>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-common</artifactId>
+            <version>${datax-project-version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>slf4j-log4j12</artifactId>
+                    <groupId>org.slf4j</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.datastax.cassandra</groupId>
+            <artifactId>cassandra-driver-core</artifactId>
+            <version>3.7.2</version>
+        </dependency>
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+			<version>1.9</version>
+		</dependency>
+		
+        <!-- for test -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-core</artifactId>
+            <version>${datax-project-version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.alibaba.datax</groupId>
+                    <artifactId>datax-service-face</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.hadoop</groupId>
+                    <artifactId>hadoop-common</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.hive</groupId>
+                    <artifactId>hive-exec</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.hive</groupId>
+                    <artifactId>hive-serde</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javolution</groupId>
+                    <artifactId>javolution</artifactId>
+                </exclusion>
+            </exclusions>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.properties</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <!-- compiler plugin -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${jdk-version}</source>
+                    <target>${jdk-version}</target>
+                    <encoding>${project-sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/package.xml</descriptor>
+                    </descriptors>
+                    <finalName>datax</finalName>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>dwzip</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 35 - 0
cassandrawriter/src/main/assembly/package.xml

@@ -0,0 +1,35 @@
+<assembly
+	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+	<id></id>
+	<formats>
+		<format>dir</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+	<fileSets>
+		<fileSet>
+			<directory>src/main/resources</directory>
+			<includes>
+				<include>plugin.json</include>
+				<include>plugin_job_template.json</include>
+			</includes>
+			<outputDirectory>plugin/writer/cassandrawriter</outputDirectory>
+		</fileSet>
+		<fileSet>
+			<directory>target/</directory>
+			<includes>
+				<include>cassandrawriter-0.0.1-SNAPSHOT.jar</include>
+			</includes>
+			<outputDirectory>plugin/writer/cassandrawriter</outputDirectory>
+		</fileSet>
+	</fileSets>
+
+	<dependencySets>
+		<dependencySet>
+			<useProjectArtifact>false</useProjectArtifact>
+			<outputDirectory>plugin/writer/cassandrawriter/libs</outputDirectory>
+			<scope>runtime</scope>
+		</dependencySet>
+	</dependencySets>
+</assembly>

+ 242 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/CassandraWriter.java

@@ -0,0 +1,242 @@
+package com.alibaba.datax.plugin.writer.cassandrawriter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import com.alibaba.datax.common.element.Column;
+import com.alibaba.datax.common.element.Record;
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.plugin.RecordReceiver;
+import com.alibaba.datax.common.spi.Writer;
+import com.alibaba.datax.common.util.Configuration;
+
+import com.datastax.driver.core.BatchStatement;
+import com.datastax.driver.core.BatchStatement.Type;
+import com.datastax.driver.core.BoundStatement;
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.ColumnMetadata;
+import com.datastax.driver.core.ConsistencyLevel;
+import com.datastax.driver.core.DataType;
+import com.datastax.driver.core.HostDistance;
+import com.datastax.driver.core.PoolingOptions;
+import com.datastax.driver.core.PreparedStatement;
+import com.datastax.driver.core.ResultSetFuture;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.TableMetadata;
+import com.datastax.driver.core.querybuilder.Insert;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.timestamp;
+
+/**
+ * Created by mazhenlin on 2019/8/19.
+ */
+public class CassandraWriter extends Writer {
+  private static final Logger LOG = LoggerFactory
+      .getLogger(CassandraWriter.class);
+  public static class Job extends Writer.Job {
+    private Configuration originalConfig = null;
+
+    @Override public List<Configuration> split(int mandatoryNumber) {
+      List<Configuration> splitResultConfigs = new ArrayList<Configuration>();
+      for (int j = 0; j < mandatoryNumber; j++) {
+        splitResultConfigs.add(originalConfig.clone());
+      }
+      return splitResultConfigs;
+
+    }
+
+    @Override public void init() {
+      originalConfig = getPluginJobConf();
+    }
+
+    @Override public void destroy() {
+
+    }
+  }
+
+  public static class Task extends Writer.Task {
+    private Configuration taskConfig;
+    private Cluster cluster = null;
+    private Session session = null;
+    private PreparedStatement statement = null;
+    private int columnNumber = 0;
+    private List<DataType> columnTypes;
+    private List<String> columnMeta = null;
+    private int writeTimeCol = -1;
+    private boolean asyncWrite = false;
+    private long batchSize = 1;
+    private List<ResultSetFuture> unConfirmedWrite;
+    private List<BoundStatement> bufferedWrite;
+
+    @Override public void startWrite(RecordReceiver lineReceiver) {
+      try {
+        Record record;
+        while ((record = lineReceiver.getFromReader()) != null) {
+          if (record.getColumnNumber() != columnNumber) {
+            // 源头读取字段列数与目的表字段写入列数不相等,直接报错
+            throw DataXException
+                .asDataXException(
+                    CassandraWriterErrorCode.CONF_ERROR,
+                    String.format(
+                        "列配置信息有错误. 因为您配置的任务中,源头读取字段数:%s 与 目的表要写入的字段数:%s 不相等. 请检查您的配置并作出修改.",
+                        record.getColumnNumber(),
+                        this.columnNumber));
+          }
+
+          BoundStatement boundStmt = statement.bind();
+          for (int i = 0; i < columnNumber; i++) {
+            if( writeTimeCol != -1 && i == writeTimeCol ) {
+              continue;
+            }
+            Column col = record.getColumn(i);
+            int pos = i;
+            if( writeTimeCol != -1 && pos > writeTimeCol ) {
+              pos = i - 1;
+            }
+            CassandraWriterHelper.setupColumn(boundStmt,pos,columnTypes.get(pos),col);
+          }
+          if(writeTimeCol != -1) {
+            Column col = record.getColumn(writeTimeCol );
+            boundStmt.setLong(columnNumber - 1,col.asLong());
+          }
+          if( batchSize <= 1 ) {
+            session.execute(boundStmt);
+          } else {
+            if( asyncWrite ) {
+              unConfirmedWrite.add(session.executeAsync(boundStmt));
+              if (unConfirmedWrite.size() >= batchSize) {
+                for (ResultSetFuture write : unConfirmedWrite) {
+                  write.getUninterruptibly(10000, TimeUnit.MILLISECONDS);
+                }
+                unConfirmedWrite.clear();
+              }
+            } else {
+              bufferedWrite.add(boundStmt);
+              if( bufferedWrite.size() >= batchSize ) {
+                BatchStatement batchStatement = new BatchStatement(Type.UNLOGGED);
+                batchStatement.addAll(bufferedWrite);
+                try {
+                  session.execute(batchStatement);
+                } catch (Exception e ) {
+                  LOG.error("batch写入失败,尝试逐条写入.",e);
+                  for( BoundStatement stmt: bufferedWrite ) {
+                    session.execute(stmt);
+                  }
+                }
+                ///LOG.info("batch finished. size = " + bufferedWrite.size());
+                bufferedWrite.clear();
+              }
+            }
+          }
+
+        }
+        if( unConfirmedWrite != null && unConfirmedWrite.size() > 0 ) {
+          for( ResultSetFuture write : unConfirmedWrite ) {
+            write.getUninterruptibly(10000, TimeUnit.MILLISECONDS);
+          }
+          unConfirmedWrite.clear();
+        }
+        if( bufferedWrite !=null && bufferedWrite.size() > 0 ) {
+          BatchStatement batchStatement = new BatchStatement(Type.UNLOGGED);
+          batchStatement.addAll(bufferedWrite);
+          session.execute(batchStatement);
+          bufferedWrite.clear();
+        }
+      } catch (Exception e) {
+        throw DataXException.asDataXException(
+            CassandraWriterErrorCode.WRITE_DATA_ERROR, e);
+      }
+    }
+
+
+    @Override public void init() {
+      this.taskConfig = super.getPluginJobConf();
+      String username = taskConfig.getString(Key.USERNAME);
+      String password = taskConfig.getString(Key.PASSWORD);
+      String hosts = taskConfig.getString(Key.HOST);
+      Integer port = taskConfig.getInt(Key.PORT,9042);
+      boolean useSSL = taskConfig.getBool(Key.USESSL);
+      String keyspace = taskConfig.getString(Key.KEYSPACE);
+      String table = taskConfig.getString(Key.TABLE);
+      batchSize = taskConfig.getLong(Key.BATCH_SIZE,1);
+      this.columnMeta = taskConfig.getList(Key.COLUMN,String.class);
+      columnTypes = new ArrayList<DataType>(columnMeta.size());
+      columnNumber = columnMeta.size();
+      asyncWrite = taskConfig.getBool(Key.ASYNC_WRITE,false);
+
+      int connectionsPerHost = taskConfig.getInt(Key.CONNECTIONS_PER_HOST,8);
+      int maxPendingPerConnection = taskConfig.getInt(Key.MAX_PENDING_CONNECTION,128);
+      PoolingOptions poolingOpts = new PoolingOptions()
+          .setConnectionsPerHost(HostDistance.LOCAL, connectionsPerHost, connectionsPerHost)
+          .setMaxRequestsPerConnection(HostDistance.LOCAL, maxPendingPerConnection)
+          .setNewConnectionThreshold(HostDistance.LOCAL, 100);
+      Cluster.Builder clusterBuilder = Cluster.builder().withPoolingOptions(poolingOpts);
+      if ((username != null) && !username.isEmpty()) {
+        clusterBuilder = clusterBuilder.withCredentials(username, password)
+            .withPort(Integer.valueOf(port)).addContactPoints(hosts.split(","));
+        if (useSSL) {
+          clusterBuilder = clusterBuilder.withSSL();
+        }
+      } else {
+        clusterBuilder = clusterBuilder.withPort(Integer.valueOf(port))
+            .addContactPoints(hosts.split(","));
+      }
+      cluster = clusterBuilder.build();
+      session = cluster.connect(keyspace);
+      TableMetadata meta = cluster.getMetadata().getKeyspace(keyspace).getTable(table);
+
+      Insert insertStmt = QueryBuilder.insertInto(table);
+      for( String colunmnName : columnMeta ) {
+        if( colunmnName.toLowerCase().equals(Key.WRITE_TIME) ) {
+          if( writeTimeCol != -1 ) {
+            throw DataXException
+                .asDataXException(
+                    CassandraWriterErrorCode.CONF_ERROR,
+                    "列配置信息有错误. 只能有一个时间戳列(writetime())");
+          }
+          writeTimeCol = columnTypes.size();
+          continue;
+        }
+        insertStmt.value(colunmnName,QueryBuilder.bindMarker());
+        ColumnMetadata col = meta.getColumn(colunmnName);
+        if( col == null ) {
+          throw DataXException
+              .asDataXException(
+                  CassandraWriterErrorCode.CONF_ERROR,
+                  String.format(
+                      "列配置信息有错误. 表中未找到列名 '%s' .",
+                      colunmnName));
+        }
+        columnTypes.add(col.getType());
+      }
+      if(writeTimeCol != -1) {
+        insertStmt.using(timestamp(QueryBuilder.bindMarker()));
+      }
+      String cl = taskConfig.getString(Key.CONSITANCY_LEVEL);
+      if( cl != null && !cl.isEmpty() ) {
+        insertStmt.setConsistencyLevel(ConsistencyLevel.valueOf(cl));
+      } else {
+        insertStmt.setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM);
+      }
+
+      statement = session.prepare(insertStmt);
+
+      if( batchSize > 1 ) {
+        if( asyncWrite ) {
+          unConfirmedWrite = new ArrayList<ResultSetFuture>();
+        } else {
+          bufferedWrite = new ArrayList<BoundStatement>();
+        }
+      }
+
+    }
+
+    @Override public void destroy() {
+
+    }
+  }
+}

+ 35 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/CassandraWriterErrorCode.java

@@ -0,0 +1,35 @@
+package com.alibaba.datax.plugin.writer.cassandrawriter;
+
+import com.alibaba.datax.common.spi.ErrorCode;
+
+/**
+ * Created by mazhenlin on 2019/8/19.
+ */
+public enum CassandraWriterErrorCode implements ErrorCode {
+  CONF_ERROR("CassandraWriter-00", "配置错误."),
+  WRITE_DATA_ERROR("CassandraWriter-01", "写入数据时失败."),
+  ;
+
+  private final String code;
+  private final String description;
+
+  private CassandraWriterErrorCode(String code, String description) {
+    this.code = code;
+    this.description = description;
+  }
+
+  @Override
+  public String getCode() {
+    return this.code;
+  }
+
+  @Override
+  public String getDescription() {
+    return this.description;
+  }
+
+  @Override
+  public String toString() {
+    return String.format("Code:[%s], Description:[%s].", this.code, this.description);
+  }
+}

+ 351 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/CassandraWriterHelper.java

@@ -0,0 +1,351 @@
+package com.alibaba.datax.plugin.writer.cassandrawriter;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import com.alibaba.datax.common.element.Column;
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONException;
+import com.alibaba.fastjson2.JSONObject;
+
+import com.datastax.driver.core.BoundStatement;
+import com.datastax.driver.core.CodecRegistry;
+import com.datastax.driver.core.DataType;
+import com.datastax.driver.core.DataType.Name;
+import com.datastax.driver.core.Duration;
+import com.datastax.driver.core.LocalDate;
+import com.datastax.driver.core.TupleType;
+import com.datastax.driver.core.TupleValue;
+import com.datastax.driver.core.UDTValue;
+import com.datastax.driver.core.UserType;
+import com.datastax.driver.core.UserType.Field;
+import com.google.common.base.Splitter;
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * Created by mazhenlin on 2019/8/21.
+ */
+public class CassandraWriterHelper {
+  static CodecRegistry registry = new CodecRegistry();
+
+  public static Object parseFromString(String s, DataType sqlType ) throws Exception {
+    if (s == null || s.isEmpty()) {
+      if (sqlType.getName() == Name.ASCII || sqlType.getName() == Name.TEXT ||
+          sqlType.getName() == Name.VARCHAR) {
+        return s;
+      } else {
+        return null;
+      }
+    }
+    switch (sqlType.getName()) {
+    case ASCII:
+    case TEXT:
+    case VARCHAR:
+      return s;
+
+    case BLOB:
+      if (s.length() == 0) {
+        return new byte[0];
+      }
+      byte[] byteArray = new byte[s.length() / 2];
+      for (int i = 0; i < byteArray.length; i++) {
+        String subStr = s.substring(2 * i, 2 * i + 2);
+        byteArray[i] = ((byte) Integer.parseInt(subStr, 16));
+      }
+      return ByteBuffer.wrap(byteArray);
+
+    case BOOLEAN:
+      return Boolean.valueOf(s);
+
+    case TINYINT:
+      return Byte.valueOf(s);
+
+    case SMALLINT:
+      return Short.valueOf(s);
+
+    case INT:
+      return Integer.valueOf(s);
+
+    case BIGINT:
+      return Long.valueOf(s);
+
+    case VARINT:
+      return new BigInteger(s, 10);
+
+    case FLOAT:
+      return Float.valueOf(s);
+
+    case DOUBLE:
+      return Double.valueOf(s);
+
+    case DECIMAL:
+      return new BigDecimal(s);
+
+    case DATE: {
+      String[] a = s.split("-");
+      if (a.length != 3) {
+        throw new Exception(String.format("DATE类型数据 '%s' 格式不正确,必须为yyyy-mm-dd格式", s));
+      }
+      return LocalDate.fromYearMonthDay(Integer.valueOf(a[0]), Integer.valueOf(a[1]),
+          Integer.valueOf(a[2]));
+    }
+
+    case TIME:
+      return Long.valueOf(s);
+
+    case TIMESTAMP:
+      return new Date(Long.valueOf(s));
+
+    case UUID:
+    case TIMEUUID:
+      return UUID.fromString(s);
+
+    case INET:
+      String[] b = s.split("/");
+      if (b.length < 2) {
+        return InetAddress.getByName(s);
+      }
+      byte[] addr = InetAddress.getByName(b[1]).getAddress();
+      return InetAddress.getByAddress(b[0], addr);
+
+    case DURATION:
+      return Duration.from(s);
+
+    case LIST:
+    case MAP:
+    case SET:
+    case TUPLE:
+    case UDT:
+      Object jsonObject = JSON.parse(s);
+      return parseFromJson(jsonObject,sqlType);
+
+    default:
+      throw DataXException.asDataXException(CassandraWriterErrorCode.CONF_ERROR,
+          "不支持您配置的列类型:" + sqlType + ", 请检查您的配置 或者 联系 管理员.");
+
+    } // end switch
+
+  }
+
+  public static Object parseFromJson(Object jsonObject,DataType type) throws Exception {
+    if( jsonObject == null ) return null;
+    switch (type.getName()) {
+    case ASCII:
+    case TEXT:
+    case VARCHAR:
+    case BOOLEAN:
+    case TIME:
+      return jsonObject;
+
+    case TINYINT:
+      return ((Number)jsonObject).byteValue();
+
+    case SMALLINT:
+      return ((Number)jsonObject).shortValue();
+
+    case INT:
+      return ((Number)jsonObject).intValue();
+
+    case BIGINT:
+      return ((Number)jsonObject).longValue();
+
+    case VARINT:
+      return new BigInteger(jsonObject.toString());
+
+    case FLOAT:
+      return ((Number)jsonObject).floatValue();
+
+    case DOUBLE:
+      return ((Number)jsonObject).doubleValue();
+
+    case DECIMAL:
+      return new BigDecimal(jsonObject.toString());
+
+    case BLOB:
+      return ByteBuffer.wrap(Base64.decodeBase64((String)jsonObject));
+
+    case DATE:
+      return LocalDate.fromMillisSinceEpoch(((Number)jsonObject).longValue());
+
+    case TIMESTAMP:
+      return new Date(((Number)jsonObject).longValue());
+
+    case DURATION:
+      return Duration.from(jsonObject.toString());
+
+    case UUID:
+    case TIMEUUID:
+      return UUID.fromString(jsonObject.toString());
+
+    case INET:
+      return InetAddress.getByName((String)jsonObject);
+
+    case LIST:
+      List l = new ArrayList();
+      for( Object o : (JSONArray)jsonObject ) {
+        l.add(parseFromJson(o,type.getTypeArguments().get(0)));
+      }
+      return l;
+
+    case MAP: {
+      Map m = new HashMap();
+      for (Map.Entry e : ((JSONObject)jsonObject).entrySet()) {
+        Object k = parseFromString((String) e.getKey(), type.getTypeArguments().get(0));
+        Object v = parseFromJson(e.getValue(), type.getTypeArguments().get(1));
+        m.put(k,v);
+      }
+      return m;
+    }
+
+    case SET:
+      Set s = new HashSet();
+      for( Object o : (JSONArray)jsonObject ) {
+        s.add(parseFromJson(o,type.getTypeArguments().get(0)));
+      }
+      return s;
+
+    case TUPLE: {
+      TupleValue t = ((TupleType) type).newValue();
+      int j = 0;
+      for (Object e : (JSONArray)jsonObject) {
+        DataType eleType = ((TupleType) type).getComponentTypes().get(j);
+        t.set(j, parseFromJson(e, eleType), registry.codecFor(eleType).getJavaType());
+        j++;
+      }
+      return t;
+    }
+
+    case UDT: {
+      UDTValue t = ((UserType) type).newValue();
+      UserType userType = t.getType();
+      for (Map.Entry e : ((JSONObject)jsonObject).entrySet()) {
+        DataType eleType = userType.getFieldType((String)e.getKey());
+        t.set((String)e.getKey(), parseFromJson(e.getValue(), eleType), registry.codecFor(eleType).getJavaType());
+      }
+      return t;
+    }
+
+    }
+    return null;
+  }
+
+  public static void setupColumn(BoundStatement ps, int pos, DataType sqlType, Column col) throws Exception {
+    if (col.getRawData() != null) {
+      switch (sqlType.getName()) {
+      case ASCII:
+      case TEXT:
+      case VARCHAR:
+        ps.setString(pos, col.asString());
+        break;
+
+      case BLOB:
+        ps.setBytes(pos, ByteBuffer.wrap(col.asBytes()));
+        break;
+
+      case BOOLEAN:
+        ps.setBool(pos, col.asBoolean());
+        break;
+
+      case TINYINT:
+        ps.setByte(pos, col.asLong().byteValue());
+        break;
+
+      case SMALLINT:
+        ps.setShort(pos, col.asLong().shortValue());
+        break;
+
+      case INT:
+        ps.setInt(pos, col.asLong().intValue());
+        break;
+
+      case BIGINT:
+        ps.setLong(pos, col.asLong());
+        break;
+
+      case VARINT:
+        ps.setVarint(pos, col.asBigInteger());
+        break;
+
+      case FLOAT:
+        ps.setFloat(pos, col.asDouble().floatValue());
+        break;
+
+      case DOUBLE:
+        ps.setDouble(pos, col.asDouble());
+        break;
+
+      case DECIMAL:
+        ps.setDecimal(pos, col.asBigDecimal());
+        break;
+
+      case DATE:
+        ps.setDate(pos, LocalDate.fromMillisSinceEpoch(col.asDate().getTime()));
+        break;
+
+      case TIME:
+        ps.setTime(pos, col.asLong());
+        break;
+
+      case TIMESTAMP:
+        ps.setTimestamp(pos, col.asDate());
+        break;
+
+      case UUID:
+      case TIMEUUID:
+        ps.setUUID(pos, UUID.fromString(col.asString()));
+        break;
+
+      case INET:
+        ps.setInet(pos, InetAddress.getByName(col.asString()));
+        break;
+
+      case DURATION:
+        ps.set(pos, Duration.from(col.asString()), Duration.class);
+        break;
+
+      case LIST:
+        ps.setList(pos, (List<?>) parseFromString(col.asString(), sqlType));
+        break;
+
+      case MAP:
+        ps.setMap(pos, (Map) parseFromString(col.asString(), sqlType));
+        break;
+
+      case SET:
+        ps.setSet(pos, (Set) parseFromString(col.asString(), sqlType));
+        break;
+
+      case TUPLE:
+        ps.setTupleValue(pos, (TupleValue) parseFromString(col.asString(), sqlType));
+        break;
+
+      case UDT:
+        ps.setUDTValue(pos, (UDTValue) parseFromString(col.asString(), sqlType));
+        break;
+
+      default:
+        throw DataXException.asDataXException(CassandraWriterErrorCode.CONF_ERROR,
+            "不支持您配置的列类型:" + sqlType + ", 请检查您的配置 或者 联系 管理员.");
+
+      } // end switch
+    } else {
+      ps.setToNull(pos);
+    }
+  }
+
+}

+ 43 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/Key.java

@@ -0,0 +1,43 @@
+package com.alibaba.datax.plugin.writer.cassandrawriter;
+
+/**
+ * Created by mazhenlin on 2019/8/19.
+ */
+public class Key {
+  public final static String USERNAME = "username";
+  public final static String PASSWORD = "password";
+
+  public final static String HOST = "host";
+  public final static String PORT = "port";
+  public final static String USESSL = "useSSL";
+
+  public final static String KEYSPACE = "keyspace";
+  public final static String TABLE = "table";
+  public final static String COLUMN = "column";
+  public final static String WRITE_TIME = "writetime()";
+  public final static String ASYNC_WRITE = "asyncWrite";
+  public final static String CONSITANCY_LEVEL = "consistancyLevel";
+  public final static String CONNECTIONS_PER_HOST = "connectionsPerHost";
+  public final static String MAX_PENDING_CONNECTION = "maxPendingPerConnection";
+  /**
+   * 异步写入的批次大小,默认1(不异步写入)
+   */
+  public final static String BATCH_SIZE = "batchSize";
+
+  /**
+   * 每个列的名字
+   */
+  public static final String COLUMN_NAME = "name";
+  /**
+   * 列分隔符
+   */
+  public static final String COLUMN_SPLITTER = "format";
+  public static final String ELEMENT_SPLITTER = "splitter";
+  public static final String ENTRY_SPLITTER = "entrySplitter";
+  public static final String KV_SPLITTER = "kvSplitter";
+  public static final String ELEMENT_CONFIG = "element";
+  public static final String TUPLE_CONNECTOR = "_";
+  public static final String KEY_CONFIG = "key";
+  public static final String VALUE_CONFIG = "value";
+
+}

+ 2 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings.properties

@@ -0,0 +1,2 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF.
+errorcode.write_failed_exception=\u5199\u5165\u6570\u636E\u65F6\u5931\u8D25

+ 2 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_en_US.properties

@@ -0,0 +1,2 @@
+errorcode.config_invalid_exception=Error in parameter configuration.
+errorcode.write_failed_exception=\u5199\u5165\u6570\u636E\u65F6\u5931\u8D25

+ 2 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_ja_JP.properties

@@ -0,0 +1,2 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF.
+errorcode.write_failed_exception=\u5199\u5165\u6570\u636E\u65F6\u5931\u8D25

+ 2 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_zh_CN.properties

@@ -0,0 +1,2 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF.
+errorcode.write_failed_exception=\u5199\u5165\u6570\u636E\u65F6\u5931\u8D25

+ 2 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_zh_HK.properties

@@ -0,0 +1,2 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF.
+errorcode.write_failed_exception=\u5199\u5165\u6570\u636E\u65F6\u5931\u8D25

+ 2 - 0
cassandrawriter/src/main/java/com/alibaba/datax/plugin/writer/cassandrawriter/LocalStrings_zh_TW.properties

@@ -0,0 +1,2 @@
+errorcode.config_invalid_exception=\u914D\u7F6E\u9519\u8BEF.
+errorcode.write_failed_exception=\u5199\u5165\u6570\u636E\u65F6\u5931\u8D25

+ 7 - 0
cassandrawriter/src/main/resources/plugin.json

@@ -0,0 +1,7 @@
+{
+    "name": "cassandrawriter",
+    "class": "com.alibaba.datax.plugin.writer.cassandrawriter.CassandraWriter",
+    "description": "useScene: prod. mechanism: use datax driver, execute insert sql.",
+    "developer": "alibaba"
+}
+

+ 15 - 0
cassandrawriter/src/main/resources/plugin_job_template.json

@@ -0,0 +1,15 @@
+{
+    "name": "cassandrawriter",
+    "parameter": {
+        "username": "",
+        "password": "",
+        "host": "",
+        "port": "",
+        "useSSL": false,
+        "keyspace": "",
+        "table": "",
+        "column": [
+            "c1","c2","c3"
+        ]
+    }
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 344 - 0
clickhousereader/doc/clickhousereader.md


+ 91 - 0
clickhousereader/pom.xml

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>datax-all</artifactId>
+        <groupId>com.alibaba.datax</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>clickhousereader</artifactId>
+    <name>clickhousereader</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>ru.yandex.clickhouse</groupId>
+            <artifactId>clickhouse-jdbc</artifactId>
+            <version>0.2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-core</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-common</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>plugin-rdbms-util</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.properties</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <!-- compiler plugin -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${jdk-version}</source>
+                    <target>${jdk-version}</target>
+                    <encoding>${project-sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+            <!-- assembly plugin -->
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/package.xml</descriptor>
+                    </descriptors>
+                    <finalName>datax</finalName>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>dwzip</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+
+</project>

+ 35 - 0
clickhousereader/src/main/assembly/package.xml

@@ -0,0 +1,35 @@
+<assembly
+        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+    <id></id>
+    <formats>
+        <format>dir</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <fileSets>
+        <fileSet>
+            <directory>src/main/resources</directory>
+            <includes>
+                <include>plugin.json</include>
+                <include>plugin_job_template.json</include>
+            </includes>
+            <outputDirectory>plugin/reader/clickhousereader</outputDirectory>
+        </fileSet>
+        <fileSet>
+            <directory>target/</directory>
+            <includes>
+                <include>clickhousereader-0.0.1-SNAPSHOT.jar</include>
+            </includes>
+            <outputDirectory>plugin/reader/clickhousereader</outputDirectory>
+        </fileSet>
+    </fileSets>
+
+    <dependencySets>
+        <dependencySet>
+            <useProjectArtifact>false</useProjectArtifact>
+            <outputDirectory>plugin/reader/clickhousereader/libs</outputDirectory>
+            <scope>runtime</scope>
+        </dependencySet>
+    </dependencySets>
+</assembly>

+ 85 - 0
clickhousereader/src/main/java/com/alibaba/datax/plugin/reader/clickhousereader/ClickhouseReader.java

@@ -0,0 +1,85 @@
+package com.alibaba.datax.plugin.reader.clickhousereader;
+
+import java.sql.Array;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.List;
+
+import com.alibaba.datax.common.element.Record;
+import com.alibaba.datax.common.element.StringColumn;
+import com.alibaba.datax.common.plugin.RecordSender;
+import com.alibaba.datax.common.plugin.TaskPluginCollector;
+import com.alibaba.datax.common.spi.Reader;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.common.util.MessageSource;
+import com.alibaba.datax.plugin.rdbms.reader.CommonRdbmsReader;
+import com.alibaba.datax.plugin.rdbms.util.DataBaseType;
+import com.alibaba.fastjson2.JSON;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClickhouseReader extends Reader {
+
+    private static final DataBaseType DATABASE_TYPE = DataBaseType.ClickHouse;
+    private static final Logger LOG = LoggerFactory.getLogger(ClickhouseReader.class);
+
+    public static class Job extends Reader.Job {
+        private Configuration jobConfig = null;
+        private CommonRdbmsReader.Job commonRdbmsReaderMaster;
+
+        @Override
+        public void init() {
+            this.jobConfig = super.getPluginJobConf();
+            this.commonRdbmsReaderMaster = new CommonRdbmsReader.Job(DATABASE_TYPE);
+            this.commonRdbmsReaderMaster.init(this.jobConfig);
+        }
+
+        @Override
+        public List<Configuration> split(int mandatoryNumber) {
+            return this.commonRdbmsReaderMaster.split(this.jobConfig, mandatoryNumber);
+        }
+
+        @Override
+        public void post() {
+            this.commonRdbmsReaderMaster.post(this.jobConfig);
+        }
+
+        @Override
+        public void destroy() {
+            this.commonRdbmsReaderMaster.destroy(this.jobConfig);
+        }
+    }
+
+    public static class Task extends Reader.Task {
+
+        private Configuration jobConfig;
+        private CommonRdbmsReader.Task commonRdbmsReaderSlave;
+
+        @Override
+        public void init() {
+            this.jobConfig = super.getPluginJobConf();
+            this.commonRdbmsReaderSlave = new CommonRdbmsReader.Task(DATABASE_TYPE, super.getTaskGroupId(), super.getTaskId());
+            this.commonRdbmsReaderSlave.init(this.jobConfig);
+        }
+
+        @Override
+        public void startRead(RecordSender recordSender) {
+            int fetchSize = this.jobConfig.getInt(com.alibaba.datax.plugin.rdbms.reader.Constant.FETCH_SIZE, 1000);
+
+            this.commonRdbmsReaderSlave.startRead(this.jobConfig, recordSender, super.getTaskPluginCollector(), fetchSize);
+        }
+
+        @Override
+        public void post() {
+            this.commonRdbmsReaderSlave.post(this.jobConfig);
+        }
+
+        @Override
+        public void destroy() {
+            this.commonRdbmsReaderSlave.destroy(this.jobConfig);
+        }
+    }
+}

+ 6 - 0
clickhousereader/src/main/resources/plugin.json

@@ -0,0 +1,6 @@
+{
+  "name": "clickhousereader",
+  "class": "com.alibaba.datax.plugin.reader.clickhousereader.ClickhouseReader",
+  "description": "useScene: prod. mechanism: Jdbc connection using the database, execute select sql.",
+  "developer": "alibaba"
+}

+ 16 - 0
clickhousereader/src/main/resources/plugin_job_template.json

@@ -0,0 +1,16 @@
+{
+  "name": "clickhousereader",
+  "parameter": {
+    "username": "username",
+    "password": "password",
+    "column": ["col1", "col2", "col3"],
+    "connection": [
+      {
+        "jdbcUrl": "jdbc:clickhouse://<host>:<port>[/<database>]",
+        "table": ["table1", "table2"]
+      }
+    ],
+    "preSql": [],
+    "postSql": []
+  }
+}

+ 57 - 0
clickhousereader/src/test/resources/basic1.json

@@ -0,0 +1,57 @@
+{
+  "job": {
+    "setting": {
+      "speed": {
+        "channel": 5
+      }
+    },
+    "content": [
+      {
+        "reader": {
+          "name": "clickhousereader",
+          "parameter": {
+            "username": "XXXX",
+            "password": "XXXX",
+            "column": [
+              "uint8_col",
+              "uint16_col",
+              "uint32_col",
+              "uint64_col",
+              "int8_col",
+              "int16_col",
+              "int32_col",
+              "int64_col",
+              "float32_col",
+              "float64_col",
+              "bool_col",
+              "str_col",
+              "fixedstr_col",
+              "uuid_col",
+              "date_col",
+              "datetime_col",
+              "enum_col",
+              "ary_uint8_col",
+              "ary_str_col",
+              "tuple_col",
+              "nullable_col",
+              "nested_col.nested_id",
+              "nested_col.nested_str",
+              "ipv4_col",
+              "ipv6_col",
+              "decimal_col"
+            ],
+            "connection": [
+              {
+                "table": [
+                  "all_type_tbl"
+                ],
+                "jdbcUrl":["jdbc:clickhouse://XXXX:8123/default"]
+              }
+            ]
+          }
+        },
+        "writer": {}
+      }
+    ]
+  }
+}

+ 34 - 0
clickhousereader/src/test/resources/basic1.sql

@@ -0,0 +1,34 @@
+CREATE TABLE IF NOT EXISTS default.all_type_tbl
+(
+`uint8_col` UInt8,
+`uint16_col` UInt16,
+uint32_col UInt32,
+uint64_col UInt64,
+int8_col Int8,
+int16_col Int16,
+int32_col Int32,
+int64_col Int64,
+float32_col Float32,
+float64_col Float64,
+bool_col UInt8,
+str_col String,
+fixedstr_col FixedString(3),
+uuid_col UUID,
+date_col Date,
+datetime_col DateTime,
+enum_col Enum('hello' = 1, 'world' = 2),
+ary_uint8_col Array(UInt8),
+ary_str_col Array(String),
+tuple_col Tuple(UInt8, String),
+nullable_col Nullable(UInt8),
+nested_col Nested
+    (
+        nested_id UInt32,
+        nested_str String
+    ),
+ipv4_col IPv4,
+ipv6_col IPv6,
+decimal_col Decimal(5,3)
+)
+ENGINE = MergeTree()
+ORDER BY (uint8_col);

+ 88 - 0
clickhousewriter/pom.xml

@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>datax-all</artifactId>
+        <groupId>com.alibaba.datax</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>clickhousewriter</artifactId>
+    <name>clickhousewriter</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>ru.yandex.clickhouse</groupId>
+            <artifactId>clickhouse-jdbc</artifactId>
+            <version>0.2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-core</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>datax-common</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.datax</groupId>
+            <artifactId>plugin-rdbms-util</artifactId>
+            <version>${datax-project-version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.properties</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <!-- compiler plugin -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${jdk-version}</source>
+                    <target>${jdk-version}</target>
+                    <encoding>${project-sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+            <!-- assembly plugin -->
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/package.xml</descriptor>
+                    </descriptors>
+                    <finalName>datax</finalName>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>dwzip</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 35 - 0
clickhousewriter/src/main/assembly/package.xml

@@ -0,0 +1,35 @@
+<assembly
+        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+    <id></id>
+    <formats>
+        <format>dir</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <fileSets>
+        <fileSet>
+            <directory>src/main/resources</directory>
+            <includes>
+                <include>plugin.json</include>
+ 				<include>plugin_job_template.json</include>
+ 			</includes>
+            <outputDirectory>plugin/writer/clickhousewriter</outputDirectory>
+        </fileSet>
+        <fileSet>
+            <directory>target/</directory>
+            <includes>
+                <include>clickhousewriter-0.0.1-SNAPSHOT.jar</include>
+            </includes>
+            <outputDirectory>plugin/writer/clickhousewriter</outputDirectory>
+        </fileSet>
+    </fileSets>
+
+    <dependencySets>
+        <dependencySet>
+            <useProjectArtifact>false</useProjectArtifact>
+            <outputDirectory>plugin/writer/clickhousewriter/libs</outputDirectory>
+            <scope>runtime</scope>
+        </dependencySet>
+    </dependencySets>
+</assembly>

+ 329 - 0
clickhousewriter/src/main/java/com/alibaba/datax/plugin/writer/clickhousewriter/ClickhouseWriter.java

@@ -0,0 +1,329 @@
+package com.alibaba.datax.plugin.writer.clickhousewriter;
+
+import com.alibaba.datax.common.element.Column;
+import com.alibaba.datax.common.element.StringColumn;
+import com.alibaba.datax.common.exception.CommonErrorCode;
+import com.alibaba.datax.common.exception.DataXException;
+import com.alibaba.datax.common.plugin.RecordReceiver;
+import com.alibaba.datax.common.spi.Writer;
+import com.alibaba.datax.common.util.Configuration;
+import com.alibaba.datax.plugin.rdbms.util.DBUtilErrorCode;
+import com.alibaba.datax.plugin.rdbms.util.DataBaseType;
+import com.alibaba.datax.plugin.rdbms.writer.CommonRdbmsWriter;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+
+import java.sql.Array;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class ClickhouseWriter extends Writer {
+	private static final DataBaseType DATABASE_TYPE = DataBaseType.ClickHouse;
+
+	public static class Job extends Writer.Job {
+		private Configuration originalConfig = null;
+		private CommonRdbmsWriter.Job commonRdbmsWriterMaster;
+
+		@Override
+		public void init() {
+			this.originalConfig = super.getPluginJobConf();
+			this.commonRdbmsWriterMaster = new CommonRdbmsWriter.Job(DATABASE_TYPE);
+			this.commonRdbmsWriterMaster.init(this.originalConfig);
+		}
+
+		@Override
+		public void prepare() {
+			this.commonRdbmsWriterMaster.prepare(this.originalConfig);
+		}
+
+		@Override
+		public List<Configuration> split(int mandatoryNumber) {
+			return this.commonRdbmsWriterMaster.split(this.originalConfig, mandatoryNumber);
+		}
+
+		@Override
+		public void post() {
+			this.commonRdbmsWriterMaster.post(this.originalConfig);
+		}
+
+		@Override
+		public void destroy() {
+			this.commonRdbmsWriterMaster.destroy(this.originalConfig);
+		}
+	}
+
+	public static class Task extends Writer.Task {
+		private Configuration writerSliceConfig;
+
+		private CommonRdbmsWriter.Task commonRdbmsWriterSlave;
+
+		@Override
+		public void init() {
+			this.writerSliceConfig = super.getPluginJobConf();
+
+			this.commonRdbmsWriterSlave = new CommonRdbmsWriter.Task(DATABASE_TYPE) {
+				@Override
+				protected PreparedStatement fillPreparedStatementColumnType(PreparedStatement preparedStatement, int columnIndex, int columnSqltype, String typeName, Column column) throws SQLException {
+					try {
+						if (column.getRawData() == null) {
+							preparedStatement.setNull(columnIndex + 1, columnSqltype);
+							return preparedStatement;
+						}
+
+						java.util.Date utilDate;
+						switch (columnSqltype) {
+							case Types.CHAR:
+							case Types.NCHAR:
+							case Types.CLOB:
+							case Types.NCLOB:
+							case Types.VARCHAR:
+							case Types.LONGVARCHAR:
+							case Types.NVARCHAR:
+							case Types.LONGNVARCHAR:
+								preparedStatement.setString(columnIndex + 1, column
+										.asString());
+								break;
+
+							case Types.TINYINT:
+							case Types.SMALLINT:
+							case Types.INTEGER:
+							case Types.BIGINT:
+							case Types.DECIMAL:
+							case Types.FLOAT:
+							case Types.REAL:
+							case Types.DOUBLE:
+								String strValue = column.asString();
+								if (emptyAsNull && "".equals(strValue)) {
+									preparedStatement.setNull(columnIndex + 1, columnSqltype);
+								} else {
+									switch (columnSqltype) {
+										case Types.TINYINT:
+										case Types.SMALLINT:
+										case Types.INTEGER:
+											preparedStatement.setInt(columnIndex + 1, column.asBigInteger().intValue());
+											break;
+										case Types.BIGINT:
+											preparedStatement.setLong(columnIndex + 1, column.asLong());
+											break;
+										case Types.DECIMAL:
+											preparedStatement.setBigDecimal(columnIndex + 1, column.asBigDecimal());
+											break;
+										case Types.REAL:
+										case Types.FLOAT:
+											preparedStatement.setFloat(columnIndex + 1, column.asDouble().floatValue());
+											break;
+										case Types.DOUBLE:
+											preparedStatement.setDouble(columnIndex + 1, column.asDouble());
+											break;
+									}
+								}
+								break;
+
+							case Types.DATE:
+								if (this.resultSetMetaData.getRight().get(columnIndex)
+										.equalsIgnoreCase("year")) {
+									if (column.asBigInteger() == null) {
+										preparedStatement.setString(columnIndex + 1, null);
+									} else {
+										preparedStatement.setInt(columnIndex + 1, column.asBigInteger().intValue());
+									}
+								} else {
+									java.sql.Date sqlDate = null;
+									try {
+										utilDate = column.asDate();
+									} catch (DataXException e) {
+										throw new SQLException(String.format(
+												"Date 类型转换错误:[%s]", column));
+									}
+
+									if (null != utilDate) {
+										sqlDate = new java.sql.Date(utilDate.getTime());
+									}
+									preparedStatement.setDate(columnIndex + 1, sqlDate);
+								}
+								break;
+
+							case Types.TIME:
+								java.sql.Time sqlTime = null;
+								try {
+									utilDate = column.asDate();
+								} catch (DataXException e) {
+									throw new SQLException(String.format(
+											"Date 类型转换错误:[%s]", column));
+								}
+
+								if (null != utilDate) {
+									sqlTime = new java.sql.Time(utilDate.getTime());
+								}
+								preparedStatement.setTime(columnIndex + 1, sqlTime);
+								break;
+
+							case Types.TIMESTAMP:
+								Timestamp sqlTimestamp = null;
+								if (column instanceof StringColumn && column.asString() != null) {
+									String timeStampStr = column.asString();
+									// JAVA TIMESTAMP 类型入参必须是 "2017-07-12 14:39:00.123566" 格式
+									String pattern = "^\\d+-\\d+-\\d+ \\d+:\\d+:\\d+.\\d+";
+									boolean isMatch = Pattern.matches(pattern, timeStampStr);
+									if (isMatch) {
+										sqlTimestamp = Timestamp.valueOf(timeStampStr);
+										preparedStatement.setTimestamp(columnIndex + 1, sqlTimestamp);
+										break;
+									}
+								}
+								try {
+									utilDate = column.asDate();
+								} catch (DataXException e) {
+									throw new SQLException(String.format(
+											"Date 类型转换错误:[%s]", column));
+								}
+
+								if (null != utilDate) {
+									sqlTimestamp = new Timestamp(
+											utilDate.getTime());
+								}
+								preparedStatement.setTimestamp(columnIndex + 1, sqlTimestamp);
+								break;
+
+							case Types.BINARY:
+							case Types.VARBINARY:
+							case Types.BLOB:
+							case Types.LONGVARBINARY:
+								preparedStatement.setBytes(columnIndex + 1, column
+										.asBytes());
+								break;
+
+							case Types.BOOLEAN:
+								preparedStatement.setInt(columnIndex + 1, column.asBigInteger().intValue());
+								break;
+
+							// warn: bit(1) -> Types.BIT 可使用setBoolean
+							// warn: bit(>1) -> Types.VARBINARY 可使用setBytes
+							case Types.BIT:
+								if (this.dataBaseType == DataBaseType.MySql) {
+									Boolean asBoolean = column.asBoolean();
+									if (asBoolean != null) {
+										preparedStatement.setBoolean(columnIndex + 1, asBoolean);
+									} else {
+										preparedStatement.setNull(columnIndex + 1, Types.BIT);
+									}
+								} else {
+									preparedStatement.setString(columnIndex + 1, column.asString());
+								}
+								break;
+
+							default:
+								boolean isHandled = fillPreparedStatementColumnType4CustomType(preparedStatement,
+										columnIndex, columnSqltype, column);
+								if (isHandled) {
+									break;
+								}
+								throw DataXException
+										.asDataXException(
+												DBUtilErrorCode.UNSUPPORTED_TYPE,
+												String.format(
+														"您的配置文件中的列配置信息有误. 因为DataX 不支持数据库写入这种字段类型. 字段名:[%s], 字段类型:[%d], 字段Java类型:[%s]. 请修改表中该字段的类型或者不同步该字段.",
+														this.resultSetMetaData.getLeft()
+																.get(columnIndex),
+														this.resultSetMetaData.getMiddle()
+																.get(columnIndex),
+														this.resultSetMetaData.getRight()
+																.get(columnIndex)));
+						}
+						return preparedStatement;
+					} catch (DataXException e) {
+						// fix类型转换或者溢出失败时,将具体哪一列打印出来
+						if (e.getErrorCode() == CommonErrorCode.CONVERT_NOT_SUPPORT ||
+								e.getErrorCode() == CommonErrorCode.CONVERT_OVER_FLOW) {
+							throw DataXException
+									.asDataXException(
+											e.getErrorCode(),
+											String.format(
+													"类型转化错误. 字段名:[%s], 字段类型:[%d], 字段Java类型:[%s]. 请修改表中该字段的类型或者不同步该字段.",
+													this.resultSetMetaData.getLeft()
+															.get(columnIndex),
+													this.resultSetMetaData.getMiddle()
+															.get(columnIndex),
+													this.resultSetMetaData.getRight()
+															.get(columnIndex)));
+						} else {
+							throw e;
+						}
+					}
+				}
+
+				private Object toJavaArray(Object val) {
+					if (null == val) {
+						return null;
+					} else if (val instanceof JSONArray) {
+						Object[] valArray = ((JSONArray) val).toArray();
+						for (int i = 0; i < valArray.length; i++) {
+							valArray[i] = this.toJavaArray(valArray[i]);
+						}
+						return valArray;
+					} else {
+						return val;
+					}
+				}
+
+				boolean fillPreparedStatementColumnType4CustomType(PreparedStatement ps,
+				                                                   int columnIndex, int columnSqltype,
+				                                                   Column column) throws SQLException {
+					switch (columnSqltype) {
+						case Types.OTHER:
+							if (this.resultSetMetaData.getRight().get(columnIndex).startsWith("Tuple")) {
+								throw DataXException
+										.asDataXException(ClickhouseWriterErrorCode.TUPLE_NOT_SUPPORTED_ERROR, ClickhouseWriterErrorCode.TUPLE_NOT_SUPPORTED_ERROR.getDescription());
+							} else {
+								ps.setString(columnIndex + 1, column.asString());
+							}
+							return true;
+
+						case Types.ARRAY:
+							Connection conn = ps.getConnection();
+							List<Object> values = JSON.parseArray(column.asString(), Object.class);
+							for (int i = 0; i < values.size(); i++) {
+								values.set(i, this.toJavaArray(values.get(i)));
+							}
+							Array array = conn.createArrayOf("String", values.toArray());
+							ps.setArray(columnIndex + 1, array);
+							return true;
+
+						default:
+							break;
+					}
+
+					return false;
+				}
+			};
+
+			this.commonRdbmsWriterSlave.init(this.writerSliceConfig);
+		}
+
+		@Override
+		public void prepare() {
+			this.commonRdbmsWriterSlave.prepare(this.writerSliceConfig);
+		}
+
+		@Override
+		public void startWrite(RecordReceiver recordReceiver) {
+			this.commonRdbmsWriterSlave.startWrite(recordReceiver, this.writerSliceConfig, super.getTaskPluginCollector());
+		}
+
+		@Override
+		public void post() {
+			this.commonRdbmsWriterSlave.post(this.writerSliceConfig);
+		}
+
+		@Override
+		public void destroy() {
+			this.commonRdbmsWriterSlave.destroy(this.writerSliceConfig);
+		}
+	}
+
+}

+ 31 - 0
clickhousewriter/src/main/java/com/alibaba/datax/plugin/writer/clickhousewriter/ClickhouseWriterErrorCode.java

@@ -0,0 +1,31 @@
+package com.alibaba.datax.plugin.writer.clickhousewriter;
+
+import com.alibaba.datax.common.spi.ErrorCode;
+
+public enum ClickhouseWriterErrorCode implements ErrorCode {
+	TUPLE_NOT_SUPPORTED_ERROR("ClickhouseWriter-00", "不支持TUPLE类型导入."),
+	;
+
+	private final String code;
+	private final String description;
+
+	private ClickhouseWriterErrorCode(String code, String description) {
+		this.code = code;
+		this.description = description;
+	}
+
+	@Override
+	public String getCode() {
+		return this.code;
+	}
+
+	@Override
+	public String getDescription() {
+		return this.description;
+	}
+
+	@Override
+	public String toString() {
+		return String.format("Code:[%s], Description:[%s].", this.code, this.description);
+	}
+}

+ 6 - 0
clickhousewriter/src/main/resources/plugin.json

@@ -0,0 +1,6 @@
+{
+    "name": "clickhousewriter",
+    "class": "com.alibaba.datax.plugin.writer.clickhousewriter.ClickhouseWriter",
+    "description": "useScene: prod. mechanism: Jdbc connection using the database, execute insert sql.",
+    "developer": "alibaba"
+}

+ 21 - 0
clickhousewriter/src/main/resources/plugin_job_template.json

@@ -0,0 +1,21 @@
+{
+    "name": "clickhousewriter",
+    "parameter": {
+        "username": "username",
+        "password": "password",
+        "column": ["col1", "col2", "col3"],
+        "connection": [
+            {
+                "jdbcUrl": "jdbc:clickhouse://<host>:<port>[/<database>]",
+                "table": ["table1", "table2"]
+            }
+        ],
+        "preSql": [],
+        "postSql": [],
+
+        "batchSize": 65536,
+        "batchByteSize": 134217728,
+        "dryRun": false,
+        "writeMode": "insert"
+    }
+}

+ 83 - 0
common/pom.xml

@@ -0,0 +1,83 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.alibaba.datax</groupId>
+        <artifactId>datax-all</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>datax-common</artifactId>
+    <name>datax-common</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>fluent-hc</artifactId>
+            <version>4.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-math3</artifactId>
+            <version>3.1.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+          <resource>
+            <directory>src/main/java</directory>
+            <includes>
+              <include>**/*.properties</include>
+            </includes>
+          </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${jdk-version}</source>
+                    <target>${jdk-version}</target>
+                    <encoding>${project-sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 25 - 0
common/src/main/java/com/alibaba/datax/common/base/BaseObject.java

@@ -0,0 +1,25 @@
+package com.alibaba.datax.common.base;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+public class BaseObject {
+
+	@Override
+	public int hashCode() {
+		return HashCodeBuilder.reflectionHashCode(this, false);
+	}
+
+	@Override
+	public boolean equals(Object object) {
+		return EqualsBuilder.reflectionEquals(this, object, false);
+	}
+
+	@Override
+	public String toString() {
+		return ToStringBuilder.reflectionToString(this,
+				ToStringStyle.MULTI_LINE_STYLE);
+	}
+}

+ 9 - 0
common/src/main/java/com/alibaba/datax/common/constant/CommonConstant.java

@@ -0,0 +1,9 @@
+package com.alibaba.datax.common.constant;
+
+public final class CommonConstant {
+    /**
+     * 用于插件对自身 split 的每个 task 标识其使用的资源,以告知core 对 reader/writer split 之后的 task 进行拼接时需要根据资源标签进行更有意义的 shuffle 操作
+     */
+    public static String LOAD_BALANCE_RESOURCE_MARK = "loadBalanceResourceMark";
+
+}

+ 20 - 0
common/src/main/java/com/alibaba/datax/common/constant/PluginType.java

@@ -0,0 +1,20 @@
+package com.alibaba.datax.common.constant;
+
+/**
+ * Created by jingxing on 14-8-31.
+ */
+public enum PluginType {
+    //pluginType还代表了资源目录,很难扩展,或者说需要足够必要才扩展。先mark Handler(其实和transformer一样),再讨论
+    READER("reader"), TRANSFORMER("transformer"), WRITER("writer"), HANDLER("handler");
+
+    private String pluginType;
+
+    private PluginType(String pluginType) {
+        this.pluginType = pluginType;
+    }
+
+    @Override
+    public String toString() {
+        return this.pluginType;
+    }
+}

+ 121 - 0
common/src/main/java/com/alibaba/datax/common/element/BoolColumn.java

@@ -0,0 +1,121 @@
+package com.alibaba.datax.common.element;
+
+import com.alibaba.datax.common.exception.CommonErrorCode;
+import com.alibaba.datax.common.exception.DataXException;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
+/**
+ * Created by jingxing on 14-8-24.
+ */
+public class BoolColumn extends Column {
+
+	public BoolColumn(Boolean bool) {
+		super(bool, Column.Type.BOOL, 1);
+	}
+
+	public BoolColumn(final String data) {
+		this(true);
+		this.validate(data);
+		if (null == data) {
+			this.setRawData(null);
+			this.setByteSize(0);
+		} else {
+			this.setRawData(Boolean.valueOf(data));
+			this.setByteSize(1);
+		}
+		return;
+	}
+
+	public BoolColumn() {
+		super(null, Column.Type.BOOL, 1);
+	}
+
+	@Override
+	public Boolean asBoolean() {
+		if (null == super.getRawData()) {
+			return null;
+		}
+
+		return (Boolean) super.getRawData();
+	}
+
+	@Override
+	public Long asLong() {
+		if (null == this.getRawData()) {
+			return null;
+		}
+
+		return this.asBoolean() ? 1L : 0L;
+	}
+
+	@Override
+	public Double asDouble() {
+		if (null == this.getRawData()) {
+			return null;
+		}
+
+		return this.asBoolean() ? 1.0d : 0.0d;
+	}
+
+	@Override
+	public String asString() {
+		if (null == super.getRawData()) {
+			return null;
+		}
+
+		return this.asBoolean() ? "true" : "false";
+	}
+
+	@Override
+	public BigInteger asBigInteger() {
+		if (null == this.getRawData()) {
+			return null;
+		}
+
+		return BigInteger.valueOf(this.asLong());
+	}
+
+	@Override
+	public BigDecimal asBigDecimal() {
+		if (null == this.getRawData()) {
+			return null;
+		}
+
+		return BigDecimal.valueOf(this.asLong());
+	}
+
+	@Override
+	public Date asDate() {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bool类型不能转为Date .");
+	}
+	
+	@Override
+	public Date asDate(String dateFormat) {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bool类型不能转为Date .");
+	}
+	
+	@Override
+	public byte[] asBytes() {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Boolean类型不能转为Bytes .");
+	}
+
+	private void validate(final String data) {
+		if (null == data) {
+			return;
+		}
+
+		if ("true".equalsIgnoreCase(data) || "false".equalsIgnoreCase(data)) {
+			return;
+		}
+
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT,
+				String.format("String[%s]不能转为Bool .", data));
+	}
+}

+ 90 - 0
common/src/main/java/com/alibaba/datax/common/element/BytesColumn.java

@@ -0,0 +1,90 @@
+package com.alibaba.datax.common.element;
+
+import com.alibaba.datax.common.exception.CommonErrorCode;
+import com.alibaba.datax.common.exception.DataXException;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
+/**
+ * Created by jingxing on 14-8-24.
+ */
+public class BytesColumn extends Column {
+
+	public BytesColumn() {
+		this(null);
+	}
+
+	public BytesColumn(byte[] bytes) {
+		super(ArrayUtils.clone(bytes), Column.Type.BYTES, null == bytes ? 0
+				: bytes.length);
+	}
+
+	@Override
+	public byte[] asBytes() {
+		if (null == this.getRawData()) {
+			return null;
+		}
+
+		return (byte[]) this.getRawData();
+	}
+
+	@Override
+	public String asString() {
+		if (null == this.getRawData()) {
+			return null;
+		}
+
+		try {
+			return ColumnCast.bytes2String(this);
+		} catch (Exception e) {
+			throw DataXException.asDataXException(
+					CommonErrorCode.CONVERT_NOT_SUPPORT,
+					String.format("Bytes[%s]不能转为String .", this.toString()));
+		}
+	}
+
+	@Override
+	public Long asLong() {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bytes类型不能转为Long .");
+	}
+
+	@Override
+	public BigDecimal asBigDecimal() {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bytes类型不能转为BigDecimal .");
+	}
+
+	@Override
+	public BigInteger asBigInteger() {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bytes类型不能转为BigInteger .");
+	}
+
+	@Override
+	public Double asDouble() {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bytes类型不能转为Long .");
+	}
+
+	@Override
+	public Date asDate() {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bytes类型不能转为Date .");
+	}
+	
+	@Override
+	public Date asDate(String dateFormat) {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bytes类型不能转为Date .");
+	}
+
+	@Override
+	public Boolean asBoolean() {
+		throw DataXException.asDataXException(
+				CommonErrorCode.CONVERT_NOT_SUPPORT, "Bytes类型不能转为Boolean .");
+	}
+}

+ 0 - 0
common/src/main/java/com/alibaba/datax/common/element/Column.java


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov