├── .gitignore ├── LICENSE.txt ├── README.md ├── build_for_presto.sh ├── hs_function_test.sql ├── package.xml ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── wgzhao │ │ └── presto │ │ └── udfs │ │ ├── UdfPlugin.java │ │ ├── dto │ │ └── IsoDto.java │ │ ├── scalar │ │ ├── BankFunction.java │ │ ├── ExtendedDateTimeFunctions.java │ │ ├── ExtendedMathematicaFunctions.java │ │ ├── ExtendedNumberFunctions.java │ │ ├── ExtendedStringFunctions.java │ │ ├── IP2RegionFunction.java │ │ ├── IPFunction.java │ │ ├── IdCardFunctions.java │ │ ├── List2Json.java │ │ ├── Map2Json.java │ │ ├── Mobile2Region.java │ │ ├── PinyinFunction.java │ │ ├── PrestoDateTimeFunctions.java │ │ ├── PrestoDateTimeZoneIndex.java │ │ ├── TradeDateFunctions.java │ │ └── XmlFunctions.java │ │ ├── utils │ │ ├── CloseDateUtil.java │ │ ├── IPSearcher.java │ │ ├── IdCardUtil.java │ │ ├── IsoSearcher.java │ │ ├── MobileSearcher.java │ │ └── UDFXPathUtil.java │ │ └── window │ │ ├── FirstNonNullValueFunction.java │ │ ├── FirstOrLastValueIgnoreNullFunction.java │ │ └── LastNonNullValueFunction.java └── resources │ ├── META-INF │ └── services │ │ └── io.trino.spi.Plugin │ ├── bankInfo.json │ ├── closedate.dat.gz │ ├── ip2region.db │ ├── iso3166.csv.gz │ └── presto-udfs-services.jar └── test └── java └── com └── wgzhao └── presto └── udfs ├── TestBankFunctions.java ├── TestExtendDatetimeFunctions.java ├── TestIP2RegionFunctions.java ├── TestIPFunctions.java ├── TestIdCardFunctions.java ├── TestNumberFunctions.java ├── TestPinyinFunction.java ├── TestStringFunctions.java ├── TestTradeDateFunctions.java └── TestXmlFunctions.java /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /udfs.iml 3 | /target/ 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013-2021 wgzhao 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Presto User-Defined Functions(UDFs) 2 | 3 | Presto/Trino 自定义函数,当前仅针对 [Trino](https://trino.io) 349及以后的版本有有效 4 | 如果想编译兼容名为 `prestosql` 的版本,则可以执行 5 | 6 | ```shell 7 | bash build_for_presto.sh 8 | ``` 9 | 脚本。该脚本执行完后,会在当前目录下,生产一个 `udfs--presto-348.zip` 的压缩文件。使用方式和下面描述一样。 10 | 11 | ## 使用方法 12 | 13 | ### 直接下载 14 | 15 | 如果不想编译,可以从 `release` 页面中找到对应的版本的压缩文件下载到本地。或者你也可以按照以下方式进行编译 16 | 17 | ### 自行编译 18 | 19 | 下载代码并编译打包 20 | 21 | ```shell 22 | git clone https://github.com/wgzhao/presto-udfs 23 | mvn clean package assembly:single 24 | ``` 25 | 26 | 执行上述指令后,将在 `target` 目录下,生成一个 `udfs--release.zip` 的压缩包 27 | 28 | ### 安装 29 | 30 | 假定你的 Presto 安装 `/usr/lib/presto` 目下,执行下面的命令进行安装 31 | 32 | ```shell 33 | unzip -q -o udfs--release.zip -d /usr/lib/presto/plugin/ 34 | ``` 35 | 36 | 如果安装的是 Trino, 假定安装目录为 `/usr/lib/trino`, 则执行下面的命令: 37 | 38 | ```shell 39 | unzip -q -o udfs--release.zip -d /usr/lib/trino/plugin/ 40 | ``` 41 | 42 | 上面命令完成后,将会在安装目录的 `plugin/` 目录下创建一个 `extra` 目录,所有需要的jar文件均在该目录。 43 | 44 | 重启 Presto/Trino 服务,连接 Presto/Trino 服务后,执行 `show functions like 'udf_%'` 将得到类似如下的输出 45 | 46 | | Function | Return Type | Argument Types | Function Type | Deterministic | Description | 47 | ----------------------|--------------|------------------|---------------|---------------|-----------------------------------------------------| 48 | | udf_add_normal_days | varchar | varchar, integer | scalar | true | add days from base date | 49 | | udf_add_trade_days | varchar | varchar, integer | scalar | true | add days from base date with yyyyMMdd format | 50 | | udf_bank_name | varchar | varchar | scalar | true | return bank name for giving card number | 51 | | udf_bank_code | varchar | varchar | scalar | true | return bank code for giving card number | 52 | | udf_ch2num | bigint | varchar | scalar | true | convert chinese number to Arabia number | 53 | | udf_count_trade_days | integer | varchar, varchar | scalar | true | count the number of trade date between given two dates | 54 | | udf_eval | double | varchar | scalar | true | the implement of javascript eval function | 55 | | udf_get_birthday | integer | varchar | scalar | true | Extract birthday from valid ID card | 56 | | udf_int2ip | varchar | integer | scalar | true | get region from IP Address | 57 | | udf_ip2int | bigint | varchar | scalar | true | get region from IP Address | 58 | | udf_ip2region | varchar | varchar | scalar | true | get region from IP Address | 59 | | udf_ip2region | varchar | varchar, varchar | scalar | true | get region from IP Address | 60 | | udf_is_idcard | boolean | varchar | scalar | true | Check IdCard is valid or not | 61 | | udf_is_trade_date | boolean | varchar | scalar | true | is close day or not | 62 | | udf_last_trade_date | varchar | varchar | scalar | true | get the last exchange day before specified date | 63 | | udf_max_draw_down | double | varchar | scalar | true | get the max drawdown rate | 64 | | udf_num2ch | varchar | bigint | scalar | true | convert Chinese number to Arabia number | 65 | | udf_num2ch | varchar | bigint, boolean | scalar | true | convert Chinese number to Arabia number | 66 | | udf_num2ch | varchar | varchar | scalar | true | convert Chinese number to Arabia number | 67 | | udf_num2ch | varchar | varchar, boolean | scalar | true | convert Chinese number to Arabia number | 68 | | udf_pmod | bigint | bigint, bigint | scalar | true | Returns the positive value of a mod b. | 69 | | udf_pmod | double | double, double | scalar | true | Returns the positive value of a mod b | 70 | | udf_to_chinese | varchar | varchar | scalar | true | convert number string to chinese number string | 71 | | udf_to_chinese | varchar | varchar, boolean | scalar | true | convert number string to chinese number string | 72 | | udf_pinyin | varchar | varchar | scalar | true | convert chinese to pinyin | 73 | | udf_xpath | array(varchar(x)) | varchar(x), varchar(y) | scalar | true | Returns a string array of values within xml nodes that match the xpath 74 | | udf_xpath_boolean | boolean | varchar(x), varchar(y) | scalar | true | Evaluates a boolean xpath expression 75 | | udf_xpath_double | double | varchar(x), varchar(y) | scalar | true | Returns a double value that matches the xpath expression 76 | | udf_xpath_float | double | varchar(x), varchar(y) | scalar | true | Returns a double value that matches the xpath expression 77 | | udf_xpath_int | bigint | varchar(x), varchar(y) | scalar | true | Returns a long value that matches the xpath expression 78 | | udf_xpath_long | bigint | varchar(x), varchar(y) | scalar | true | Returns a long value that matches the xpath expression 79 | | udf_xpath_number | double | varchar(x), varchar(y) | scalar | true | Returns a double value that matches the xpath expression 80 | | udf_xpath_str | varchar | varchar(x), varchar(y) | scalar | true | Returns a string value that matches the xpath expression 81 | | udf_xpath_string | varchar | varchar(x), varchar(y) | scalar | true | Returns a string value that matches the xpath expression 82 | 83 | ## 已经实现的 UDF 84 | 85 | ### 数学函数 86 | 87 | #### udf_pmod(n, m) -> [same as input] 88 | 89 | 返回 n mod m 的值,商取正数 90 | 91 | ```sql 92 | select udf_pmod(17, -5) -- -3 93 | ``` 94 | 95 | #### num2ch(string str, [boolean flag]) -> string , num2ch(long num, [ boolean flag]) -> string 96 | 97 | 把阿拉伯数字转为汉字数字,数字汉字有两种写法,一种是普通写法,一种是大写写法:比如 1 ,普通写作 `一`, 大写则为 `壹`。 98 | `flag` 用来指定采取何种写法,`true` 表示普通写法,`false` 表示大写写法,默认值为 `false` 99 | 100 | ```sql 101 | select udf_num2ch('103543'); -- 拾万叁仟伍佰肆拾叁 102 | select udf_num2ch(103543, true); -- 十万三千五百四十三 103 | select udf_num2ch(103_543); -- 十万三千五百四十三 104 | select udf_num2ch(); -- NULL 105 | ``` 106 | 107 | ### 字符串函数 108 | 109 | #### udf_ch2num(string str) -> long, ch2num(long a) -> long 110 | 111 | 返回中文标记的数字的阿拉伯数字形式 ,如果传递字符串为空或包含非汉字数字,则返回为 NULL。 112 | 113 | ```sql 114 | select udf_ch2num('一十万三千五百四十三'); -- 103543 115 | select udf_ch2num('壹拾万叁仟伍佰肆拾叁'); -- 103543 116 | select udf_ch2num(''); -- NULL 117 | select udf_ch2num(null); -- NULL 118 | select udf_ch2num('abc'); -- NULL 119 | ``` 120 | 121 | **注意**: 122 | 123 | 1. 目前实现的限制,如果是十万XXX这种形式会报错,必须写成一十万 124 | 2. 但如果一个不合法的汉字数字,目前无法正确识别, 比如 `select udf_ch2num('拾万万'); -- 10` 得到的是一个不正确的结果 125 | 126 | #### udf_to_chinese(string str, [boolean flag] ) -> string 127 | 128 | 把数字字符串转为汉字字符串,注意它和 `udf_num2ch` 函数区别是本函数不含数量关系,即仅仅把每个阿拉伯数字转为中文汉字。 同样的,使用 `flag` 来区分是普通大写,还是汉字大写。true` 表示普通写法,`false` 表示大写写法,默认值为 `false` 129 | 130 | ```sql 131 | select udf_to_chinese('2002'); -- 贰零零贰 132 | select udf_to_chinese('2002', false); -- 贰零零贰 133 | select udf_to_chinese('2002', true); -- 二〇〇二 134 | ``` 135 | 136 | #### eval(string str) -> double 137 | 138 | 实现Javascript中eval函数的功能, 暂时仅支持 `+`,`-`,`*`, `/` 运算 139 | 140 | ```sql 141 | select udf_eval('4*(5+2)'); -- 28 142 | select udf_eval(null); -- NULL 143 | select udf_eval('ab'); -- NULL 144 | ``` 145 | 146 | #### udf_is_idcard(string id) -> bool 147 | 148 | 检测给定的身份证号码是否有效, 支持中国大陆身份证(15位和18位)以及港澳台的10位证件号码 149 | 150 | ```sql 151 | select udf_is_idcard(null); -- false 152 | select udf_is_idcard('23070719391110007X'); -- true 153 | select udf_is_idcard('230707391110007'); -- true 154 | select udf_is_idcard('1234566'); -- false 155 | ``` 156 | 157 | #### get_birthday(string id) -> int 158 | 159 | 从有效的身份证号码中提取生日,如果身份证无效,则返回为0 160 | 161 | ```sql 162 | select udf_get_birthday(null); -- 0 163 | select udf_get_birthday('23070719391110007X'); -- 19391110 164 | select udf_get_birthday('230707391110007'); -- 19391110 165 | select udf_get_birthday('1234566'); -- NULL 166 | ``` 167 | 168 | ### IP 相关函数 169 | 170 | #### udf_ip2int(string ip) -> int 171 | 172 | 将有效的IP地址转为长整数表达法,如果指定的IP地址无效,则返回为0 173 | 174 | ```sql 175 | select udf_ip2int('127.0.0.1'); -- 2130706433 176 | select udf_ip2int('0.0.0.0'); -- 0 177 | select udf_ip2int('a.b.c.d'); -- 0 178 | ``` 179 | 180 | #### udf_int2ip(int a) -> string 181 | 182 | 将长整数表示的IP地址转为十进制字符出表达法,如果`a` 小于0 ,则返回为 NULL 183 | 184 | ```sql 185 | select udf_int2ip(185999660); -- 11.22.33.44 186 | select udf_int2ip(0); -- 0.0.0.0 187 | select udf_int2ip(-1); -- NULL 188 | ``` 189 | 190 | #### udf_ip2region(string ip, [string flag]) -> string 191 | 192 | `ip2region` 实现了IP地址归属地以及国家对应的国际编码查询,国际编码定义来源于 [ISO 3166-1](https://zh.wikipedia.org/wiki/ISO_3166-1)。 193 | 该函数带一个必选参数和一个可选参数,语义如下: 194 | 195 | ``` 196 | udf_ip2region(ip, [country|g|province|p|city|c|isp|i|en|m2|m3|digit]) 197 | ``` 198 | 199 | 必选参数 `ip` 表示要查询的 IP 地址,目前仅支持点分字符串IP地址格式,比如 200 | 201 | ```sql 202 | select udf_ip2region('119.29.29.29'); -- 中国|0|北京|北京市|腾讯 203 | ``` 204 | 205 | 上述查询结果的层级用 `|` 分隔,从第一列开始,分别表示 `国家|区域|省份|城市|供应商(ISP)` 206 | 207 | 如果对应的列没有值,则用 `0` 表示(注意:不是用`null`表示) 208 | 209 | 比如下面的查询: 210 | 211 | ```sql 212 | select udf_ip2region('1.1.1.1'); -- 澳大利亚|0|0|0|0 213 | ``` 214 | 215 | 则表示只有国家信息,其他信息缺失 216 | 217 | 如果IP地址非法,则返回`null`,比如 218 | 219 | ```sql 220 | select udf_ip2region('a.b.c.d'); -- NULL 221 | select udf_ip2region('1.1.1.'); -- NULL 222 | ``` 223 | 224 | 这里的IP地址也可以网络地址,比如 225 | 226 | ```sql 227 | select udf_ip2region('119.29.29.0'); -- 中国|0|北京|北京市|腾讯 228 | select udf_ip2region('119.29.0.0'); -- 中国|0|广东省|广州市|电信 229 | ``` 230 | 231 | 第二个参数为可选参数,用来指定想要获取哪个层级的信息,每个定义有完整单词以及缩写,含义如下: 232 | 233 | - `country|g` 表示查询IP地址所在国家 234 | - `province|p` 表示查询IP地址所在省份/州/道 235 | - `city|c` 表示查询IP地址所在城市 236 | - `isp|i` 表示查询 237 | - `en` 返回IP地址表示国家英语名称,比如 `China` 238 | - `m2` 返回IP地址表示国家两位自字母代码,比如 `CN` 239 | - `m3` 返回IP地址表示国家三位自字母代码,比如 `CHN` 240 | - `digit` 返回IP地址表示国家数字代码,比如 `156` 241 | 242 | 以下是一些查询例子 243 | 244 | ```sql 245 | select udf_ip2region('119.29.29.29', 'c'); -- 北京市 246 | select udf_ip2region('8.8.8.8', 'g'); -- 美国 247 | select udf_ip2region('223.5.5.5', 'i'); -- 阿里云 248 | select udf_ip2region('1.1.1.1', 'en') -- Australia 249 | select udf_ip2region('1.1.1.1', 'm2'); -- AU 250 | select udf_ip2region('1.1.1.1', 'm3'); -- AUS 251 | select udf_ip2region('1.1.1.1', 'digit'); -- 36 252 | ``` 253 | 254 | ### mobile2region 255 | 256 | `mobile2region` 实现了手机号码归属地查询,该函数接受一个必选参数和一个可选参数,语义如下: 257 | 258 | ``` 259 | udf_mobile2region(tel, [province|p|city|c|isp|i]) 260 | ``` 261 | 262 | 第一个参数 `tel` 表示要查询的手机号码,第二个参数表示要返回的层级,含义如下: 263 | 264 | - `province|p` 表示查询IP地址所在省份/州/道 265 | - `city|c` 表示查询IP地址所在城市 266 | - `isp|i` 表示查询 267 | 268 | 以下是一些查询例子 269 | 270 | ```sql 271 | select udf_mobile2region('13410774560'); -- 广东|深圳|中国移动 272 | select udf_mobile2region('13011'); -- NULL 273 | select udf_mobile2region('18945871234', 'p'); -- 黑龙江 274 | select udf_mobile2region('18945871234', 'c'); -- 伊春 275 | select udf_mobile2region('18945871234', 'i'); -- 中国电信 276 | ``` 277 | 278 | ### 证券交易日相关函数 279 | 280 | 这里的函数都和中国大陆证券交易日相关的函数,国内证券交易日符合以下条件 281 | 282 | 1. 双休日和国家法定节假日必然不是交易日 283 | 2. 调休中遇到双休日(比如周六要求上班)也不是双休日 284 | 285 | 由于每年的调休不同,也就导致证券交易日没有固定的规律,需要有交易所在头一年年底下发到各券商,同时遇到一些特别情况,还有临时调整(比如2020年1月31日周五,农历初七,本应为交易日,但受疫情影响,调整为非交易日)。因此交易日之间的计算是证券相关数据分析必然会遇到的问题。下面的函数试图简化交易日期计算难度。 286 | 287 | #### udf_add_normal_days(string base_date, int n) -> string 288 | 289 | 计算在给定日期后的 n 天内的第一个交易日, 如果 n 是正数,则往后计算;如果是负数,则往前计算。 290 | 291 | 该函数的计算分成两步: 292 | 293 | 1. 在指定的日期 `base_date`上增加 `n` 天,得到一个日期 `delta_date`; 294 | 295 | 第二步是找到不超过 `delta_date` 日期的最近交易日。 296 | 297 | ```sql 298 | select udf_add_normal_days('20210903', 4); -- 20210907 299 | select udf_add_normal_days('20210904', 1); -- 20210903 300 | select udf_add_normal_days('20210906', -1); -- 20210903 301 | ``` 302 | 303 | #### udf_count_trade_days(string d1, string d2) -> int 304 | 305 | 计算两个给定的第一个日期(包括)和第二个日期(包括)之间有多少个交易日,日期格式为 `yyyyMMdd` 306 | 307 | ```sql 308 | select udf_count_trade_days('20210901', '20210906'); -- 4 309 | select udf_count_trade_days('20210906', '20210906'); -- 1 310 | select udf_count_trade_days('20210903', '20210906'); -- 2 311 | 312 | ``` 313 | 314 | #### udf_add_trade_days(string base_date, int n) -> string 315 | 316 | 计算在超过指定日期(`base_date`)的最近交易日上增加 n 个交易日后的日期并返回。这个计算实际上两个步骤 317 | 318 | 1. 找到不超过 `base_date` 日期最近的交易日(`base_date` 如果本身是交易日,则为自身),然后 319 | 2. 增加 n 个交易日(注意:不是自然日),等于找到指定日期后几个交易日期 320 | 321 | ```sql 322 | select udf_add_trade_days('20210903', 1); -- 20210906 323 | select udf_add_trade_days('20210904', 1); -- 20210906 324 | select udf_add_trade_days('20210101', 3); -- 20210104 325 | ``` 326 | 327 | 注意该函数于 `udf_add_normal_days` 的逻辑区别。 328 | 329 | #### udf_last_trade_trade(string d) -> string 330 | 331 | 获取指定日期(`d`)的上一 日 交易日并返回,如果没有找到,则返回 NULL. 332 | 333 | ```sql 334 | select udf_last_trade_date('20210906'); -- 20210903 335 | select udf_last_trade_date('20210907'); -- 20210906 336 | select udf_last_trade_date('19920101'); -- NULL 337 | ``` 338 | 339 | #### udf_is_trade_date(string d) -> bool 340 | 341 | 判断给定的日期是否为交易日,如果是,返回 true,其他情况返回 false 342 | 343 | ```sql 344 | select udf_is_trade_date(null); -- false 345 | select udf_is_trade_date(''); -- false 346 | select udf_is_trade_date('20210906'); -- true 347 | select udf_is_trade_date('20210904'); -- false 348 | select udf_is_trade_date('20210904'); -- false 349 | ``` 350 | 351 | ### xpath 相关函数 352 | 353 | 这是一组用来使用 `xpath` 表达式来分析 xml 字符串的函数,其代码来自 [Apache Hive](https://github.com/apache/hive/blob/master/ql/src/java/org/apache/hadoop/hive/ql/udf/xml/) 354 | 355 | 具体的用法,可以参考 [LanguageManual XPathUDF](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+XPathUDF) 356 | 357 | 要注意的是,相对 Hive 的函数名而言,这里的函数都增加了 `udf_` 前缀 358 | 359 | 这里列出函数的基本用法 360 | 361 | ```sql 362 | select udf_xpath('b1b2','a/*'); -- [] 363 | select udf_xpath('b1b2','a/*/text()'); -- [b1, b2] 364 | select udf_xpath('b1b2','//@id'); -- [foot, bar] 365 | SELECT udf_xpath_string('bbcc', 'a/b'); -- bb 366 | SELECT udf_xpath_string ('bbcc', 'a'); -- bbcc 367 | SELECT udf_xpath_boolean ('b', 'a/b'); -- true 368 | SELECT udf_xpath_boolean ('b', 'a/c'); -- false 369 | SELECT udf_xpath_int('b', 'a = 10'); -- 0 370 | SELECT udf_xpath_int('1248', 'sum(a/*)'); -- 15 371 | ``` 372 | 373 | ### 银行卡相关函数 374 | 375 | 主要是根据给定的银行卡,获得对应的开户行名称以及编号 376 | 377 | ```sql 378 | select udf_bank_name('621700292010'); -- 中国建设银行 379 | select udf_bank_name(''); -- NULL 380 | select udf_bank_code('621768160266'); -- CITIC 381 | select udf_bank_code(''); -- NULL 382 | ``` 383 | 384 | ### 拼音函数 385 | 386 | 把中文转换为拼音小写字母,多音字只返回常用的拼音 387 | 388 | ```sql 389 | select udf_pinyin('中文'); -- zhongwen 390 | select udf_pinyin('中'); -- zhong 391 | select udf_pinyin(''); -- '' 392 | select udf_pinyin(); -- exception occurred 393 | ``` 394 | 395 | ## 注意事项 396 | 397 | `src/main/resources/closedate.dat.gz` 文件存储的是从 2000 年开始到当年的所有交易日日期,每行一个日期。因此每到年底,需要将交易所下发的来年交易日追加到该文件中,并重新打包发布到生产环境。 398 | 399 | 如果中途有交易日变更,也需要执行上述操作。 400 | -------------------------------------------------------------------------------- /build_for_presto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # build for Trino version 348 or earlier (formly known as PrestoSQL) 4 | 5 | os=$(uname -s) 6 | if [ "$os" = "Darwin" ];then 7 | # macos built-in sed has different behavior with -i option 8 | sed -i '' 's#358#348#g;s#io.trino#io.prestosql#g;s#trino-spi#presto-spi#g;s#trino-main#presto-main#g;s#${trino.version}#${presto.version}#g' pom.xml 9 | find ./src -name "*.java" | xargs sed -i '' 's#io.trino#io.prestosql#g' 10 | else 11 | sed -i 's#358#348#g;s#io.trino#io.prestosql#g;s#trino-spi#presto-spi#g;s#trino-main#presto-main#g;s#${trino.version}#${presto.version}#g' pom.xml 12 | find ./src -name "*.java" | xargs sed -i 's#io.trino#io.prestosql#g' 13 | fi 14 | # change the trino package name and version 15 | 16 | # change import package name 17 | 18 | # change service package name 19 | mv src/main/resources/META-INF/services/io.trino.spi.Plugin src/main/resources/META-INF/services/io.prestosql.spi.Plugin 20 | # build 21 | mvn clean package assembly:single -Dmaven.test.skip=true 22 | 23 | # get current version 24 | version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) 25 | 26 | # copy artifacts to target directory 27 | cp target/udfs-${version}-release.zip udfs-${version}-presto-348.zip 28 | mv src/main/resources/META-INF/services/io.prestosql.spi.Plugin src/main/resources/META-INF/services/io.trino.spi.Plugin 29 | # reset all changes 30 | git reset --hard 31 | 32 | -------------------------------------------------------------------------------- /hs_function_test.sql: -------------------------------------------------------------------------------- 1 | -- udf_add_close_day | varchar | varchar, integer 2 | -- udf_add_normal_day | varchar | varchar, integer 3 | -- udf_count_close_day | integer | varchar, varchar 4 | -- udf_fill_data_by_close_day | varchar | varchar 5 | -- udf_fill_data_by_close_day | varchar | varchar, varchar, varchar 6 | -- udf_fill_data_by_close_day | varchar | varchar, varchar, varchar, varchar, varchar 7 | -- udf_fill_data_by_close_day | varchar | varchar, varchar, varchar, varchar, varchar, varchar, varchar, varchar 8 | -- udf_fill_data_by_close_day | varchar | varchar, varchar, varchar, varchar, varchar, varchar, varchar, varchar 9 | -- udf_hs_date_diff | integer | varchar, varchar 10 | -- udf_last_close_day | varchar | varchar 11 | -- udf_max_draw_down | double | varchar 12 | 13 | select udf_add_close_day('20200101', 2); 14 | select udf_add_close_day('20200101', -1); 15 | select udf_add_normal_day('20200101', 10); 16 | select udf_add_normal_day('20200101', -10); 17 | select udf_count_close_day('20200101', '20200301'); 18 | select udf_fill_data_by_close_day('20200106'); 19 | select udf_hs_date_diff('20200101', '20200302'); 20 | select udf_last_close_day('20200607'); 21 | select udf_last_close_day('20150105'); 22 | select udf_last_close_day('20201231'); 23 | select udf_max_draw_down('1,2,3,4,1'); 24 | 25 | -- test null 26 | select udf_add_close_day(null, 2); 27 | select udf_add_close_day(null, null); 28 | select udf_add_close_day(null, -1); 29 | select udf_add_normal_day(null, 10); 30 | select udf_add_close_day(null, null); 31 | select udf_add_normal_day(null, -10); 32 | select udf_count_close_day(null, '20200301'); 33 | select udf_count_close_day(null, null); 34 | select udf_fill_data_by_close_day(null); 35 | select udf_hs_date_diff(null, '20200302'); 36 | select udf_hs_date_diff('20200302', null); 37 | select udf_hs_date_diff(null, null); 38 | select udf_last_close_day(null); 39 | select udf_max_draw_down(null); 40 | 41 | -- test empty string 42 | select udf_add_close_day('', 2); 43 | select udf_add_close_day('', -1); 44 | select udf_add_normal_day('', 10); 45 | select udf_add_normal_day('', -10); 46 | select udf_count_close_day('', '20200301'); 47 | select udf_count_close_day('', ''); 48 | select udf_fill_data_by_close_day(''); 49 | select udf_hs_date_diff('', '20200302'); 50 | select udf_hs_date_diff('', ''); 51 | select udf_last_close_day(''); 52 | select udf_max_draw_down(''); 53 | 54 | 55 | select udf_fill_data_by_close_day('D!#900947!#20200403!#1!#0.00000000!#97087.0000!#1!#0.287!#0.000!#0!#0.291!#0.283!#0.2870!#0.2870!#20200403!@#D!#900947!#20190425!#1!#0.00000000!#457169.0000!#1!#0.389!#0.000!#0!#0.395!#0.389!#0.3890!#0.3890!#20190425!@#D!#900947!#20200313!#1!#0.00000000!#442754.0000!#1!#0.292!#0.000!#0!#0.294!#0.285!#0.2920!#0.2920!#20200313!@#D!#900947!#20190304!#1!#0.00000000!#2911337.0000!#1!#0.409!#0.000!#0!#0.418!#0.401!#0.4090!#0.4090!#20190304!@#D!#900947!#20180703!#1!#0.00000000!#156746.0000!#1!#0.469!#0.000!#0!#0.470!#0.460!#0.4690!#0.4690!#20180703!@#D!#900947!#20180607!#1!#0.00000000!#209897.0000!#1!#0.501!#0.000!#0!#0.506!#0.500!#0.5010!#0.5010!#20180607!@#D!#900947!#20191230!#1!#0.00000000!#237310.0000!#1!#0.331!#0.000!#0!#0.331!#0.325!#0.3310!#0.3310!#20191230!@#D!#900947!#20200511!#1!#0.00000000!#324944.0000!#1!#0.242!#0.000!#0!#0.250!#0.240!#0.2420!#0.2420!#20200511!@#D!#900947!#20190423!#1!#0.00000000!#570243.0000!#1!#0.392!#0.000!#0!#0.397!#0.392!#0.3920!#0.3920!#20190423!@#D!#900947!#20180605!#1!#0.00000000!#265566.0000!#1!#0.506!#0.000!#0!#0.506!#0.499!#0.5060!#0.5060!#20180605!@#D!#900947!#20190807!#1!#0.00000000!#141769.0000!#1!#0.340!#0.000!#0!#0.343!#0.335!#0.3400!#0.3400!#20190807!@#D!#900947!#20180507!#1!#0.00000000!#251860.0000!#1!#0.517!#0.000!#0!#0.519!#0.511!#0.5170!#0.5170!#20180507!@#D!#900947!#20190625!#1!#0.00000000!#339236.0000!#1!#0.373!#0.000!#0!#0.379!#0.368!#0.3730!#0.3730!#20190625!@#D!#900947!#20191212!#1!#0.00000000!#84370.0000!#1!#0.316!#0.000!#0!#0.316!#0.309!#0.3160!#0.3160!#20191212!@#D!#900947!#20191129!#1!#0.00000000!#335698.0000!#1!#0.311!#0.000!#0!#0.319!#0.306!#0.3110!#0.3110!#20191129!@#D!#900947!#20180612!#1!#0.00000000!#372507.0000!#1!#0.502!#0.000!#0!#0.502!#0.496!#0.5020!#0.5020!#20180612!@#D!#900947!#20200508!#1!#0.00000000!#235923.0000!#1!#0.249!#0.000!#0!#0.250!#0.243!#0.2490!#0.2490!#20200508!@#D!#900947!#20200605!#1!#0.00000000!#101102.0000!#1!#0.208!#0.000!#0!#0.211!#0.208!#0.2080!#0.2080!#20200605', 56 | '20180301', '20200713','2','14',null,'-1','-1','asc'); -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 3 | release 4 | 5 | zip 6 | 7 | false 8 | 9 | 10 | src/main/resources 11 | 12 | *.jar 13 | 14 | extra 15 | 16 | 17 | target/ 18 | 19 | ${project.artifactId}-${project.version}.jar 20 | 21 | extra 22 | 23 | 24 | 25 | 26 | 27 | false 28 | extra 29 | runtime 30 | 31 | org.slf4j:slf4j-api 32 | io.airlift:log 33 | com.google.guava:guava 34 | commons-io:commons-io 35 | joda-time:joda-time 36 | me.ihxq.projects:phone-number-geo 37 | org.lionsoul:ip2region 38 | com.alibaba.fastjson2:fastjson2 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | com.wgzhao.presto 7 | udfs 8 | 2.10.4-SNAPSHOT 9 | PrestoUDFs 10 | https://github.com/wgzhao/presto-udfs 11 | Common Functions for the PrestoSQL/TrinoDB Engine 12 | 13 | 14 | 16 | 19 | 387 20 | 0.40 21 | 30.0-jre 22 | 1 23 | 0.125 24 | 2.8.1 25 | 11 26 | 11 27 | UTF-8 28 | 29 | 30 | 31 | https://github.com/wgzhao/presto-udfs 32 | scm:git:git://github.com/wgzhao/presto-udfs.git 33 | scm:git:git@github.com:wgzhao/presto-udfs.git 34 | HEAD 35 | 36 | 37 | 38 | 39 | Apache License, Version 2.0 40 | https://github.com/wgzhao/presto-udfs/LICENSE.txt 41 | repo 42 | 43 | 44 | 45 | 46 | 47 | wgzhao 48 | wgzhao 49 | https://github.com/wgzhao 50 | 51 | developer 52 | 53 | 54 | 55 | 56 | 57 | 58 | io.trino 59 | trino-spi 60 | ${trino.version} 61 | 62 | 63 | 64 | io.trino 65 | trino-main 66 | ${trino.version} 67 | 68 | 69 | 70 | io.airlift 71 | slice 72 | ${slice.version} 73 | 74 | 75 | 76 | com.alibaba.fastjson2 77 | fastjson2 78 | 2.0.7 79 | 80 | 81 | com.google.guava 82 | guava 83 | ${guava.version} 84 | 85 | 86 | 87 | javax.inject 88 | javax.inject 89 | ${javax.inject.version} 90 | 91 | 92 | 93 | io.airlift 94 | log 95 | ${io.airlift.log.version} 96 | 97 | 98 | 99 | joda-time 100 | joda-time 101 | ${jodatime.version} 102 | 103 | 104 | 105 | 106 | me.ihxq.projects 107 | phone-number-geo 108 | 1.0.8-202004 109 | 110 | 111 | org.slf4j 112 | slf4j-log4j12 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.lionsoul 120 | ip2region 121 | 1.7.2 122 | 123 | 124 | commons-io 125 | commons-io 126 | 2.8.0 127 | 128 | 129 | 130 | 131 | io.github.biezhi 132 | TinyPinyin 133 | 2.0.3.RELEASE 134 | 135 | 136 | 137 | 138 | org.junit.jupiter 139 | junit-jupiter-api 140 | 5.7.2 141 | test 142 | 143 | 144 | 145 | 146 | 147 | 148 | org.apache.maven.plugins 149 | maven-release-plugin 150 | 2.5.1 151 | 152 | @{project.version} 153 | true 154 | 155 | 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-assembly-plugin 160 | 3.3.0 161 | 162 | 163 | package.xml 164 | 165 | ${project.artifactId}-${project.version} 166 | 167 | 168 | 169 | release 170 | package 171 | 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-deploy-plugin 177 | 3.0.0-M1 178 | 179 | deploy-file 180 | 181 | 182 | github 183 | zip 184 | false 185 | https://maven.pkg.github.com/wgzhao/presto-udfs 186 | ${project.artifactId} 187 | ${project.groupId} 188 | ${project.version} 189 | target/${project.artifactId}-${project.version}-release.zip 190 | 191 | 192 | 193 | 194 | 195 | 196 | release 197 | 198 | 199 | 200 | org.apache.maven.plugins 201 | maven-source-plugin 202 | 2.4 203 | 204 | 205 | create-sources-jar 206 | 207 | jar-no-fork 208 | 209 | 210 | 211 | 212 | 213 | org.apache.maven.plugins 214 | maven-gpg-plugin 215 | 1.5 216 | 217 | 218 | sign-artifacts 219 | verify 220 | 221 | sign 222 | 223 | 224 | 225 | 226 | 227 | org.apache.maven.plugins 228 | maven-javadoc-plugin 229 | 3.3.0 230 | 231 | 232 | create-javadoc-jar 233 | 234 | jar 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | github 247 | GitHub Packages 248 | https://maven.pkg.github.com/wgzhao/presto-udfs 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/UdfPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.wgzhao.presto.udfs; 17 | 18 | import com.google.common.collect.ImmutableSet; 19 | import com.wgzhao.presto.udfs.scalar.BankFunction; 20 | import com.wgzhao.presto.udfs.scalar.ExtendedDateTimeFunctions; 21 | import com.wgzhao.presto.udfs.scalar.ExtendedMathematicaFunctions; 22 | import com.wgzhao.presto.udfs.scalar.ExtendedNumberFunctions; 23 | import com.wgzhao.presto.udfs.scalar.ExtendedStringFunctions; 24 | import com.wgzhao.presto.udfs.scalar.IP2RegionFunction; 25 | import com.wgzhao.presto.udfs.scalar.IPFunction; 26 | import com.wgzhao.presto.udfs.scalar.IdCardFunctions; 27 | import com.wgzhao.presto.udfs.scalar.List2Json; 28 | import com.wgzhao.presto.udfs.scalar.Map2Json; 29 | import com.wgzhao.presto.udfs.scalar.Mobile2Region; 30 | import com.wgzhao.presto.udfs.scalar.PinyinFunction; 31 | import com.wgzhao.presto.udfs.scalar.TradeDateFunctions; 32 | import com.wgzhao.presto.udfs.scalar.XmlFunctions; 33 | import com.wgzhao.presto.udfs.window.FirstNonNullValueFunction; 34 | import com.wgzhao.presto.udfs.window.LastNonNullValueFunction; 35 | import io.trino.spi.Plugin; 36 | 37 | import java.util.Set; 38 | 39 | public class UdfPlugin 40 | implements Plugin 41 | { 42 | @Override 43 | public Set> getFunctions() 44 | { 45 | return ImmutableSet.>builder() 46 | .add(ExtendedMathematicaFunctions.class) 47 | .add(ExtendedDateTimeFunctions.class) 48 | .add(ExtendedStringFunctions.class) 49 | .add(FirstNonNullValueFunction.class) 50 | .add(LastNonNullValueFunction.class) 51 | .add(ExtendedNumberFunctions.class) 52 | .add(IP2RegionFunction.class) 53 | .add(Mobile2Region.class) 54 | .add(Map2Json.class) 55 | .add(List2Json.class) 56 | .add(TradeDateFunctions.class) 57 | .add(IPFunction.class) 58 | .add(IdCardFunctions.class) 59 | .add(XmlFunctions.class) 60 | .add(BankFunction.class) 61 | .add(PinyinFunction.class) 62 | .build(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/dto/IsoDto.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.dto; 2 | 3 | /** 4 | * Created by Administrator on 2020/3/23. 5 | */ 6 | public class IsoDto 7 | { 8 | 9 | private String cn; 10 | 11 | private String en; 12 | 13 | private String m2; 14 | 15 | private String m3; 16 | 17 | private String digit; 18 | 19 | public String getCn() 20 | { 21 | return cn; 22 | } 23 | 24 | public void setCn(String cn) 25 | { 26 | this.cn = cn; 27 | } 28 | 29 | public String getEn() 30 | { 31 | return en; 32 | } 33 | 34 | public void setEn(String en) 35 | { 36 | this.en = en; 37 | } 38 | 39 | public String getM2() 40 | { 41 | return m2; 42 | } 43 | 44 | public void setM2(String m2) 45 | { 46 | this.m2 = m2; 47 | } 48 | 49 | public String getM3() 50 | { 51 | return m3; 52 | } 53 | 54 | public void setM3(String m3) 55 | { 56 | this.m3 = m3; 57 | } 58 | 59 | public String getDigit() 60 | { 61 | return digit; 62 | } 63 | 64 | public void setDigit(String digit) 65 | { 66 | this.digit = digit; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/BankFunction.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.scalar; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import io.airlift.slice.Slice; 5 | import io.trino.spi.function.Description; 6 | import io.trino.spi.function.ScalarFunction; 7 | import io.trino.spi.function.SqlNullable; 8 | import io.trino.spi.function.SqlType; 9 | import io.trino.spi.type.StandardTypes; 10 | import org.apache.commons.io.IOUtils; 11 | 12 | import java.io.IOException; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.Arrays; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.Objects; 19 | 20 | import static io.airlift.slice.Slices.utf8Slice; 21 | 22 | public class BankFunction 23 | { 24 | private static final String fileName = "/bankInfo.json"; 25 | private static final List checkLength = Arrays.asList(9, 8, 6, 5, 4, 3); 26 | private static Map cardToBankName = new HashMap<>(); 27 | private static Map cardToType = new HashMap<>(); 28 | private static Map cardToCode = new HashMap<>(); 29 | 30 | static { 31 | try { 32 | String jsonString = 33 | IOUtils.toString(Objects.requireNonNull(BankFunction.class.getResourceAsStream(fileName)), StandardCharsets.UTF_8); 34 | JSONObject jsonObject = JSONObject.parseObject(jsonString); 35 | cardToBankName = (Map) jsonObject.get("codeToBank"); 36 | cardToType = (Map) jsonObject.get("cardToType"); 37 | cardToCode = (Map) jsonObject.get("cardToCode"); 38 | } 39 | catch (IOException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | private BankFunction() {} 45 | 46 | private static String getCardCode(String card) 47 | { 48 | for (int len : checkLength) { 49 | if (card.length() >= len && cardToCode.containsKey(card.substring(0, len))) { 50 | return cardToCode.get(card.substring(0, len)); 51 | } 52 | } 53 | return null; 54 | } 55 | 56 | @Description("get bank name from card number") 57 | @ScalarFunction("udf_bank_name") 58 | @SqlType(StandardTypes.VARCHAR) 59 | public static @SqlNullable 60 | Slice getBankName(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice cardNo) 61 | { 62 | if (cardNo == null || cardNo.toStringUtf8().isEmpty()) { 63 | return null; 64 | } 65 | String code = getCardCode(cardNo.toStringUtf8()); 66 | if (code == null) { 67 | return null; 68 | } 69 | return utf8Slice(cardToBankName.get(code)); 70 | } 71 | 72 | @Description("get bank code from card number") 73 | @ScalarFunction("udf_bank_code") 74 | @SqlType(StandardTypes.VARCHAR) 75 | public static @SqlNullable 76 | Slice getBankCode(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice cardNo) 77 | { 78 | if (cardNo == null || cardNo.toStringUtf8().isEmpty()) { 79 | return null; 80 | } 81 | String code = getCardCode(cardNo.toStringUtf8()); 82 | return code == null ? null : utf8Slice(code); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/ExtendedDateTimeFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.wgzhao.presto.udfs.scalar; 19 | 20 | import io.airlift.slice.Slice; 21 | import io.trino.spi.function.Description; 22 | import io.trino.spi.function.ScalarFunction; 23 | import io.trino.spi.function.SqlNullable; 24 | import io.trino.spi.function.SqlType; 25 | import io.trino.spi.type.StandardTypes; 26 | 27 | import java.sql.Timestamp; 28 | import java.time.ZoneId; 29 | 30 | import static io.airlift.slice.Slices.utf8Slice; 31 | import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; 32 | 33 | public class ExtendedDateTimeFunctions 34 | { 35 | private ExtendedDateTimeFunctions() {} 36 | 37 | @Description("given timestamp in UTC and converts to given timezone") 38 | @ScalarFunction("from_utc_timestamp") 39 | @SqlType(StandardTypes.TIMESTAMP) 40 | public static long fromUtcTimestamp(@SqlType(StandardTypes.TIMESTAMP) long timestamp, @SqlType(StandardTypes.VARCHAR) Slice inputZoneId) 41 | { 42 | ZoneId zoneId = ZoneId.of(inputZoneId.toStringUtf8(), ZoneId.SHORT_IDS); 43 | long offsetTimestamp = packDateTimeWithZone(timestamp, zoneId.toString()); 44 | return timestamp + ((PrestoDateTimeFunctions.timeZoneHourFromTimestampWithTimeZone(offsetTimestamp) * 60 45 | + PrestoDateTimeFunctions.timeZoneMinuteFromTimestampWithTimeZone(offsetTimestamp)) * 60) * 1000; 46 | } 47 | 48 | @Description("given timestamp (in varchar) in UTC and converts to given timezone") 49 | @ScalarFunction("from_utc_timestamp") 50 | @SqlType(StandardTypes.TIMESTAMP) 51 | public static long fromUtcTimestamp(@SqlType(StandardTypes.VARCHAR) Slice inputTimestamp, @SqlType(StandardTypes.VARCHAR) Slice inputZoneId) 52 | { 53 | Timestamp javaTimestamp = Timestamp.valueOf(inputTimestamp.toStringUtf8()); 54 | ZoneId zoneId = ZoneId.of(inputZoneId.toStringUtf8(), ZoneId.SHORT_IDS); 55 | long offsetTimestamp = packDateTimeWithZone(javaTimestamp.getTime(), zoneId.toString()); 56 | return javaTimestamp.getTime() + ((PrestoDateTimeFunctions.timeZoneHourFromTimestampWithTimeZone(offsetTimestamp) * 60 57 | + PrestoDateTimeFunctions.timeZoneMinuteFromTimestampWithTimeZone(offsetTimestamp)) * 60) * 1000; 58 | } 59 | 60 | @Description("given timestamp in a timezone convert it to UTC") 61 | @ScalarFunction("to_utc_timestamp") 62 | @SqlType(StandardTypes.TIMESTAMP) 63 | public static long toUtcTimestamp(@SqlType(StandardTypes.TIMESTAMP) long timestamp, @SqlType(StandardTypes.VARCHAR) Slice inputZoneId) 64 | { 65 | ZoneId zoneId = ZoneId.of(inputZoneId.toStringUtf8(), ZoneId.SHORT_IDS); 66 | long offsetTimestamp = packDateTimeWithZone(timestamp, zoneId.toString()); 67 | return timestamp - ((PrestoDateTimeFunctions.timeZoneHourFromTimestampWithTimeZone(offsetTimestamp) * 60 68 | + PrestoDateTimeFunctions.timeZoneMinuteFromTimestampWithTimeZone(offsetTimestamp)) * 60) * 1000; 69 | } 70 | 71 | @Description("given timestamp (in varchar) in a timezone convert it to UTC") 72 | @ScalarFunction("to_utc_timestamp") 73 | @SqlType(StandardTypes.TIMESTAMP) 74 | public static long toUtcTimestamp(@SqlType(StandardTypes.VARCHAR) Slice inputTimestamp, @SqlType(StandardTypes.VARCHAR) Slice inputZoneId) 75 | { 76 | Timestamp javaTimestamp = Timestamp.valueOf(inputTimestamp.toStringUtf8()); 77 | ZoneId zoneId = ZoneId.of(inputZoneId.toStringUtf8(), ZoneId.SHORT_IDS); 78 | long offsetTimestamp = packDateTimeWithZone(javaTimestamp.getTime(), zoneId.toString()); 79 | return javaTimestamp.getTime() - ((PrestoDateTimeFunctions.timeZoneHourFromTimestampWithTimeZone(offsetTimestamp) * 60 80 | + PrestoDateTimeFunctions.timeZoneMinuteFromTimestampWithTimeZone(offsetTimestamp)) * 60) * 1000; 81 | } 82 | 83 | @Description("Convert a timestamp string to yyyyMMdd format string") 84 | @ScalarFunction(value = "to_yyyymmdd", alias = {"toyyyymmdd"}) 85 | @SqlType(StandardTypes.VARCHAR) 86 | public 87 | static @SqlNullable 88 | Slice toDate8(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice inputTimestamp) 89 | { 90 | if (inputTimestamp == null || inputTimestamp.toStringUtf8().isEmpty()) { 91 | return null; 92 | } 93 | String originTs = inputTimestamp.toStringUtf8(); 94 | if (originTs.length() < 8) { 95 | return null; 96 | } 97 | return utf8Slice(originTs.replace("-", "").substring(0, 8)); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/ExtendedMathematicaFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.wgzhao.presto.udfs.scalar; 19 | 20 | import io.trino.spi.function.Description; 21 | import io.trino.spi.function.ScalarFunction; 22 | import io.trino.spi.function.SqlNullable; 23 | import io.trino.spi.function.SqlType; 24 | import io.trino.spi.type.StandardTypes; 25 | 26 | public class ExtendedMathematicaFunctions 27 | { 28 | private ExtendedMathematicaFunctions() {} 29 | 30 | @SqlNullable 31 | @Description("Returns the positive value of a mod b.") 32 | @ScalarFunction("udf_pmod") 33 | @SqlType(StandardTypes.BIGINT) 34 | public static Long positiveModulus(@SqlType(StandardTypes.BIGINT) long a, @SqlType(StandardTypes.BIGINT) long b) 35 | { 36 | if (b == 0) { 37 | return null; 38 | } 39 | return java.lang.Math.floorMod(a, b); 40 | } 41 | 42 | @SqlNullable 43 | @Description("Returns the positive value of a mod b ") 44 | @ScalarFunction("udf_pmod") 45 | @SqlType(StandardTypes.DOUBLE) 46 | public static Double positiveModulusDouble(@SqlType(StandardTypes.DOUBLE) double a, @SqlType(StandardTypes.DOUBLE) double b) 47 | { 48 | if (b == 0) { 49 | return null; 50 | } 51 | 52 | if ((a >= 0 && b > 0) || (a <= 0 && b < 0)) { 53 | return a % b; 54 | } 55 | else { 56 | return (a % b) + b; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/ExtendedNumberFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.wgzhao.presto.udfs.scalar; 19 | 20 | import io.airlift.slice.Slice; 21 | import io.trino.spi.function.Description; 22 | import io.trino.spi.function.ScalarFunction; 23 | import io.trino.spi.function.SqlNullable; 24 | import io.trino.spi.function.SqlType; 25 | import io.trino.spi.type.StandardTypes; 26 | 27 | import java.util.Map; 28 | 29 | import static io.airlift.slice.Slices.utf8Slice; 30 | import static java.util.Map.entry; 31 | 32 | /** 33 | * 阿拉伯数字和中文数字互转 34 | */ 35 | public class ExtendedNumberFunctions 36 | { 37 | //存放数量级中文数字信息 {十,百。。。亿。。。} 38 | private static final Map magnitudeMap = Map.ofEntries( 39 | entry("十", 10L), 40 | entry("拾", 10L), 41 | entry("百", 100L), 42 | entry("佰", 100L), 43 | entry("千", 1000L), 44 | entry("仟", 1000L), 45 | entry("万", 10000L), 46 | entry("亿", 100000000L), 47 | entry("兆", 1000000000000L), 48 | entry("京", 10000000000000000L) 49 | ); 50 | //存放0~9基本中文数字信息, {一,二。。。九,零} 51 | private static final Map dataMap = Map.ofEntries( 52 | entry("一", 1L), 53 | entry("二", 2L), 54 | entry("三", 3L), 55 | entry("四", 4L), 56 | entry("五", 5L), 57 | entry("六", 6L), 58 | entry("七", 7L), 59 | entry("八", 8L), 60 | entry("九", 9L), 61 | entry("零", 0L), 62 | entry("壹", 1L), 63 | entry("贰", 2L), 64 | entry("叁", 3L), 65 | entry("肆", 4L), 66 | entry("伍", 5L), 67 | entry("陆", 6L), 68 | entry("柒", 7L), 69 | entry("捌", 8L), 70 | entry("玖", 9L)); 71 | 72 | private ExtendedNumberFunctions() {} 73 | 74 | /** 75 | * 从后往前遍历字符串的方式将中文数字转换为阿拉伯数字 76 | * 77 | * @param chStr 源字符串 78 | * @return 转换后的阿拉伯数字 79 | */ 80 | @Description("convert chinese number to Arabia number") 81 | @ScalarFunction("udf_ch2num") 82 | @SqlType(StandardTypes.BIGINT) 83 | public @SqlNullable 84 | static Long convertChineseNumberToArabiaNumber(@SqlType(StandardTypes.VARCHAR) Slice chStr) 85 | { 86 | if (chStr == null || "".equals(chStr.toStringUtf8())) { 87 | return null; 88 | } 89 | String inputStr = chStr.toStringUtf8(); 90 | //存储遇到该数字前的最大一个数量值,这个值是累乘之前所有数量级, 91 | //比如二百万,到二的时候最高数量级就是100*10000 92 | long currentMaxLevel = 1L; 93 | //存储之前一次执行过乘操作的数量级 94 | long previousOpeMagnitude = 1L; 95 | //存储当前字符所对应的数量级 96 | long currentMagnitude = 1L; 97 | //存储当前所有字符仲最大的单个字符的数量级, 98 | long maxMagnitude = 1L; 99 | 100 | long sumVal = 0L; 101 | 102 | int len = inputStr.length(); 103 | //倒序循环整个字符串,从最低位开始计算整个数值 104 | for (int i = len - 1; i >= 0; i--) { 105 | String currentTxt = String.valueOf(inputStr.charAt(i)); 106 | 107 | //如果当前值是数量级 108 | if (magnitudeMap.containsKey(currentTxt)) { 109 | currentMagnitude = magnitudeMap.get(currentTxt); 110 | //如果第一位是一个数量级(比如十二), 将当前值相加 111 | if (i == 0) { 112 | sumVal = sumVal + currentMagnitude; 113 | return sumVal; 114 | } 115 | //比较当前数量级与当前最大数量值,如果大于当前最大值,将当然最大数量值更新为当前数量级 116 | if (currentMagnitude > currentMaxLevel) { 117 | currentMaxLevel = currentMagnitude; 118 | } 119 | else { 120 | if (currentMagnitude < maxMagnitude && currentMagnitude > previousOpeMagnitude) { 121 | //如果当前数量级小于当前最大数量级并且大于之前的数量级,比如二十五万五百亿,抵达"万"的时候因为之前的百 122 | //已经与亿相乘,所以应该除以之前的百才能得到当前真正的最大数量值 123 | currentMaxLevel = currentMaxLevel * currentMagnitude / previousOpeMagnitude; 124 | } 125 | else { 126 | currentMaxLevel = currentMaxLevel * currentMagnitude; 127 | } 128 | previousOpeMagnitude = currentMagnitude; 129 | } 130 | //将当前最大单数量级更新为当前数量级 131 | if (currentMagnitude > maxMagnitude) { 132 | maxMagnitude = currentMagnitude; 133 | } 134 | } 135 | else if (dataMap.containsKey(currentTxt)) { 136 | //如果是0~9之间的数字,与前面一位数量级相乘,并累加到当前sumVal 137 | long data = dataMap.get(currentTxt); 138 | if (data != 0) { 139 | sumVal = sumVal + data * currentMaxLevel; 140 | } 141 | } 142 | else { 143 | // illegal value 144 | return null; 145 | } 146 | } 147 | return sumVal; 148 | } 149 | 150 | @Description("convert Chinese number to Arabia number") 151 | @ScalarFunction("udf_num2ch") 152 | @SqlType(StandardTypes.VARCHAR) 153 | public static Slice convertArabiaNumberToChineseNumber(@SqlType(StandardTypes.VARCHAR) Slice value) 154 | { 155 | if (value == null || "".equals(value.toStringUtf8())) { 156 | return null; 157 | } 158 | long inputValue = Long.parseLong(value.toStringUtf8()); 159 | return numberToChinese(inputValue, false); 160 | } 161 | 162 | @Description("convert Chinese number to Arabia number") 163 | @ScalarFunction("udf_num2ch") 164 | @SqlType(StandardTypes.VARCHAR) 165 | public static Slice convertArabiaNumberToChineseNumber(@SqlType(StandardTypes.BIGINT) long value) 166 | { 167 | return numberToChinese(value, false); 168 | } 169 | 170 | @Description("convert Chinese number to Arabia number") 171 | @ScalarFunction("udf_num2ch") 172 | @SqlType(StandardTypes.VARCHAR) 173 | public static Slice convertArabiaNumberToChineseNumber(@SqlType(StandardTypes.BIGINT) long value, 174 | @SqlType(StandardTypes.BOOLEAN) boolean flag) 175 | { 176 | return numberToChinese(value, flag); 177 | } 178 | 179 | @Description("convert Chinese number to Arabia number") 180 | @ScalarFunction("udf_num2ch") 181 | @SqlType(StandardTypes.VARCHAR) 182 | public static Slice convertArabiaNumberToChineseNumber(@SqlType(StandardTypes.VARCHAR) Slice value, 183 | @SqlType(StandardTypes.BOOLEAN) boolean flag) 184 | { 185 | if (value == null || "".equals(value.toStringUtf8())) { 186 | return null; 187 | } 188 | 189 | long inputValue = Long.parseLong(value.toStringUtf8()); 190 | return numberToChinese(inputValue, flag); 191 | } 192 | 193 | private static Slice numberToChinese(long value, boolean flag) 194 | { 195 | int pos = flag ? 1 : 0; 196 | final String[][] units = { 197 | {"", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟",}, 198 | {"", "十", "百", "千", "万", "十", "百", "千", "亿", "十", "百", "千",}}; 199 | final String[][] nums = { 200 | {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖",}, 201 | {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九",}}; 202 | StringBuilder result = new StringBuilder(); //转译结果 203 | 204 | for (int i = String.valueOf(value).length() - 1; i >= 0; i--) { 205 | //value / Math.pow(10, i) 截位匹配单位 206 | int r = (int) (value / Math.pow(10, i)); 207 | result.append(nums[pos][r % 10]).append(units[pos][i]); 208 | } 209 | 210 | //匹配字符串中的 "零[十, 百, 千]" 替换为 "零" 211 | result = new StringBuilder(result.toString().replaceAll("零[拾, 佰, 仟]", "零")); //匹配字符串中的 "零[十, 百, 千]" 替换为 "零" 212 | result = new StringBuilder(result.toString().replaceAll("零[十, 百, 千]", "零")); //匹配字符串中的 "零[十, 百, 千]" 替换为 "零" 213 | result = new StringBuilder(result.toString().replaceAll("零+", "零")); //匹配字符串中的1或多个 "零" 替换为 "零" 214 | result = new StringBuilder(result.toString().replaceAll("零([万, 亿])", "$1")); 215 | result = new StringBuilder(result.toString().replaceAll("亿万", "亿")); //亿万位拼接时发生的特殊情况 216 | 217 | if (result.toString().startsWith("壹拾") || result.toString().startsWith("一十")) { 218 | //判断是否以 "一十" 开头 如果是截取第一个字符 219 | result = new StringBuilder(result.substring(1)); 220 | } 221 | 222 | if (result.toString().endsWith("零")) { 223 | //判断是否以 "零" 结尾 如果是截取除 "零" 外的字符 224 | result = new StringBuilder(result.substring(0, result.length() - 1)); 225 | } 226 | return utf8Slice(result.toString()); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/ExtendedStringFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.wgzhao.presto.udfs.scalar; 19 | 20 | import io.airlift.slice.Slice; 21 | import io.trino.spi.function.Description; 22 | import io.trino.spi.function.ScalarFunction; 23 | import io.trino.spi.function.SqlNullable; 24 | import io.trino.spi.function.SqlType; 25 | import io.trino.spi.type.StandardTypes; 26 | 27 | import javax.script.ScriptEngine; 28 | import javax.script.ScriptEngineManager; 29 | import javax.script.ScriptException; 30 | 31 | import static io.airlift.slice.Slices.utf8Slice; 32 | 33 | public class ExtendedStringFunctions 34 | { 35 | private static final String[][] nums = { 36 | {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖",}, 37 | {"〇", "一", "二", "三", "四", "五", "六", "七", "八", "九",}}; 38 | 39 | private ExtendedStringFunctions() {} 40 | 41 | @Description("the implement of javascript eval function") 42 | @ScalarFunction("udf_eval") 43 | @SqlType(StandardTypes.DOUBLE) 44 | public @SqlNullable 45 | static Double eval(@SqlType(StandardTypes.VARCHAR) Slice estr) 46 | { 47 | if (estr == null || "".equals(estr.toStringUtf8())) { 48 | return null; 49 | } 50 | 51 | String evalStr = estr.toStringUtf8(); 52 | 53 | ScriptEngineManager manager = new ScriptEngineManager(); 54 | try { 55 | // Adopt OpenJdk does not work 56 | ScriptEngine engine = manager.getEngineByName("js"); 57 | if (engine == null ) { 58 | // current jdk does not support it 59 | return null; 60 | } 61 | return Double.parseDouble(engine.eval(evalStr).toString()); 62 | } 63 | catch (ScriptException ignored) { 64 | return null; 65 | } 66 | 67 | } 68 | 69 | @Description("convert number string to chinese number string") 70 | @ScalarFunction("udf_to_chinese") 71 | @SqlType(StandardTypes.VARCHAR) 72 | public static Slice convertDigitalToChinese(@SqlType(StandardTypes.VARCHAR) Slice slice) 73 | { 74 | if (slice == null || "".equals(slice.toStringUtf8())) { 75 | return null; 76 | } 77 | String res = toChinese(slice.toStringUtf8(), false); 78 | if (res == null) { 79 | return null; 80 | } 81 | return utf8Slice(res); 82 | } 83 | 84 | @Description("convert number string to chinese number string") 85 | @ScalarFunction("udf_to_chinese") 86 | @SqlType(StandardTypes.VARCHAR) 87 | public static Slice convertDigitalToChinese(@SqlType(StandardTypes.VARCHAR) Slice slice, @SqlType(StandardTypes.BOOLEAN) boolean flag) 88 | { 89 | if (slice == null || "".equals(slice.toStringUtf8())) { 90 | return null; 91 | } 92 | 93 | String res = toChinese(slice.toStringUtf8(), flag); 94 | 95 | if (res == null) { 96 | return null; 97 | } 98 | return utf8Slice(res); 99 | } 100 | 101 | private static String toChinese(String str, boolean flag) 102 | { 103 | int pos = flag ? 1 : 0; 104 | int curDigit; 105 | StringBuilder sb = new StringBuilder(); 106 | try { 107 | for (int i = 0; i < str.length(); i++) { 108 | curDigit = Integer.parseInt(str.charAt(i) + ""); 109 | sb.append(nums[pos][curDigit]); 110 | } 111 | return sb.toString(); 112 | } 113 | catch (NumberFormatException e) { 114 | return null; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/IP2RegionFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wgzhao.presto.udfs.scalar; 18 | 19 | import com.wgzhao.presto.udfs.utils.IPSearcher; 20 | import com.wgzhao.presto.udfs.utils.IsoSearcher; 21 | import io.airlift.slice.Slice; 22 | import io.airlift.slice.Slices; 23 | import io.trino.spi.function.Description; 24 | import io.trino.spi.function.ScalarFunction; 25 | import io.trino.spi.function.SqlNullable; 26 | import io.trino.spi.function.SqlType; 27 | import io.trino.spi.type.StandardTypes; 28 | import org.lionsoul.ip2region.DataBlock; 29 | 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | 33 | import static io.airlift.slice.Slices.utf8Slice; 34 | import static java.util.Map.entry; 35 | 36 | public class IP2RegionFunction 37 | { 38 | private static final String FILE_NAME = "/ip2region.db"; 39 | private static final Map gMap = Map.ofEntries( 40 | entry("country", 0), 41 | entry("g",0), 42 | entry("province",2), 43 | entry("p",2), 44 | entry("city",3), 45 | entry("c",3), 46 | entry("isp",4), 47 | entry("i",4) 48 | ); 49 | 50 | private static final Map codeMap = Map.ofEntries( 51 | entry("en", 0), 52 | entry("m2", 1), 53 | entry("m3", 2), 54 | entry("digit", 3) 55 | ); 56 | 57 | private static final io.airlift.log.Logger logger = io.airlift.log.Logger.get(IP2RegionFunction.class); 58 | 59 | private IP2RegionFunction() {} 60 | 61 | private static String ipSearch(String ip, String segment) 62 | { 63 | String result = null; 64 | IPSearcher searcher = IPSearcher.getInstance(); 65 | 66 | DataBlock dataBlock = searcher.lookup(ip); 67 | Map ipInfo = new HashMap<>(); 68 | if (dataBlock != null) { 69 | result = dataBlock.getRegion(); 70 | } 71 | 72 | if (result == null) { 73 | return null; 74 | } 75 | //如果不指定返回部分,则返回全部 76 | if (segment == null) { 77 | return result.replace("0", ""); 78 | } 79 | // 城市Id|国家|区域|省份|城市|ISP 80 | String[] arrInfo = result.split("\\|"); 81 | gMap.forEach((k, v) -> ipInfo.put(k, arrInfo[v].equals("0") ? null : arrInfo[v])); 82 | 83 | //检查是否是需要返回区域信息 84 | if (gMap.containsKey(segment)) { 85 | return ipInfo.getOrDefault(segment, null); 86 | } 87 | 88 | //检查是否是需要返回国际编码信息 89 | if (codeMap.containsKey(segment)) { 90 | return IsoSearcher.getInstance().searcher(ipInfo.get("g"), segment); 91 | } 92 | //都不是,说明指定的返回编码出现未知情况,直接返回为空 93 | logger.warn("invalid segment values:" + segment); 94 | return null; 95 | } 96 | 97 | @Description("get region from IP Address") 98 | @ScalarFunction("udf_ip2region") 99 | @SqlType(StandardTypes.VARCHAR) 100 | public static @SqlNullable 101 | Slice ip2region(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice ip) 102 | { 103 | if (ip == null || ip.toStringUtf8().trim().isEmpty()) { 104 | return null; 105 | } 106 | if (!isIpAddress(ip.toStringUtf8())) { 107 | logger.warn("invalid IP address: " + ip.toStringUtf8()); 108 | return null; 109 | } 110 | String res = ipSearch(ip.toStringUtf8(), null); 111 | if (res == null) { 112 | return Slices.EMPTY_SLICE; 113 | } 114 | return utf8Slice(res); 115 | } 116 | 117 | @Description("get region from IP Address") 118 | @ScalarFunction("udf_ip2region") 119 | @SqlType(StandardTypes.VARCHAR) 120 | public static @SqlNullable 121 | Slice ip2region(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice ip, @SqlNullable @SqlType(StandardTypes.VARCHAR) Slice segment) 122 | { 123 | if (ip == null || segment == null) { 124 | return null; 125 | } 126 | if (ip.toStringUtf8().trim().isEmpty() || segment.toStringUtf8().trim().isEmpty()) { 127 | return null; 128 | } 129 | if (! isIpAddress(ip.toStringUtf8())) { 130 | logger.warn("invalid IP address: " + ip.toStringUtf8()); 131 | return null; 132 | } 133 | String result; 134 | result = ipSearch(ip.toStringUtf8(), segment.toStringUtf8()); 135 | if (result == null) { 136 | return Slices.EMPTY_SLICE; 137 | } 138 | return utf8Slice(result); 139 | } 140 | 141 | private static boolean isIpAddress(String ip) 142 | { 143 | try { 144 | for (String part : ip.split("\\.")) { 145 | Integer.parseInt(part); 146 | } 147 | return true; 148 | } 149 | catch (NumberFormatException ignored) { 150 | return false; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/IPFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.wgzhao.presto.udfs.scalar; 19 | 20 | import io.airlift.slice.Slice; 21 | import io.trino.spi.function.Description; 22 | import io.trino.spi.function.ScalarFunction; 23 | import io.trino.spi.function.SqlNullable; 24 | import io.trino.spi.function.SqlType; 25 | import io.trino.spi.type.StandardTypes; 26 | 27 | import java.io.IOException; 28 | import java.net.InetAddress; 29 | 30 | import static io.airlift.slice.Slices.utf8Slice; 31 | 32 | public class IPFunction 33 | { 34 | private IPFunction() {} 35 | 36 | @Description("get region from IP Address") 37 | @ScalarFunction("udf_ip2int") 38 | @SqlType(StandardTypes.BIGINT) 39 | public @SqlNullable 40 | static Long ip2int(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice ipStr) 41 | { 42 | if (ipStr == null || ipStr.toStringUtf8().equals("")) { 43 | return null; 44 | } 45 | String[] ip = ipStr.toStringUtf8().split("\\."); 46 | if (ip.length != 4) { 47 | return null; 48 | } 49 | long result = 0; 50 | try { 51 | for (String part : ip) { 52 | result = result << 8; 53 | result |= Integer.parseInt(part); 54 | } 55 | return result; 56 | } 57 | catch (NumberFormatException ignored) { 58 | return null; 59 | } 60 | } 61 | 62 | @Description("get region from IP Address") 63 | @ScalarFunction("udf_int2ip") 64 | @SqlType(StandardTypes.VARCHAR) 65 | public static Slice int2ip(@SqlNullable @SqlType(StandardTypes.INTEGER) Long ip) 66 | { 67 | if (ip == null || ip < 0) { 68 | return null; 69 | } 70 | 71 | try { 72 | InetAddress i = InetAddress.getByName(String.valueOf(ip)); 73 | String ipStr = i.getHostAddress(); 74 | return utf8Slice(ipStr); 75 | } 76 | catch (IOException ignored) { 77 | return null; 78 | } 79 | } 80 | 81 | public static void main(String[] args) 82 | { 83 | int a = 1; 84 | a |= 0; 85 | System.out.println(a); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/IdCardFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.wgzhao.presto.udfs.scalar; 19 | 20 | import com.wgzhao.presto.udfs.utils.IdCardUtil; 21 | import io.airlift.slice.Slice; 22 | import io.trino.spi.function.Description; 23 | import io.trino.spi.function.ScalarFunction; 24 | import io.trino.spi.function.SqlNullable; 25 | import io.trino.spi.function.SqlType; 26 | import io.trino.spi.type.StandardTypes; 27 | 28 | /** 29 | * 身份证校验,对于合法的身份证返回true,否则返回false,对于空身份证,则返回null 30 | */ 31 | public class IdCardFunctions 32 | { 33 | private IdCardFunctions() {} 34 | 35 | @Description("Check IdCard is valid or not") 36 | @ScalarFunction("udf_is_idcard") 37 | @SqlType(StandardTypes.BOOLEAN) 38 | public static boolean idCheck(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice idNo) 39 | { 40 | if (idNo == null) { 41 | return false; 42 | } 43 | return IdCardUtil.isValidCard(idNo.toStringUtf8()); 44 | } 45 | 46 | @Description("Check ID Card is valid or not, return the birthday if valid else 0") 47 | @ScalarFunction("id_check") 48 | @SqlType(StandardTypes.BIGINT) 49 | public static long idCheckWithBirthday(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice idNo) 50 | { 51 | if (idNo == null ) { 52 | return 0L; 53 | } 54 | String prettyId = idNo.toStringUtf8().trim(); 55 | if (prettyId.isEmpty()) { 56 | return 0L; 57 | } 58 | if (!IdCardUtil.isValidCard(idNo.toStringUtf8())) { 59 | return 0L; 60 | } 61 | String birthday = IdCardUtil.getBirth(idNo.toStringUtf8()); 62 | return birthday == null ? 0L : Long.parseLong(birthday); 63 | } 64 | 65 | /** 66 | * 提取身份证号码里的生日,格式为 yyyyMMdd, 如果给定的身份证为空或不合法,则返回为 NULL 67 | * 68 | * @param idNo 身份证号码,支持中国大陆身份证(15位和18位)以及港澳台的10位证件号码 69 | * @return 生日(code>yyyyMMdd) 70 | */ 71 | @Description("Extract birthday from valid ID card") 72 | @ScalarFunction("udf_get_birthday") 73 | @SqlType(StandardTypes.INTEGER) 74 | public @SqlNullable 75 | static Long getBirthFromIdCard(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice idNo) 76 | { 77 | if (idNo == null) { 78 | return null; 79 | } 80 | String prettyId = idNo.toStringUtf8().trim(); 81 | if (!IdCardUtil.isValidCard(prettyId)) { 82 | return null; 83 | } 84 | String birthday = IdCardUtil.getBirth(prettyId); 85 | if (birthday == null) { 86 | return null; 87 | } 88 | else { 89 | return Long.parseLong(birthday); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/List2Json.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.wgzhao.presto.udfs.scalar; 19 | 20 | import com.alibaba.fastjson2.JSON; 21 | import io.airlift.slice.Slice; 22 | import io.airlift.slice.Slices; 23 | import io.trino.spi.function.Description; 24 | import io.trino.spi.function.ScalarFunction; 25 | import io.trino.spi.function.SqlNullable; 26 | import io.trino.spi.function.SqlType; 27 | import io.trino.spi.type.StandardTypes; 28 | 29 | import java.io.IOException; 30 | 31 | import static io.airlift.slice.Slices.utf8Slice; 32 | 33 | public class List2Json 34 | { 35 | 36 | private List2Json() {} 37 | 38 | @Description("convert List to Json") 39 | @ScalarFunction("list_json") 40 | @SqlType(StandardTypes.JSON) 41 | public static @SqlNullable 42 | Slice ListToJson(@SqlNullable @SqlType(StandardTypes.ARRAY) Slice aList) 43 | throws IOException 44 | { 45 | String res = JSON.parseArray(aList.toString()).toString(); 46 | if (res == null) { 47 | return Slices.EMPTY_SLICE; 48 | } 49 | return utf8Slice(res); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/Map2Json.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.wgzhao.presto.udfs.scalar; 20 | 21 | import io.airlift.slice.Slice; 22 | import io.trino.spi.function.Description; 23 | import io.trino.spi.function.ScalarFunction; 24 | import io.trino.spi.function.SqlNullable; 25 | import io.trino.spi.function.SqlType; 26 | import io.trino.spi.type.StandardTypes; 27 | 28 | import static com.alibaba.fastjson2.JSON.toJSONString; 29 | import static io.airlift.slice.Slices.utf8Slice; 30 | 31 | public class Map2Json 32 | { 33 | private Map2Json() {} 34 | 35 | @Description("convert Map to Json") 36 | @ScalarFunction("map_json") 37 | @SqlType(StandardTypes.JSON) 38 | public static Slice MapToJson(@SqlNullable @SqlType(StandardTypes.MAP) Slice map) 39 | { 40 | String res; 41 | res = toJSONString(map); 42 | return utf8Slice(res); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/Mobile2Region.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wgzhao.presto.udfs.scalar; 18 | 19 | import com.wgzhao.presto.udfs.utils.MobileSearcher; 20 | import io.airlift.slice.Slice; 21 | import io.trino.spi.function.Description; 22 | import io.trino.spi.function.ScalarFunction; 23 | import io.trino.spi.function.SqlNullable; 24 | import io.trino.spi.function.SqlType; 25 | import io.trino.spi.type.StandardTypes; 26 | import me.ihxq.projects.pna.PhoneNumberInfo; 27 | 28 | import static io.airlift.slice.Slices.utf8Slice; 29 | 30 | public class Mobile2Region 31 | { 32 | 33 | private Mobile2Region() 34 | { 35 | } 36 | 37 | private static String mobileSearch(String phone, String segment) 38 | { 39 | MobileSearcher mobileSearcher = MobileSearcher.getInstance(); 40 | try { 41 | PhoneNumberInfo phoneInfo = mobileSearcher.lookup(phone); 42 | 43 | if (phoneInfo == null) { 44 | return null; 45 | } 46 | 47 | if (segment == null) { 48 | return phoneInfo.getAttribution().getProvince() + "|" 49 | + phoneInfo.getAttribution().getCity() + "|" 50 | + phoneInfo.getIsp().getCnName(); 51 | } 52 | String res; 53 | switch (segment) { 54 | case "province": 55 | case "p": 56 | res = phoneInfo.getAttribution().getProvince(); 57 | break; 58 | case "city": 59 | case "c": 60 | res = phoneInfo.getAttribution().getCity(); 61 | break; 62 | case "isp": 63 | case "i": 64 | res = phoneInfo.getIsp().getCnName(); 65 | break; 66 | default: 67 | res = ""; 68 | break; 69 | } 70 | return res; 71 | } 72 | catch (Exception e) { 73 | return null; 74 | } 75 | } 76 | 77 | @Description("get region from mobile phone") 78 | @ScalarFunction("udf_mobile2region") 79 | @SqlType(StandardTypes.VARCHAR) 80 | public static @SqlNullable 81 | Slice mobile2region(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice phone) 82 | { 83 | String result; 84 | if (phone == null || "".equals(phone.toStringUtf8().trim())) { 85 | return null; 86 | } 87 | result = mobileSearch(phone.toStringUtf8().trim(), null); 88 | if (result == null) { 89 | return null; 90 | } 91 | return utf8Slice(result); 92 | } 93 | 94 | @Description("get region from mobile phone") 95 | @ScalarFunction("udf_mobile2region") 96 | @SqlType(StandardTypes.VARCHAR) 97 | public static @SqlNullable 98 | Slice mobile2region(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice phone, 99 | @SqlNullable @SqlType(StandardTypes.VARCHAR) Slice segment) 100 | { 101 | String result; 102 | if (phone == null 103 | || "".equals(phone.toStringUtf8().trim()) 104 | || segment == null 105 | || "".equals(segment.toStringUtf8().trim())) { 106 | return null; 107 | } 108 | result = mobileSearch(phone.toStringUtf8().trim(), segment.toStringUtf8().trim()); 109 | if (result == null) { 110 | return null; 111 | } 112 | return utf8Slice(result); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/PinyinFunction.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.scalar; 2 | 3 | import com.github.promeg.pinyinhelper.Pinyin; 4 | import io.airlift.slice.Slice; 5 | import io.trino.spi.function.Description; 6 | import io.trino.spi.function.ScalarFunction; 7 | import io.trino.spi.function.SqlNullable; 8 | import io.trino.spi.function.SqlType; 9 | import io.trino.spi.type.StandardTypes; 10 | 11 | import static io.airlift.slice.Slices.utf8Slice; 12 | 13 | public class PinyinFunction 14 | { 15 | private PinyinFunction() {} 16 | 17 | @Description("convert chinese to pinyin") 18 | @ScalarFunction("udf_pinyin") 19 | @SqlType(StandardTypes.VARCHAR) 20 | public @SqlNullable static Slice pinyin(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice chinese) 21 | { 22 | if (chinese == null) { 23 | return null; 24 | } 25 | String chStr = chinese.toStringUtf8(); 26 | if (chStr.equals("")) { 27 | return utf8Slice(""); 28 | } 29 | 30 | return utf8Slice(Pinyin.toPinyin(chStr, "").toLowerCase()); 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/PrestoDateTimeFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | /* 20 | * Licensed under the Apache License, Version 2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | */ 32 | package com.wgzhao.presto.udfs.scalar; 33 | 34 | import org.joda.time.Chronology; 35 | import org.joda.time.DateTimeField; 36 | import org.joda.time.DateTimeFieldType; 37 | import org.joda.time.DurationField; 38 | import org.joda.time.DurationFieldType; 39 | import org.joda.time.field.DividedDateTimeField; 40 | import org.joda.time.field.OffsetDateTimeField; 41 | import org.joda.time.field.ScaledDurationField; 42 | 43 | import static com.wgzhao.presto.udfs.scalar.PrestoDateTimeZoneIndex.extractZoneOffsetMinutes; 44 | 45 | // This is copy of DateTimeFunctions because presto does not provide presto-main jars to plugins anymore 46 | public final class PrestoDateTimeFunctions 47 | { 48 | public static final DateTimeFieldType QUARTER_OF_YEAR = new QuarterOfYearDateTimeField(); 49 | 50 | private PrestoDateTimeFunctions() {} 51 | 52 | public static long timeZoneMinuteFromTimestampWithTimeZone(long timestampWithTimeZone) 53 | { 54 | return extractZoneOffsetMinutes(timestampWithTimeZone) % 60; 55 | } 56 | 57 | public static long timeZoneHourFromTimestampWithTimeZone(long timestampWithTimeZone) 58 | { 59 | return extractZoneOffsetMinutes(timestampWithTimeZone) / 60; 60 | } 61 | 62 | private static class QuarterOfYearDateTimeField 63 | extends DateTimeFieldType 64 | { 65 | private static final long serialVersionUID = -5677872459807379123L; 66 | 67 | private static final DurationFieldType QUARTER_OF_YEAR_DURATION_FIELD_TYPE = new QuarterOfYearDurationFieldType(); 68 | 69 | private QuarterOfYearDateTimeField() 70 | { 71 | super("quarterOfYear"); 72 | } 73 | 74 | @Override 75 | public DurationFieldType getDurationType() 76 | { 77 | return QUARTER_OF_YEAR_DURATION_FIELD_TYPE; 78 | } 79 | 80 | @Override 81 | public DurationFieldType getRangeDurationType() 82 | { 83 | return DurationFieldType.years(); 84 | } 85 | 86 | @Override 87 | public DateTimeField getField(Chronology chronology) 88 | { 89 | return new OffsetDateTimeField(new DividedDateTimeField(new OffsetDateTimeField(chronology.monthOfYear(), -1), QUARTER_OF_YEAR, 3), 1); 90 | } 91 | 92 | private static class QuarterOfYearDurationFieldType 93 | extends DurationFieldType 94 | { 95 | private static final long serialVersionUID = -8167713675442491871L; 96 | 97 | public QuarterOfYearDurationFieldType() 98 | { 99 | super("quarters"); 100 | } 101 | 102 | @Override 103 | public DurationField getField(Chronology chronology) 104 | { 105 | return new ScaledDurationField(chronology.months(), QUARTER_OF_YEAR_DURATION_FIELD_TYPE, 3); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/PrestoDateTimeZoneIndex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | /* 20 | * Licensed under the Apache License, Version 2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | */ 32 | package com.wgzhao.presto.udfs.scalar; 33 | 34 | import io.trino.spi.type.TimeZoneKey; 35 | import org.joda.time.DateTimeZone; 36 | import org.joda.time.chrono.ISOChronology; 37 | 38 | import static io.trino.spi.type.DateTimeEncoding.unpackMillisUtc; 39 | import static io.trino.spi.type.DateTimeEncoding.unpackZoneKey; 40 | import static io.trino.spi.type.TimeZoneKey.MAX_TIME_ZONE_KEY; 41 | import static io.trino.spi.type.TimeZoneKey.getTimeZoneKeys; 42 | 43 | // This is copy of PrestoDateTimeZoneIndex because presto does not provide presto-main jars to plugins anymore 44 | public final class PrestoDateTimeZoneIndex 45 | { 46 | private static final DateTimeZone[] DATE_TIME_ZONES; 47 | private static final ISOChronology[] CHRONOLOGIES; 48 | private static final int[] FIXED_ZONE_OFFSET; 49 | private static final int VARIABLE_ZONE = Integer.MAX_VALUE; 50 | 51 | private PrestoDateTimeZoneIndex() 52 | { 53 | } 54 | 55 | public static int extractZoneOffsetMinutes(long dateTimeWithTimeZone) 56 | { 57 | short zoneKey = unpackZoneKey(dateTimeWithTimeZone).getKey(); 58 | 59 | if (FIXED_ZONE_OFFSET[zoneKey] == VARIABLE_ZONE) { 60 | return DATE_TIME_ZONES[zoneKey].getOffset(unpackMillisUtc(dateTimeWithTimeZone)) / 60_000; 61 | } 62 | else { 63 | return FIXED_ZONE_OFFSET[zoneKey]; 64 | } 65 | } 66 | 67 | static { 68 | DATE_TIME_ZONES = new DateTimeZone[MAX_TIME_ZONE_KEY + 1]; 69 | CHRONOLOGIES = new ISOChronology[MAX_TIME_ZONE_KEY + 1]; 70 | FIXED_ZONE_OFFSET = new int[MAX_TIME_ZONE_KEY + 1]; 71 | for (TimeZoneKey timeZoneKey : getTimeZoneKeys()) { 72 | short zoneKey = timeZoneKey.getKey(); 73 | DateTimeZone dateTimeZone; 74 | try { 75 | dateTimeZone = DateTimeZone.forID(timeZoneKey.getId()); 76 | } 77 | catch (IllegalArgumentException e) { 78 | // This can stop this Class from loading and 79 | // any UDF calls using this will fail. 80 | continue; 81 | } 82 | DATE_TIME_ZONES[zoneKey] = dateTimeZone; 83 | CHRONOLOGIES[zoneKey] = ISOChronology.getInstance(dateTimeZone); 84 | if (dateTimeZone.isFixed() && dateTimeZone.getOffset(0) % 60_000 == 0) { 85 | FIXED_ZONE_OFFSET[zoneKey] = dateTimeZone.getOffset(0) / 60_000; 86 | } 87 | else { 88 | FIXED_ZONE_OFFSET[zoneKey] = VARIABLE_ZONE; 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/TradeDateFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2021 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.wgzhao.presto.udfs.scalar; 19 | 20 | import com.wgzhao.presto.udfs.utils.CloseDateUtil; 21 | import io.airlift.slice.Slice; 22 | import io.trino.spi.function.Description; 23 | import io.trino.spi.function.ScalarFunction; 24 | import io.trino.spi.function.SqlNullable; 25 | import io.trino.spi.function.SqlType; 26 | import io.trino.spi.type.StandardTypes; 27 | 28 | import java.io.IOException; 29 | import java.util.Map; 30 | import java.util.stream.Stream; 31 | 32 | import static com.alibaba.fastjson2.JSON.toJSONString; 33 | import static io.airlift.slice.Slices.utf8Slice; 34 | 35 | /** 36 | * common functions about stock exchange date 37 | */ 38 | public class TradeDateFunctions 39 | { 40 | private TradeDateFunctions() 41 | { 42 | } 43 | 44 | /** 45 | * 计算在给定日期后的N天内的第一个交易日 46 | *

47 | * 如果N是正数,则往后计算;如果是负数,则往前计算 48 | *

49 | * 示例: 50 | *

{@code
 51 |      *  select udf_add_normal_days('20210903', 4); -- 20210907
 52 |      *  select udf_add_normal_days('20210906', -1) -- 20210903
 53 |      * }
54 | * 55 | * @param baseDate 给定的日期, 格式为 yyyyMMdd 56 | * @param days 指定的天数,正数表示往后计算,负数表示往前计算 57 | * @return 交易日,如果没有找到,则返回为 null 58 | */ 59 | @Description("add days from base date") 60 | @ScalarFunction("udf_add_normal_days") 61 | @SqlType(StandardTypes.VARCHAR) 62 | public static @SqlNullable 63 | Slice udfAddNormalDays(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice baseDate, 64 | @SqlNullable @SqlType(StandardTypes.INTEGER) Long days) 65 | { 66 | if (baseDate == null || "".equals(baseDate.toStringUtf8())) { 67 | return null; 68 | } 69 | String res; 70 | res = CloseDateUtil.getFirstPeriodExchangeDay(baseDate.toStringUtf8(), days.intValue()); 71 | if (res == null) { 72 | return null; 73 | } 74 | return utf8Slice(res); 75 | } 76 | 77 | /** 78 | * 返回两个给定的第一个日期(包括)和第二个日期(包括)之间有多少个交易日 79 | * 给定的两个日期有任意一个为 null 或为空,均返回为 null 80 | * 81 | * @param d1 第一个日期 82 | * @param d2 第二个日期 83 | * @return 交易日数量 84 | */ 85 | @Description("count the number of trade date between given two dates") 86 | @ScalarFunction("udf_count_trade_days") 87 | @SqlType(StandardTypes.INTEGER) 88 | public static @SqlNullable 89 | Long udfCountTradeDays(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice d1, 90 | @SqlNullable @SqlType(StandardTypes.VARCHAR) Slice d2) 91 | { 92 | if (d1 == null || d2 == null || "".equals(d1.toStringUtf8()) || "".equals(d2.toStringUtf8())) { 93 | return null; 94 | } 95 | 96 | return (long) CloseDateUtil.getCountExchangeDay(d1.toStringUtf8(), d2.toStringUtf8()); 97 | } 98 | 99 | /* 100 | -- yyyyMMdd格式,加减days个交易日 101 | */ 102 | @Description("add days from base date with yyyyMMdd format") 103 | @ScalarFunction("udf_add_trade_days") 104 | @SqlType(StandardTypes.VARCHAR) 105 | public static @SqlNullable 106 | Slice udfAddTradeDays(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice s, @SqlNullable @SqlType(StandardTypes.INTEGER) Long days) 107 | { 108 | if (s == null || "".equals(s.toStringUtf8())) { 109 | return null; 110 | } 111 | String res; 112 | res = CloseDateUtil.getPeriodExchangeDay(s.toStringUtf8(), days.intValue()); 113 | if (res == null) { 114 | return null; 115 | } 116 | return utf8Slice(res); 117 | } 118 | 119 | /* 120 | 传入以逗号分隔的数据数组,返回这段数据的最大回撤率。 121 | */ 122 | @Description("get the max drawdown rate") 123 | @ScalarFunction("udf_max_draw_down") 124 | @SqlType(StandardTypes.DOUBLE) 125 | public static @SqlNullable 126 | Double udfMaxDrawDown(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice s) 127 | throws IOException 128 | { 129 | if (s == null || s.toStringUtf8().trim().isEmpty()) { 130 | return null; 131 | } 132 | String[] arry = s.toStringUtf8().split(","); 133 | if (arry.length == 0) { 134 | return 0.0; 135 | } 136 | double[] da; 137 | try { 138 | da = Stream.of(arry).map( 139 | x -> { 140 | if (x == null || x.trim().isEmpty()) { 141 | return "0.0"; 142 | } 143 | else { 144 | return x; 145 | } 146 | }) 147 | .mapToDouble(Double::parseDouble).toArray(); 148 | } 149 | catch (Exception e) { 150 | throw new IOException("参数中包含非数字", e); 151 | } 152 | double mdd = 0.0; 153 | double peak = da[0]; 154 | double dd; 155 | for (double v : da) { 156 | if (v > peak) { 157 | peak = v; 158 | } 159 | dd = (peak - v) / peak; 160 | if (dd > mdd) { 161 | mdd = dd; 162 | } 163 | } 164 | return mdd; 165 | } 166 | 167 | /* 168 | -- 获取日期的上一个交易日 169 | */ 170 | @Description("get the last exchange day before specified date") 171 | @ScalarFunction("udf_last_trade_date") 172 | @SqlType(StandardTypes.VARCHAR) 173 | public static @SqlNullable 174 | Slice udfLastTradeDate(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice s) 175 | { 176 | if (s == null || "".equals(s.toStringUtf8())) { 177 | return null; 178 | } 179 | 180 | String res = CloseDateUtil.getLastExchangeDay(s.toStringUtf8()); 181 | if (res == null) { 182 | return null; 183 | } 184 | return utf8Slice(res); 185 | } 186 | 187 | @Description("is close day or not") 188 | @ScalarFunction("udf_is_trade_date") 189 | @SqlType(StandardTypes.BOOLEAN) 190 | public static @SqlNullable 191 | Boolean udfIsTradeDate(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice s) 192 | { 193 | if (s == null || "".equals(s.toStringUtf8())) { 194 | return false; 195 | } 196 | return CloseDateUtil.isCloseDate(s.toStringUtf8()); 197 | } 198 | 199 | @Description("convert map to json") 200 | @ScalarFunction("map_to_json") 201 | @SqlType(StandardTypes.VARCHAR) 202 | public static @SqlNullable 203 | Slice udfMapToJson(@SqlNullable @SqlType(StandardTypes.VARCHAR) Map smap) 204 | { 205 | String res = toJSONString(smap); 206 | return utf8Slice(res); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/scalar/XmlFunctions.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.scalar; 2 | 3 | import com.wgzhao.presto.udfs.utils.UDFXPathUtil; 4 | import io.airlift.slice.Slice; 5 | import io.trino.spi.block.Block; 6 | import io.trino.spi.block.BlockBuilder; 7 | import io.trino.spi.function.Description; 8 | import io.trino.spi.function.LiteralParameters; 9 | import io.trino.spi.function.ScalarFunction; 10 | import io.trino.spi.function.SqlNullable; 11 | import io.trino.spi.function.SqlType; 12 | import io.trino.spi.type.StandardTypes; 13 | import org.w3c.dom.NodeList; 14 | 15 | import static io.airlift.slice.Slices.utf8Slice; 16 | import static io.trino.spi.type.VarcharType.VARCHAR; 17 | 18 | /** 19 | * implementation of xml string operation function, migrate from hive function 20 | * https://github.com/apache/hive/blob/master/ql/src/java/org/apache/hadoop/hive/ql/udf/xml/ 21 | */ 22 | public class XmlFunctions 23 | { 24 | private static final UDFXPathUtil xpathUtil = UDFXPathUtil.getInstance(); 25 | private XmlFunctions() {} 26 | 27 | /** 28 | * xpath(xml, xpath) - Returns a string array of values within xml nodes that match the xpath expression 29 | * 30 | * Example: 31 | * SELECT _FUNC_('b1b2b3c1c2', 'a/text()') FROM src LIMIT 1 32 | * []\n" 33 | * SELECT _FUNC_('b1b2b3c1c2', 'a/b/text()') FROM src LIMIT 1 34 | * [\"b1\",\"b2\",\"b3\"]\n" 35 | * SELECT _FUNC_('b1b2b3c1c2', 'a/c/text()') FROM src LIMIT 1 36 | * [\"c1\",\"c2\"]" 37 | * 38 | */ 39 | @SqlNullable 40 | @Description("Returns a string array of values within xml nodes that match the xpath expression") 41 | @ScalarFunction("udf_xpath") 42 | @LiteralParameters({"x", "y"}) 43 | @SqlType("array(varchar(x))") 44 | public static Block xpath(@SqlType("varchar(x)") Slice xml, @SqlType("varchar(y)") Slice path) 45 | { 46 | BlockBuilder parts = VARCHAR.createBlockBuilder(null, 1, xml.length()); 47 | NodeList nodeList = xpathUtil.evalNodeList(xml.toStringUtf8(), path.toStringUtf8()); 48 | 49 | if (nodeList == null || nodeList.getLength() == 0) { 50 | return parts.build(); 51 | } 52 | 53 | for (int i = 0; i < nodeList.getLength(); i++) { 54 | VARCHAR.writeSlice(parts, utf8Slice(nodeList.item(i).getNodeValue())); 55 | } 56 | return parts.build(); 57 | } 58 | 59 | /** 60 | * xpath_boolean(xml, xpath) - Returns the number of nodes that match the xpath expression 61 | * 62 | * xpath_boolean('1','a/b') return true 63 | * xpath_boolean('1','a/b = 2') return false 64 | * 65 | * @param xml xml string 66 | * @param path xpath expression 67 | * @return true if found , else false 68 | */ 69 | @SqlNullable 70 | @Description("Evaluates a boolean xpath expression") 71 | @ScalarFunction("udf_xpath_boolean") 72 | @LiteralParameters({"x", "y"}) 73 | @SqlType(StandardTypes.BOOLEAN) 74 | public static Boolean xpathBoolean(@SqlType("varchar(x)") Slice xml, @SqlType("varchar(y)") Slice path) 75 | { 76 | return xpathUtil.evalBoolean(xml.toStringUtf8(), path.toStringUtf8()); 77 | } 78 | 79 | @SqlNullable 80 | @Description("Returns a double value that matches the xpath expression") 81 | @ScalarFunction(value = "udf_xpath_double", alias={"udf_xpath_number", "udf_xpath_float"}) 82 | @LiteralParameters({"x", "y"}) 83 | @SqlType(StandardTypes.DOUBLE) 84 | public static Double xpathDouble(@SqlType("varchar(x)") Slice xml, @SqlType("varchar(y)") Slice path) 85 | { 86 | return xpathUtil.evalNumber(xml.toStringUtf8(), path.toStringUtf8()); 87 | } 88 | 89 | 90 | @SqlNullable 91 | @Description("Returns a long value that matches the xpath expression") 92 | @ScalarFunction(value = "udf_xpath_long", alias="udf_xpath_int") 93 | @LiteralParameters({"x", "y"}) 94 | @SqlType(StandardTypes.BIGINT) 95 | public static Long xpathLong(@SqlType("varchar(x)") Slice xml, @SqlType("varchar(y)") Slice path) 96 | { 97 | return xpathUtil.evalNumber(xml.toStringUtf8(), path.toStringUtf8()).longValue(); 98 | } 99 | 100 | @SqlNullable 101 | @Description("Returns a string value that matches the xpath expression") 102 | @ScalarFunction(value = "udf_xpath_string", alias="udf_xpath_str") 103 | @LiteralParameters({"x", "y"}) 104 | @SqlType(StandardTypes.VARCHAR) 105 | public static Slice xpathString(@SqlType("varchar(x)") Slice xml, @SqlType("varchar(y)") Slice path) 106 | { 107 | String result = xpathUtil.evalString(xml.toStringUtf8(), path.toStringUtf8()); 108 | return result == null ? null : utf8Slice(result); 109 | } 110 | } 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/utils/CloseDateUtil.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.utils; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.Reader; 9 | import java.nio.charset.StandardCharsets; 10 | import java.text.ParseException; 11 | import java.text.SimpleDateFormat; 12 | import java.util.ArrayList; 13 | import java.util.Calendar; 14 | import java.util.List; 15 | import java.util.zip.GZIPInputStream; 16 | 17 | public class CloseDateUtil 18 | { 19 | public static final String closePath = "/closedate.dat.gz"; 20 | 21 | private static final io.airlift.log.Logger LOG = io.airlift.log.Logger.get(CloseDateUtil.class); 22 | 23 | private static final List closeDateList = new ArrayList<>(); 24 | 25 | public static boolean isCloseDate(final String date) 26 | { 27 | return closeDateList.contains(date); 28 | } 29 | 30 | public static String getNextExchangeDay(final String date) 31 | { 32 | String nextDate = nextDay(date); 33 | while (!isCloseDate(nextDate)) { 34 | nextDate = nextDay(nextDate); 35 | if (nextDate.compareTo(getMaxExchangeDay()) > 0) { 36 | return null; 37 | } 38 | } 39 | return nextDate; 40 | } 41 | 42 | public static String getLastExchangeDay(final String date) 43 | { 44 | // 如果传递的日期恰好是交易日列表的第一天,则需要直接返回null,否则陷入死循环 45 | if (date.equals(closeDateList.get(0))) { 46 | return null; 47 | } 48 | String lastDate = lastDay(date); 49 | if (lastDate == null) { 50 | return null; 51 | } 52 | while (!isCloseDate(lastDate)) { 53 | if (lastDate.compareTo(closeDateList.get(0)) < 0) { 54 | return null; 55 | } 56 | lastDate = lastDay(lastDate); 57 | if (lastDate == null) { 58 | return null; 59 | } 60 | } 61 | return lastDate; 62 | } 63 | 64 | public static String getPeriodExchangeDay(final String date, final int day) 65 | { 66 | String lastDate = date; 67 | while (!closeDateList.contains(lastDate)) { 68 | lastDate = lastDay(lastDate); 69 | if (lastDate == null) { 70 | return null; 71 | } 72 | if (lastDate.compareTo(getMinExchangeDay()) < 0 || lastDate.compareTo(getMaxExchangeDay()) > 0) { 73 | return null; 74 | } 75 | } 76 | final int i = closeDateList.indexOf(lastDate); 77 | 78 | return closeDateList.get(i + day); 79 | } 80 | 81 | public static String getFirstPeriodExchangeDay(final String date, final int day) 82 | { 83 | String lastDate = addDate(date, day); 84 | if (lastDate == null) { 85 | return null; 86 | } 87 | while (!closeDateList.contains(lastDate)) { 88 | if (day < 0) { 89 | if (closeDateList.isEmpty() || closeDateList.get(closeDateList.size() - 1).compareTo(lastDate) < 0) { 90 | return null; 91 | } 92 | lastDate = nextDay(lastDate); 93 | } 94 | else { 95 | if (closeDateList.isEmpty() || closeDateList.get(0).compareTo(lastDate) > 0) { 96 | return null; 97 | } 98 | lastDate = lastDay(lastDate); 99 | } 100 | } 101 | return lastDate; 102 | } 103 | 104 | public static String addDate(final String date, final int days) 105 | { 106 | final Calendar c = Calendar.getInstance(); 107 | final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); 108 | try { 109 | c.setTime(sdf.parse(date)); 110 | } 111 | catch (final ParseException e) { 112 | e.printStackTrace(); 113 | return null; 114 | } 115 | c.add(Calendar.DAY_OF_MONTH, days); 116 | return sdf.format(c.getTime()); 117 | } 118 | 119 | public static String nextDay(final String date) 120 | { 121 | return addDate(date, 1); 122 | } 123 | 124 | public static String lastDay(final String date) 125 | { 126 | return addDate(date, -1); 127 | } 128 | 129 | public static String getPrevCloseDay(final String date, final String type) 130 | { 131 | String newDate; 132 | final Calendar c = Calendar.getInstance(); 133 | final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); 134 | try { 135 | c.setTime(sdf.parse(date)); 136 | } 137 | catch (final ParseException e) { 138 | e.printStackTrace(); 139 | return null; 140 | } 141 | 142 | switch (type) { 143 | case "month": 144 | c.add(Calendar.MONTH, -1); 145 | break; 146 | case "quoter": 147 | c.add(Calendar.MONTH, -3); 148 | break; 149 | case "halfyear": 150 | c.add(Calendar.MONTH, -6); 151 | break; 152 | case "year": 153 | c.add(Calendar.YEAR, -1); 154 | break; 155 | default: 156 | c.add(Calendar.DATE, -1); 157 | } 158 | newDate = sdf.format(c.getTime()); 159 | return CloseDateUtil.getLastExchangeDay(newDate); 160 | } 161 | 162 | public static int getCountExchangeDay(final String date1, final String date2) 163 | { 164 | if (date1 == null || date2 == null) { 165 | return 0; 166 | } 167 | String lastDate = date1; 168 | int count = 0; 169 | while (lastDate.compareTo(date2) <= 0) { 170 | if (closeDateList.contains(lastDate)) { 171 | count++; 172 | } 173 | lastDate = nextDay(lastDate); 174 | if (lastDate == null) { 175 | return 0; 176 | } 177 | } 178 | 179 | return count; 180 | } 181 | 182 | /** 183 | * 获取当前交易日文件的最大日期 184 | * 185 | * @return the max trade date 186 | */ 187 | public static String getMaxExchangeDay() 188 | { 189 | return closeDateList.get(closeDateList.size() - 1); 190 | } 191 | 192 | /** 193 | * 获取当前交易文件的最小交易日 194 | * 195 | * @return the min trade date 196 | */ 197 | public static String getMinExchangeDay() 198 | { 199 | return closeDateList.get(0); 200 | } 201 | 202 | static { 203 | InputStream in = null; 204 | BufferedReader buffered = null; 205 | InputStream gzipStream = null; 206 | try { 207 | in = CloseDateUtil.class.getResourceAsStream(closePath); 208 | assert in != null; 209 | gzipStream = new GZIPInputStream(in); 210 | Reader decoder = new InputStreamReader(gzipStream, StandardCharsets.UTF_8); 211 | buffered = new BufferedReader(decoder); 212 | closeDateList.addAll(IOUtils.readLines(buffered)); 213 | } 214 | catch (Exception e) { 215 | e.printStackTrace(); 216 | LOG.error("CloseDateUtil init failed", e); 217 | } 218 | finally { 219 | if (gzipStream != null) { 220 | IOUtils.closeQuietly(gzipStream, null); 221 | } 222 | if (buffered != null) { 223 | IOUtils.closeQuietly(buffered, null); 224 | } 225 | if (in != null) { 226 | IOUtils.closeQuietly(in, null); 227 | } 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/utils/IPSearcher.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.utils; 2 | 3 | import org.lionsoul.ip2region.DataBlock; 4 | import org.lionsoul.ip2region.DbConfig; 5 | import org.lionsoul.ip2region.DbMakerConfigException; 6 | import org.lionsoul.ip2region.DbSearcher; 7 | 8 | import java.io.IOException; 9 | import java.util.Objects; 10 | 11 | public class IPSearcher 12 | { 13 | private static final String FILE_NAME = "/ip2region.db"; 14 | 15 | private static final class IPSearcherHolder 16 | { 17 | private static final IPSearcher instance = new IPSearcher(); 18 | } 19 | 20 | private final DbSearcher dbSearcher; 21 | 22 | private IPSearcher() 23 | { 24 | try { 25 | DbConfig config = new DbConfig(); 26 | byte[] dbData = Objects.requireNonNull(IPSearcher.class.getResourceAsStream(FILE_NAME)).readAllBytes(); 27 | this.dbSearcher = new DbSearcher(config, dbData); 28 | // load into memory when first query 29 | this.dbSearcher.memorySearch("127.0.0.1"); 30 | } 31 | catch (DbMakerConfigException | IOException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | public static IPSearcher getInstance() 37 | { 38 | return IPSearcherHolder.instance; 39 | } 40 | 41 | public DataBlock lookup(String ipStr) 42 | { 43 | try { 44 | return this.dbSearcher.memorySearch(ipStr); 45 | } 46 | catch (IOException e) { 47 | return null; 48 | } 49 | } 50 | 51 | public DataBlock lookup(long ipLong) 52 | { 53 | try { 54 | return this.dbSearcher.memorySearch(ipLong); 55 | } 56 | catch (IOException e) { 57 | return null; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/utils/IdCardUtil.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.utils; 2 | 3 | import java.text.DateFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | import java.util.GregorianCalendar; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * 身份证相关工具类 15 | * 16 | * @author zhaojiaxing 17 | * @version 1.0 18 | * @since 2020/01/07 14:40 19 | */ 20 | public class IdCardUtil 21 | { 22 | 23 | public static final char SPACE = ' '; 24 | /** 25 | * 数字 26 | */ 27 | public final static Pattern NUMBERS = Pattern.compile("\\d+"); 28 | /** 29 | * 生日 30 | */ 31 | public final static Pattern BIRTHDAY = Pattern.compile("^(\\d{2,4})([/\\-.年]?)(\\d{1,2})([/\\-.月]?)(\\d{1,2})日?$"); 32 | /** 33 | * 中国公民身份证号码最小长度。 34 | */ 35 | private static final int CHINA_ID_MIN_LENGTH = 15; 36 | /** 37 | * 中国公民身份证号码最大长度。 38 | */ 39 | private static final int CHINA_ID_MAX_LENGTH = 18; 40 | /** 41 | * 每位加权因子 42 | */ 43 | private static final int[] power = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; 44 | /** 45 | * 默认日期模板 46 | */ 47 | private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 48 | private static final String YYYY_MM_DD = "yyyyMMdd"; 49 | private static final String YY_MM_DD = "yyMMdd"; 50 | /** 51 | * 省市代码表 52 | */ 53 | private static final Map cityCodes = new HashMap<>(); 54 | /** 55 | * 台湾身份首字母对应数字 56 | */ 57 | private static final Map twFirstCode = new HashMap<>(); 58 | /** 59 | * 香港身份首字母对应数字 60 | */ 61 | private static final Map hkFirstCode = new HashMap<>(); 62 | 63 | /** 64 | * 是否有效身份证号 65 | * 66 | * @param idCard 身份证号,支持18位、15位和港澳台的10位 67 | * @return 是否有效 68 | */ 69 | public static boolean isValidCard(String idCard) 70 | { 71 | idCard = idCard.trim(); 72 | if (idCard.isEmpty()) { 73 | return false; 74 | } 75 | int length = idCard.length(); 76 | switch (length) { 77 | // 18位身份证 78 | case 18: 79 | return isValidCard18(idCard); 80 | // 15位身份证 81 | case 15: 82 | return isValidCard15(idCard); 83 | // 10位身份证,港澳台地区 84 | case 10: { 85 | String[] cardval = isValidCard10(idCard); 86 | return null != cardval && cardval[2].equals("true"); 87 | } 88 | default: 89 | return false; 90 | } 91 | } 92 | 93 | /** 94 | * 将15位身份证号码转换为18位 95 | * 96 | * @param idCard 15位身份编码 97 | * @return 18位身份编码 98 | */ 99 | public static String convertIdCard(String idCard) 100 | { 101 | StringBuilder idCard18; 102 | if (idCard.length() != CHINA_ID_MIN_LENGTH) { 103 | return null; 104 | } 105 | if (isMatch(NUMBERS, idCard)) { 106 | // 获取出生年月日 107 | String birthday = idCard.substring(6, 12); 108 | Date birthDate = strToDate(birthday, YY_MM_DD); 109 | // 获取出生年 110 | int sYear = year(birthDate); 111 | // 理论上2000年之后不存在15位身份证,可以不要此判断 112 | if (sYear > 2000) { 113 | sYear -= 100; 114 | } 115 | idCard18 = new StringBuilder().append(idCard, 0, 6).append(sYear).append(idCard.substring(8)); 116 | // 获取校验位 117 | char sVal = getCheckCode18(idCard18.toString()); 118 | idCard18.append(sVal); 119 | } 120 | else { 121 | return null; 122 | } 123 | return idCard18.toString(); 124 | } 125 | 126 | /** 127 | * 从身份证号码中获取生日日期,只支持15或18位身份证号码 128 | * 129 | * @param idCard 身份证号码 130 | * @return 日期 131 | */ 132 | public static Date getBirthDate(String idCard) 133 | { 134 | final String birthByIdCard = getBirth(idCard); 135 | return null == birthByIdCard ? null : strToDate(birthByIdCard, YYYY_MM_DD); 136 | } 137 | 138 | /** 139 | * 根据身份编号获取年龄,只支持15或18位身份证号码 140 | * 141 | * @param idCard 身份证号 142 | * @return 年龄 143 | */ 144 | public static int getAgeByCard(String idCard) 145 | { 146 | String birth = getBirth(idCard); 147 | return getAge(strToDate(birth, YYYY_MM_DD), new Date()); 148 | } 149 | 150 | /** 151 | * 根据出生日期计算在某个日期的年龄 152 | * 153 | * @param birthDay 生日 154 | * @param dateToCompare 需要对比的日期 155 | * @return 年龄 156 | */ 157 | public static int getAge(Date birthDay, Date dateToCompare) 158 | { 159 | Calendar cal = Calendar.getInstance(); 160 | cal.setTime(dateToCompare); 161 | 162 | if (cal.before(birthDay)) { 163 | throw new IllegalArgumentException("Birthday is after date " + dateToCompare + " !"); 164 | } 165 | 166 | //先获取指定日期的年月日 167 | final int year = cal.get(Calendar.YEAR); 168 | final int month = cal.get(Calendar.MONTH); 169 | final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); 170 | final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); 171 | 172 | //获取出生日期的年月 173 | cal.setTime(birthDay); 174 | int age = year - cal.get(Calendar.YEAR); 175 | final int monthBirth = cal.get(Calendar.MONTH); 176 | 177 | if (month == monthBirth) { 178 | //获取出生日期的日 179 | final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH); 180 | final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); 181 | // 如果生日在当月,但是未达到生日当天的日期,年龄减一(判断是否是最后一天是为了去除润月的影响) 182 | if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) { 183 | age--; 184 | } 185 | } 186 | else if (month < monthBirth) { 187 | // 如果当前月份未达到生日的月份,年龄计算减一 188 | age--; 189 | } 190 | 191 | return age; 192 | } 193 | 194 | /** 195 | * 根据身份证号码获取生日年,只支持15或18位身份证号码 196 | * 197 | * @param idCard 身份编号 198 | * @return 生日(yyyy) 199 | */ 200 | public static Short getYearByIdCard(String idCard) 201 | { 202 | final int len = idCard.length(); 203 | if (len < CHINA_ID_MIN_LENGTH) { 204 | return null; 205 | } 206 | else if (len == CHINA_ID_MIN_LENGTH) { 207 | idCard = convertIdCard(idCard); 208 | } 209 | if (idCard == null ) { 210 | return null; 211 | } 212 | return Short.valueOf(idCard.substring(6, 10)); 213 | } 214 | 215 | /** 216 | * 根据身份证号码获取生日月,只支持15或18位身份证号码 217 | * 218 | * @param idCard 身份编号 219 | * @return 生日(MM) 220 | */ 221 | public static Short getMonthByIdCard(String idCard) 222 | { 223 | final int len = idCard.length(); 224 | if (len < CHINA_ID_MIN_LENGTH) { 225 | return null; 226 | } 227 | else if (len == CHINA_ID_MIN_LENGTH) { 228 | idCard = convertIdCard(idCard); 229 | } 230 | return Short.valueOf(idCard.substring(10, 12)); 231 | } 232 | 233 | /** 234 | * 根据身份证号码获取生日天,只支持15或18位身份证号码 235 | * 236 | * @param idCard 身份编号 237 | * @return 生日(dd) 238 | */ 239 | public static Short getDayByIdCard(String idCard) 240 | { 241 | final int len = idCard.length(); 242 | if (len < CHINA_ID_MIN_LENGTH) { 243 | return null; 244 | } 245 | else if (len == CHINA_ID_MIN_LENGTH) { 246 | idCard = convertIdCard(idCard); 247 | } 248 | return Short.valueOf(idCard.substring(12, 14)); 249 | } 250 | 251 | /** 252 | * 根据身份证号码获取性别,只支持15或18位身份证号码 253 | * 254 | * @param idCard 身份编号 255 | * @return 性别(1 : 男 , 0 : 女) 256 | */ 257 | public static int getGender(String idCard) 258 | { 259 | if (idCard == null || idCard.trim().length() == 0) { 260 | throw new IllegalArgumentException("ID Card is must not null"); 261 | } 262 | final int len = idCard.length(); 263 | if (len < CHINA_ID_MIN_LENGTH) { 264 | throw new IllegalArgumentException("ID Card length must be 15 or 18"); 265 | } 266 | 267 | if (len == CHINA_ID_MIN_LENGTH) { 268 | idCard = convertIdCard(idCard); 269 | } 270 | char sCardChar = idCard.charAt(16); 271 | return (sCardChar % 2 != 0) ? 1 : 0; 272 | } 273 | 274 | /** 275 | * 根据身份证号码获取户籍省份,只支持15或18位身份证号码 276 | * 277 | * @param idCard 身份编码 278 | * @return 省级编码。 279 | */ 280 | public static String getProvince(String idCard) 281 | { 282 | int len = idCard.length(); 283 | if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH) { 284 | String sProvinNum = idCard.substring(0, 2); 285 | return cityCodes.get(sProvinNum); 286 | } 287 | return null; 288 | } 289 | 290 | /** 291 | * 判断18位身份证的合法性 292 | *

293 | * 公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成,排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。 294 | * 顺序码: 表示在同一地址码所标识的区域范围内,对同年、同月、同 日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配 给女性。 295 | * 第1、2位数字表示:所在省份的代码;第3、4位数字表示:所在城市的代码;第5、6位数字表示:所在区县的代码,第7~14位数字表示:出生年、月、日 296 | * 第15、16位数字表示:所在地的派出所的代码;第17位数字表示性别:奇数表示男性,偶数表示女性 297 | * 第18位数字是校检码,用来检验身份证的正确性。校检码可以是0~9的数字,有时也用x表示 298 | * 第十八位数字(校验码)的计算方法为: 299 | * 300 | * @param idCard 待验证的身份证 301 | * @return 是否有效的18位身份证 302 | */ 303 | private static boolean isValidCard18(String idCard) 304 | { 305 | if (CHINA_ID_MAX_LENGTH != idCard.length()) { 306 | return false; 307 | } 308 | 309 | //校验生日 310 | if (!isBirthday(idCard.substring(6, 14))) { 311 | return false; 312 | } 313 | 314 | // 前17位 315 | String code17 = idCard.substring(0, 17); 316 | // 第18位 317 | char code18 = Character.toLowerCase(idCard.charAt(17)); 318 | if (isMatch(NUMBERS, code17)) { 319 | // 获取校验位 320 | char val = getCheckCode18(code17); 321 | return val == code18; 322 | } 323 | return false; 324 | } 325 | 326 | /** 327 | * 验证15位身份编码是否合法 328 | * 329 | * @param idCard 身份编码 330 | * @return 是否合法 331 | */ 332 | private static boolean isValidCard15(String idCard) 333 | { 334 | if (CHINA_ID_MIN_LENGTH != idCard.length()) { 335 | return false; 336 | } 337 | if (isMatch(NUMBERS, idCard)) { 338 | // 省份 339 | String proCode = idCard.substring(0, 2); 340 | if (null == cityCodes.get(proCode)) { 341 | return false; 342 | } 343 | 344 | //校验生日(两位年份,补充为19XX) 345 | return isBirthday("19" + idCard.substring(6, 12)); 346 | } 347 | else { 348 | return false; 349 | } 350 | } 351 | 352 | /** 353 | * 验证10位身份编码是否合法 354 | * 355 | * @param idCard 身份编码 356 | * @return 身份证信息数组 357 | * [0] - 台湾、澳门、香港 [1] - 性别(男M,女F,未知N) [2] - 是否合法(合法true,不合法false) 若不是身份证件号码则返回null 358 | */ 359 | private static String[] isValidCard10(String idCard) 360 | { 361 | if (idCard == null || idCard.trim().length() == 0) { 362 | return null; 363 | } 364 | String[] info = new String[3]; 365 | String card = idCard.replaceAll("[()]", ""); 366 | if (card.length() != 8 && card.length() != 9 && idCard.length() != 10) { 367 | return null; 368 | } 369 | // 台湾 370 | if (idCard.matches("^[a-zA-Z][0-9]{9}$")) { 371 | info[0] = "台湾"; 372 | String char2 = idCard.substring(1, 2); 373 | if (char2.equals("1")) { 374 | info[1] = "M"; 375 | } 376 | else if (char2.equals("2")) { 377 | info[1] = "F"; 378 | } 379 | else { 380 | info[1] = "N"; 381 | info[2] = "false"; 382 | return info; 383 | } 384 | info[2] = isValidTWCard(idCard) ? "true" : "false"; 385 | } 386 | else if (idCard.matches("^[157][0-9]{6}\\(?[0-9A-Z]\\)?$")) { 387 | // 澳门 388 | info[0] = "澳门"; 389 | info[1] = "N"; 390 | } 391 | else if (idCard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { 392 | // 香港 393 | info[0] = "香港"; 394 | info[1] = "N"; 395 | info[2] = isValidHKCard(idCard) ? "true" : "false"; 396 | } 397 | else { 398 | return null; 399 | } 400 | return info; 401 | } 402 | 403 | /** 404 | * 验证台湾身份证号码 405 | * 406 | * @param idCard 身份证号码 407 | * @return 验证码是否符合 408 | */ 409 | private static boolean isValidTWCard(String idCard) 410 | { 411 | if (idCard == null || idCard.trim().length() == 0) { 412 | return false; 413 | } 414 | String start = idCard.substring(0, 1); 415 | Integer iStart = twFirstCode.get(start); 416 | if (null == iStart) { 417 | return false; 418 | } 419 | String mid = idCard.substring(1, 9); 420 | String end = idCard.substring(9, 10); 421 | int sum = iStart / 10 + (iStart % 10) * 9; 422 | final char[] chars = mid.toCharArray(); 423 | int iflag = 8; 424 | for (char c : chars) { 425 | sum += Integer.valueOf(String.valueOf(c)) * iflag; 426 | iflag--; 427 | } 428 | return (sum % 10 == 0 ? 0 : (10 - sum % 10)) == Integer.valueOf(end); 429 | } 430 | 431 | /** 432 | * 验证香港身份证号码 433 | * 身份证前2位为英文字符,如果只出现一个英文字符则表示第一位是空格,对应数字58 前2位英文字符A-Z分别对应数字10-35 最后一位校验码为0-9的数字加上字符"A","A"代表10 434 | * 将身份证号码全部转换为数字,分别对应乘9-1相加的总和,整除11则证件号码有效 435 | * 436 | * @param idCard 身份证号码 437 | * @return 验证码是否符合 438 | */ 439 | private static boolean isValidHKCard(String idCard) 440 | { 441 | String card = idCard.replaceAll("[()]", ""); 442 | int sum; 443 | if (card.length() == 9) { 444 | sum = (Character.toUpperCase(card.charAt(0)) - 55) * 9 + (Character.toUpperCase(card.charAt(1)) - 55) * 8; 445 | card = card.substring(1, 9); 446 | } 447 | else { 448 | sum = 522 + (Character.toUpperCase(card.charAt(0)) - 55) * 8; 449 | } 450 | String start = idCard.substring(0, 1); 451 | Integer iStart = hkFirstCode.get(start); 452 | if (null == iStart) { 453 | return false; 454 | } 455 | String mid = card.substring(1, 7); 456 | String end = card.substring(7, 8); 457 | char[] chars = mid.toCharArray(); 458 | int iflag = 7; 459 | for (char c : chars) { 460 | sum = sum + Integer.valueOf(String.valueOf(c)) * iflag; 461 | iflag--; 462 | } 463 | if ("A".equalsIgnoreCase(end)) { 464 | sum += 10; 465 | } 466 | else { 467 | sum += Integer.parseInt(end); 468 | } 469 | return sum % 11 == 0; 470 | } 471 | 472 | /** 473 | * 根据身份编号获取生日,只支持15或18位身份证号码 474 | * 475 | * @param idCard 身份编号 476 | * @return 生日(yyyyMMdd) 477 | */ 478 | public static String getBirth(String idCard) 479 | { 480 | final int len = idCard.length(); 481 | if (len < CHINA_ID_MIN_LENGTH) { 482 | return null; 483 | } 484 | else if (len == CHINA_ID_MIN_LENGTH) { 485 | idCard = convertIdCard(idCard); 486 | } 487 | if (idCard == null) { 488 | return null; 489 | } 490 | return idCard.substring(6, 14); 491 | } 492 | 493 | /** 494 | * 获得18位身份证校验码 495 | * 计算方式: 496 | * 将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2 497 | * 将这17位数字和系数相乘的结果相加 498 | * 用加出来和除以11,看余数是多少 499 | * 余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2 500 | * 通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2 501 | * 502 | * @param code17 18位身份证号中的前17位 503 | * @return 第18位 504 | */ 505 | private static char getCheckCode18(String code17) 506 | { 507 | int sum = getPowerSum(code17.toCharArray()); 508 | return getCheckCode18(sum); 509 | } 510 | 511 | /** 512 | * 将power和值与11取模获得余数进行校验码判断 513 | * 514 | * @param iSum 加权和 515 | * @return 校验位 516 | */ 517 | private static char getCheckCode18(int iSum) 518 | { 519 | switch (iSum % 11) { 520 | case 10: 521 | return '2'; 522 | case 9: 523 | return '3'; 524 | case 8: 525 | return '4'; 526 | case 7: 527 | return '5'; 528 | case 6: 529 | return '6'; 530 | case 5: 531 | return '7'; 532 | case 4: 533 | return '8'; 534 | case 3: 535 | return '9'; 536 | case 2: 537 | return 'x'; 538 | case 1: 539 | return '0'; 540 | case 0: 541 | return '1'; 542 | default: 543 | return SPACE; 544 | } 545 | } 546 | 547 | /** 548 | * 将身份证的每位和对应位的加权因子相乘之后,再得到和值 549 | * 550 | * @param iArr 身份证号码的数组 551 | * @return 身份证编码 552 | */ 553 | private static int getPowerSum(char[] iArr) 554 | { 555 | int iSum = 0; 556 | if (power.length == iArr.length) { 557 | for (int i = 0; i < iArr.length; i++) { 558 | iSum += Integer.valueOf(String.valueOf(iArr[i])) * power[i]; 559 | } 560 | } 561 | return iSum; 562 | } 563 | 564 | /** 565 | * 根据日期获取年 566 | * 567 | * @param date 日期 568 | * @return 年的部分 569 | */ 570 | public static int year(Date date) 571 | { 572 | Calendar ca = Calendar.getInstance(); 573 | ca.setTime(date); 574 | return ca.get(Calendar.YEAR); 575 | } 576 | 577 | /** 578 | * 验证是否为生日
579 | * 只支持以下几种格式: 580 | *

    581 | *
  • yyyyMMdd
  • 582 | *
  • yyyy-MM-dd
  • 583 | *
  • yyyy/MM/dd
  • 584 | *
  • yyyy.MM.dd
  • 585 | *
  • yyyy年MM月dd日
  • 586 | *
587 | * 588 | * @param value 值 589 | * @return 是否为生日 590 | */ 591 | public static boolean isBirthday(CharSequence value) 592 | { 593 | if (isMatch(BIRTHDAY, value)) { 594 | Matcher matcher = BIRTHDAY.matcher(value); 595 | if (matcher.find()) { 596 | int year = Integer.parseInt(matcher.group(1)); 597 | int month = Integer.parseInt(matcher.group(3)); 598 | int day = Integer.parseInt(matcher.group(5)); 599 | return isBirthday(year, month, day); 600 | } 601 | } 602 | return false; 603 | } 604 | 605 | /** 606 | * 验证是否为生日 607 | * 608 | * @param year 年,从1900年开始计算 609 | * @param month 月,从1开始计数 610 | * @param day 日,从1开始计数 611 | * @return 是否为生日 612 | */ 613 | public static boolean isBirthday(int year, int month, int day) 614 | { 615 | // 验证年 616 | int thisYear = year(new Date()); 617 | if (year < 1900 || year > thisYear) { 618 | return false; 619 | } 620 | 621 | // 验证月 622 | if (month < 1 || month > 12) { 623 | return false; 624 | } 625 | 626 | // 验证日 627 | if (day < 1 || day > 31) { 628 | return false; 629 | } 630 | // 检查几个特殊月的最大天数 631 | if (day == 31 && (month == 4 || month == 6 || month == 9 || month == 11)) { 632 | return false; 633 | } 634 | if (month == 2) { 635 | // 在2月,非闰年最大28,闰年最大29 636 | return day < 29 || (day == 29 && isLeapYear(year)); 637 | } 638 | return true; 639 | } 640 | 641 | /** 642 | * 是否闰年 643 | * 644 | * @param year 年 645 | * @return 是否闰年 646 | */ 647 | private static boolean isLeapYear(int year) 648 | { 649 | return new GregorianCalendar().isLeapYear(year); 650 | } 651 | 652 | /** 653 | * 将字符串转换成指定格式的日期 654 | * 655 | * @param str 日期字符串. 656 | * @param dateFormat 日期格式. 如果为空,默认为:yyyy-MM-dd HH:mm:ss. 657 | * @return 658 | */ 659 | private static Date strToDate(final String str, String dateFormat) 660 | { 661 | if (str == null || str.trim().length() == 0) { 662 | return null; 663 | } 664 | try { 665 | if (dateFormat == null || dateFormat.length() == 0) { 666 | dateFormat = DATE_FORMAT; 667 | } 668 | DateFormat fmt = new SimpleDateFormat(dateFormat); 669 | return fmt.parse(str.trim()); 670 | } 671 | catch (Exception ex) { 672 | return null; 673 | } 674 | } 675 | 676 | /** 677 | * 给定内容是否匹配正则 678 | * 679 | * @param pattern 模式 680 | * @param content 内容 681 | * @return 正则为null或者""则不检查,返回true,内容为null返回false 682 | */ 683 | private static boolean isMatch(Pattern pattern, CharSequence content) 684 | { 685 | if (content == null || pattern == null) { 686 | // 提供null的字符串为不匹配 687 | return false; 688 | } 689 | return pattern.matcher(content).matches(); 690 | } 691 | 692 | static { 693 | cityCodes.put("11", "北京"); 694 | cityCodes.put("12", "天津"); 695 | cityCodes.put("13", "河北"); 696 | cityCodes.put("14", "山西"); 697 | cityCodes.put("15", "内蒙古"); 698 | cityCodes.put("21", "辽宁"); 699 | cityCodes.put("22", "吉林"); 700 | cityCodes.put("23", "黑龙江"); 701 | cityCodes.put("31", "上海"); 702 | cityCodes.put("32", "江苏"); 703 | cityCodes.put("33", "浙江"); 704 | cityCodes.put("34", "安徽"); 705 | cityCodes.put("35", "福建"); 706 | cityCodes.put("36", "江西"); 707 | cityCodes.put("37", "山东"); 708 | cityCodes.put("41", "河南"); 709 | cityCodes.put("42", "湖北"); 710 | cityCodes.put("43", "湖南"); 711 | cityCodes.put("44", "广东"); 712 | cityCodes.put("45", "广西"); 713 | cityCodes.put("46", "海南"); 714 | cityCodes.put("50", "重庆"); 715 | cityCodes.put("51", "四川"); 716 | cityCodes.put("52", "贵州"); 717 | cityCodes.put("53", "云南"); 718 | cityCodes.put("54", "西藏"); 719 | cityCodes.put("61", "陕西"); 720 | cityCodes.put("62", "甘肃"); 721 | cityCodes.put("63", "青海"); 722 | cityCodes.put("64", "宁夏"); 723 | cityCodes.put("65", "新疆"); 724 | cityCodes.put("71", "台湾"); 725 | cityCodes.put("81", "香港"); 726 | cityCodes.put("82", "澳门"); 727 | cityCodes.put("91", "国外"); 728 | 729 | twFirstCode.put("A", 10); 730 | twFirstCode.put("B", 11); 731 | twFirstCode.put("C", 12); 732 | twFirstCode.put("D", 13); 733 | twFirstCode.put("E", 14); 734 | twFirstCode.put("F", 15); 735 | twFirstCode.put("G", 16); 736 | twFirstCode.put("H", 17); 737 | twFirstCode.put("J", 18); 738 | twFirstCode.put("K", 19); 739 | twFirstCode.put("L", 20); 740 | twFirstCode.put("M", 21); 741 | twFirstCode.put("N", 22); 742 | twFirstCode.put("P", 23); 743 | twFirstCode.put("Q", 24); 744 | twFirstCode.put("R", 25); 745 | twFirstCode.put("S", 26); 746 | twFirstCode.put("T", 27); 747 | twFirstCode.put("U", 28); 748 | twFirstCode.put("V", 29); 749 | twFirstCode.put("X", 30); 750 | twFirstCode.put("Y", 31); 751 | twFirstCode.put("W", 32); 752 | twFirstCode.put("Z", 33); 753 | twFirstCode.put("I", 34); 754 | twFirstCode.put("O", 35); 755 | 756 | //来自http://shenfenzheng.bajiu.cn/?rid=40 757 | // 持证人拥有香港居留权 758 | hkFirstCode.put("A", 1); 759 | // 持证人所报称的出生日期或地点自首次登记以后,曾作出更改 760 | hkFirstCode.put("B", 2); 761 | // 持证人登记领证时在香港的居留受到入境事务处处长的限制 762 | hkFirstCode.put("C", 3); 763 | // 持证人所报的姓名自首次登记以后,曾作出更改 764 | hkFirstCode.put("N", 14); 765 | // 持证人报称在香港、澳门及中国以外其他地区或国家出生 766 | hkFirstCode.put("O", 15); 767 | // 持证人拥有香港入境权 768 | hkFirstCode.put("R", 18); 769 | // 持证人登记领证时在香港的居留不受入境事务处处长的限制 770 | hkFirstCode.put("U", 21); 771 | // 持证人报称在澳门地区出生 772 | hkFirstCode.put("W", 23); 773 | // 持证人报称在中国大陆出生 774 | hkFirstCode.put("X", 24); 775 | // 持证人报称在香港出生 776 | hkFirstCode.put("Z", 26); 777 | } 778 | } -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/utils/IsoSearcher.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.utils; 2 | 3 | import com.wgzhao.presto.udfs.dto.IsoDto; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.io.Reader; 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.Method; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.zip.GZIPInputStream; 16 | 17 | /** 18 | * Created by Administrator on 2020/3/23. 19 | */ 20 | public class IsoSearcher 21 | { 22 | 23 | public static Map isoMap = new HashMap<>(); 24 | 25 | private static volatile IsoSearcher instance; 26 | private static final String FILE_NAME = "/iso3166.csv.gz"; 27 | 28 | private IsoSearcher() 29 | { 30 | 31 | this.readIsoCsv(); 32 | } 33 | 34 | public static IsoSearcher getInstance() 35 | { 36 | if (instance == null) { 37 | synchronized (IsoSearcher.class) { 38 | if (instance == null) { 39 | instance = new IsoSearcher(); 40 | } 41 | } 42 | } 43 | return instance; 44 | } 45 | 46 | public void readIsoCsv() 47 | { 48 | InputStream inputStream = null; 49 | BufferedReader buffered = null; 50 | 51 | try { 52 | inputStream = IsoSearcher.class.getResourceAsStream(FILE_NAME); 53 | 54 | assert inputStream != null; 55 | InputStream ins = new GZIPInputStream(inputStream); 56 | Reader decoder = new InputStreamReader(ins, StandardCharsets.UTF_8); 57 | buffered = new BufferedReader(decoder); 58 | // skip header 59 | buffered.readLine(); 60 | 61 | String line; 62 | while ((line = buffered.readLine()) != null) { 63 | String[] item = line.split(","); //CSV格式文件时候的分割符 64 | 65 | IsoDto iso = new IsoDto(); 66 | 67 | iso.setCn(item[0].replace("\"", "")); 68 | iso.setEn(item[1].replace("\"", "")); 69 | iso.setM2(item[2].replace("\"", "")); 70 | iso.setM3(item[3].replace("\"", "")); 71 | iso.setDigit(item[4].replace("\"", "")); 72 | 73 | isoMap.put(item[0].replace("\"", ""), iso); 74 | } 75 | } 76 | catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | finally { 80 | try { 81 | if (inputStream != null) { 82 | inputStream.close(); 83 | } 84 | if (buffered != null) { 85 | buffered.close(); 86 | } 87 | } 88 | catch (IOException e) { 89 | e.printStackTrace(); 90 | } 91 | } 92 | } 93 | 94 | public String searcher(String country, String fieldName) 95 | { 96 | 97 | String value = ""; 98 | 99 | if ("".equals(country) || country == null) { 100 | return value; 101 | } 102 | 103 | if (isoMap.containsKey(country)) { 104 | IsoDto isoDto = isoMap.get(country); 105 | 106 | value = (String) this.getFieldValueByName(fieldName, isoDto); 107 | } 108 | 109 | return value; 110 | } 111 | 112 | private Object getFieldValueByName(String fieldName, Object o) 113 | { 114 | try { 115 | String firstLetter = fieldName.substring(0, 1).toUpperCase(); 116 | String getter = "get" + firstLetter + fieldName.substring(1); 117 | Method method = o.getClass().getMethod(getter); 118 | return method.invoke(o); 119 | } 120 | catch (Exception e) { 121 | return ""; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/utils/MobileSearcher.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs.utils; 2 | 3 | import me.ihxq.projects.pna.PhoneNumberInfo; 4 | import me.ihxq.projects.pna.PhoneNumberLookup; 5 | import me.ihxq.projects.pna.algorithm.BinarySearchAlgorithmImpl; 6 | import me.ihxq.projects.pna.algorithm.LookupAlgorithm; 7 | 8 | /** 9 | * Created by Administrator on 2020/3/25. 10 | */ 11 | public class MobileSearcher 12 | { 13 | private static final Object mutex = new Object(); 14 | private static volatile MobileSearcher instance; 15 | private final PhoneNumberLookup phoneNumberLookup; 16 | 17 | private MobileSearcher() 18 | { 19 | this(new BinarySearchAlgorithmImpl()); 20 | } 21 | 22 | private MobileSearcher(LookupAlgorithm lookupAlgorithm) 23 | { 24 | this.phoneNumberLookup = new PhoneNumberLookup(lookupAlgorithm); 25 | } 26 | 27 | public static MobileSearcher getInstance() 28 | { 29 | MobileSearcher result = instance; 30 | if (result == null) { 31 | synchronized (mutex) { 32 | result = instance; 33 | if (result == null) { 34 | instance = result = new MobileSearcher(); 35 | } 36 | } 37 | return result; 38 | } 39 | return instance; 40 | } 41 | 42 | public PhoneNumberInfo lookup(String phoneNumber) 43 | { 44 | return this.phoneNumberLookup.lookup(phoneNumber).orElse(null); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/utils/UDFXPathUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.wgzhao.presto.udfs.utils; 20 | 21 | import org.w3c.dom.Node; 22 | import org.w3c.dom.NodeList; 23 | import org.xml.sax.InputSource; 24 | 25 | import javax.xml.namespace.QName; 26 | import javax.xml.parsers.DocumentBuilder; 27 | import javax.xml.parsers.DocumentBuilderFactory; 28 | import javax.xml.parsers.ParserConfigurationException; 29 | import javax.xml.xpath.XPath; 30 | import javax.xml.xpath.XPathConstants; 31 | import javax.xml.xpath.XPathExpression; 32 | import javax.xml.xpath.XPathExpressionException; 33 | import javax.xml.xpath.XPathFactory; 34 | 35 | import java.io.IOException; 36 | import java.io.Reader; 37 | import java.io.StringReader; 38 | 39 | /** 40 | * Utility class for all XPath UDFs. Each UDF instance should keep an instance 41 | * of this class. 42 | */ 43 | public class UDFXPathUtil 44 | { 45 | public static final String SAX_FEATURE_PREFIX = "http://xml.org/sax/features/"; 46 | public static final String EXTERNAL_GENERAL_ENTITIES_FEATURE = "external-general-entities"; 47 | public static final String EXTERNAL_PARAMETER_ENTITIES_FEATURE = "external-parameter-entities"; 48 | // private DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 49 | // private DocumentBuilder builder = null; 50 | private final XPath xpath = XPathFactory.newInstance().newXPath(); 51 | 52 | private static final Object mutex = new Object(); 53 | private static volatile UDFXPathUtil instance; 54 | private XPathExpression expression = null; 55 | private String oldPath = null; 56 | 57 | private static final ThreadLocal docBuilderIns = ThreadLocal.withInitial(() -> { 58 | try { 59 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 60 | dbf.setFeature(SAX_FEATURE_PREFIX + EXTERNAL_GENERAL_ENTITIES_FEATURE, false); 61 | dbf.setFeature(SAX_FEATURE_PREFIX + EXTERNAL_PARAMETER_ENTITIES_FEATURE, false); 62 | return dbf.newDocumentBuilder(); 63 | } 64 | catch (ParserConfigurationException e) { 65 | throw new RuntimeException(e); 66 | } 67 | }); 68 | 69 | public static UDFXPathUtil getInstance() 70 | { 71 | UDFXPathUtil result = instance; 72 | if (result == null) { 73 | synchronized (mutex) { 74 | result = instance; 75 | if (result == null) { 76 | instance = result = new UDFXPathUtil(); 77 | } 78 | } 79 | return result; 80 | } 81 | return instance; 82 | } 83 | 84 | public Object eval(String xml, String path, QName qname) 85 | { 86 | if (xml == null || path == null || qname == null) { 87 | return null; 88 | } 89 | 90 | if (xml.length() == 0 || path.length() == 0) { 91 | return null; 92 | } 93 | 94 | if (!path.equals(oldPath)) { 95 | try { 96 | expression = xpath.compile(path); 97 | } 98 | catch (XPathExpressionException e) { 99 | expression = null; 100 | } 101 | oldPath = path; 102 | } 103 | 104 | if (expression == null) { 105 | return null; 106 | } 107 | 108 | ReusableStringReader reader = new ReusableStringReader(); 109 | InputSource inputSource = new InputSource(reader); 110 | reader.set(xml); 111 | try { 112 | return expression.evaluate(docBuilderIns.get().parse(inputSource), qname); 113 | } 114 | catch (XPathExpressionException e) { 115 | throw new RuntimeException("Invalid expression '" + oldPath + "'", e); 116 | } 117 | catch (Exception e) { 118 | throw new RuntimeException("Error loading expression '" + oldPath + "'", e); 119 | } 120 | } 121 | 122 | public Boolean evalBoolean(String xml, String path) 123 | { 124 | return (Boolean) eval(xml, path, XPathConstants.BOOLEAN); 125 | } 126 | 127 | public String evalString(String xml, String path) 128 | { 129 | return (String) eval(xml, path, XPathConstants.STRING); 130 | } 131 | 132 | public Double evalNumber(String xml, String path) 133 | { 134 | return (Double) eval(xml, path, XPathConstants.NUMBER); 135 | } 136 | 137 | public Node evalNode(String xml, String path) 138 | { 139 | return (Node) eval(xml, path, XPathConstants.NODE); 140 | } 141 | 142 | public NodeList evalNodeList(String xml, String path) 143 | { 144 | return (NodeList) eval(xml, path, XPathConstants.NODESET); 145 | } 146 | 147 | /** 148 | * Reusable, non-thread-safe version of {@link StringReader}. 149 | */ 150 | public static class ReusableStringReader 151 | extends Reader 152 | { 153 | 154 | private String str = null; 155 | private int length = -1; 156 | private int next = 0; 157 | private int mark = 0; 158 | 159 | public ReusableStringReader() 160 | { 161 | } 162 | 163 | public void set(String s) 164 | { 165 | this.str = s; 166 | this.length = s.length(); 167 | this.mark = 0; 168 | this.next = 0; 169 | } 170 | 171 | /** 172 | * Check to make sure that the stream has not been closed 173 | */ 174 | private void ensureOpen() 175 | throws IOException 176 | { 177 | if (str == null) { 178 | throw new IOException("Stream closed"); 179 | } 180 | } 181 | 182 | @Override 183 | public int read() 184 | throws IOException 185 | { 186 | ensureOpen(); 187 | if (next >= length) { 188 | return -1; 189 | } 190 | return str.charAt(next++); 191 | } 192 | 193 | @Override 194 | public int read(char cbuf[], int off, int len) 195 | throws IOException 196 | { 197 | ensureOpen(); 198 | if ((off < 0) || (off > cbuf.length) || (len < 0) 199 | || ((off + len) > cbuf.length) || ((off + len) < 0)) { 200 | throw new IndexOutOfBoundsException(); 201 | } 202 | else if (len == 0) { 203 | return 0; 204 | } 205 | if (next >= length) { 206 | return -1; 207 | } 208 | int n = Math.min(length - next, len); 209 | str.getChars(next, next + n, cbuf, off); 210 | next += n; 211 | return n; 212 | } 213 | 214 | @Override 215 | public long skip(long ns) 216 | throws IOException 217 | { 218 | ensureOpen(); 219 | if (next >= length) { 220 | return 0; 221 | } 222 | // Bound skip by beginning and end of the source 223 | long n = Math.min(length - next, ns); 224 | n = Math.max(-next, n); 225 | next += n; 226 | return n; 227 | } 228 | 229 | @Override 230 | public boolean ready() 231 | throws IOException 232 | { 233 | ensureOpen(); 234 | return true; 235 | } 236 | 237 | @Override 238 | public boolean markSupported() 239 | { 240 | return true; 241 | } 242 | 243 | @Override 244 | public void mark(int readAheadLimit) 245 | throws IOException 246 | { 247 | if (readAheadLimit < 0) { 248 | throw new IllegalArgumentException("Read-ahead limit < 0"); 249 | } 250 | ensureOpen(); 251 | mark = next; 252 | } 253 | 254 | @Override 255 | public void reset() 256 | throws IOException 257 | { 258 | ensureOpen(); 259 | next = mark; 260 | } 261 | 262 | @Override 263 | public void close() 264 | { 265 | str = null; 266 | } 267 | } 268 | } -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/window/FirstNonNullValueFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.wgzhao.presto.udfs.window; 19 | 20 | import io.trino.spi.function.WindowFunctionSignature; 21 | 22 | import java.util.List; 23 | 24 | @WindowFunctionSignature(name = "first_non_null_value", typeVariable = "T", returnType = "T", argumentTypes = "T") 25 | public class FirstNonNullValueFunction 26 | extends FirstOrLastValueIgnoreNullFunction 27 | { 28 | public FirstNonNullValueFunction(List argumentChannels) 29 | { 30 | super(argumentChannels, Direction.FIRST); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/window/FirstOrLastValueIgnoreNullFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.wgzhao.presto.udfs.window; 19 | 20 | import io.trino.spi.block.BlockBuilder; 21 | import io.trino.spi.function.ValueWindowFunction; 22 | 23 | import java.util.List; 24 | 25 | import static com.google.common.collect.Iterables.getOnlyElement; 26 | 27 | public class FirstOrLastValueIgnoreNullFunction 28 | extends ValueWindowFunction 29 | { 30 | private final int argumentChannel; 31 | private final Direction d; 32 | // data for caching, see processRow 33 | private int start = -1; 34 | private int end = -1; 35 | private int nonNullPositionInFrame = -1; 36 | 37 | protected FirstOrLastValueIgnoreNullFunction(List argumentChannels, Direction d) 38 | { 39 | this.argumentChannel = getOnlyElement(argumentChannels); 40 | this.d = d; 41 | } 42 | 43 | @Override 44 | public void processRow(BlockBuilder output, int frameStart, int frameEnd, int currentPosition) 45 | { 46 | // processRow is called for each row inside the frame => it will be costly to find non null last value everytime 47 | // => cache that value in object 48 | if (frameStart < 0) { 49 | output.appendNull(); 50 | return; 51 | } 52 | 53 | if (frameStart == start && frameEnd == end) { 54 | // Found cached value in frame 55 | windowIndex.appendTo(argumentChannel, nonNullPositionInFrame, output); 56 | return; 57 | } 58 | 59 | int pos = -1; 60 | switch (d) { 61 | case LAST: 62 | pos = getLastNotNullPosition(frameStart, frameEnd); 63 | break; 64 | case FIRST: 65 | pos = getFirstNotNullPosition(frameStart, frameEnd); 66 | break; 67 | default: 68 | break; 69 | } 70 | 71 | if (pos != -1) { 72 | windowIndex.appendTo(argumentChannel, pos, output); 73 | nonNullPositionInFrame = pos; 74 | start = frameStart; 75 | end = frameEnd; 76 | return; 77 | } 78 | 79 | // append NULL if no non-null value found 80 | output.appendNull(); 81 | } 82 | 83 | private int getLastNotNullPosition(int frameStart, int frameEnd) 84 | { 85 | int i; 86 | for (i = frameEnd; i >= frameStart; i--) { 87 | if (!windowIndex.isNull(argumentChannel, i)) { 88 | return i; 89 | } 90 | } 91 | 92 | return -1; 93 | } 94 | 95 | private int getFirstNotNullPosition(int frameStart, int frameEnd) 96 | { 97 | int i; 98 | for (i = frameStart; i <= frameEnd; i++) { 99 | if (!windowIndex.isNull(argumentChannel, i)) { 100 | return i; 101 | } 102 | } 103 | 104 | return -1; 105 | } 106 | 107 | public enum Direction 108 | { 109 | FIRST, 110 | LAST 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/wgzhao/presto/udfs/window/LastNonNullValueFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.wgzhao.presto.udfs.window; 19 | 20 | import io.trino.spi.function.WindowFunctionSignature; 21 | 22 | import java.util.List; 23 | 24 | @WindowFunctionSignature(name = "last_non_null_value", typeVariable = "T", returnType = "T", argumentTypes = "T") 25 | public class LastNonNullValueFunction 26 | extends FirstOrLastValueIgnoreNullFunction 27 | { 28 | public LastNonNullValueFunction(List argumentChannels) 29 | { 30 | super(argumentChannels, Direction.LAST); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.trino.spi.Plugin: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Copyright 2021 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | com.wgzhao.presto.udfs.UdfPlugin 19 | -------------------------------------------------------------------------------- /src/main/resources/closedate.dat.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wgzhao/presto-udfs/dae21c8533d1b640c394c8c16581adffea96e05c/src/main/resources/closedate.dat.gz -------------------------------------------------------------------------------- /src/main/resources/ip2region.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wgzhao/presto-udfs/dae21c8533d1b640c394c8c16581adffea96e05c/src/main/resources/ip2region.db -------------------------------------------------------------------------------- /src/main/resources/iso3166.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wgzhao/presto-udfs/dae21c8533d1b640c394c8c16581adffea96e05c/src/main/resources/iso3166.csv.gz -------------------------------------------------------------------------------- /src/main/resources/presto-udfs-services.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wgzhao/presto-udfs/dae21c8533d1b640c394c8c16581adffea96e05c/src/main/resources/presto-udfs-services.jar -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestBankFunctions.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs; 2 | 3 | import io.airlift.slice.Slice; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import com.wgzhao.presto.udfs.scalar.BankFunction; 10 | 11 | import static io.airlift.slice.Slices.utf8Slice; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertFalse; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | public class TestBankFunctions 17 | { 18 | /** 19 | * code = CEB, name = 中国光大银行 20 | * code = ICBC, name = 中国工商银行 21 | * code = ICBC, name = 中国工商银行 22 | * code = CCB, name = 中国建设银行 23 | * code = CCB, name = 中国建设银行 24 | * code = ABC, name = 中国农业银行 25 | * code = GDB, name = 广发银行 26 | * code = CIB, name = 兴业银行 27 | * code = PSBC, name = 中国邮政储蓄银行 28 | * code = CMB, name = 招商银行 29 | * code = CITIC, name = 中信银行 30 | * code = BOC, name = 中国银行 31 | * code = BOC, name = 中国银行 32 | * code = BOC, name = 中国银行 33 | */ 34 | List cards = Arrays.asList("622664", "955880", "622203", "621700292010", "62170029", "622848109", "62179226", "6229093", 35 | "62218855", "62258873", "621768160266", "62175675", "62166075", "45635175"); 36 | @Test 37 | public void testBankCode() 38 | { 39 | Slice code = BankFunction.getBankCode(utf8Slice(cards.get(0))); 40 | assertEquals("CEB", code.toStringUtf8()); 41 | } 42 | 43 | @Test 44 | public void testBankName() 45 | { 46 | Slice name = BankFunction.getBankName(utf8Slice(cards.get(0))); 47 | assertEquals("中国光大银行", name.toStringUtf8()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestExtendDatetimeFunctions.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs; 2 | 3 | import static com.wgzhao.presto.udfs.scalar.ExtendedDateTimeFunctions.toDate8; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.airlift.slice.Slices.utf8Slice; 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertNull; 9 | 10 | public class TestExtendDatetimeFunctions 11 | { 12 | @Test 13 | public void testToDate8() 14 | { 15 | assertNull(toDate8(null)); 16 | assertNull(toDate8(utf8Slice(""))); 17 | assertNull(toDate8(utf8Slice(" "))); 18 | assertNull(toDate8(utf8Slice("abc"))); 19 | assertNull(toDate8(utf8Slice("2021-02"))); 20 | assertEquals("20190101", toDate8(utf8Slice("20190101")).toStringUtf8()); 21 | assertEquals("20190101", toDate8(utf8Slice("2019-01-01")).toStringUtf8()); 22 | assertEquals("20190101", toDate8(utf8Slice("2019-01-01 00:00:00")).toStringUtf8()); 23 | assertEquals("20190101", toDate8(utf8Slice("2019-01-01 00:00:00.000")).toStringUtf8()); 24 | assertEquals("20190101", toDate8(utf8Slice("2019-01-01 00:00:00.0000")).toStringUtf8()); 25 | assertEquals("20190101", toDate8(utf8Slice("2019-01-01 00:00:00.00000")).toStringUtf8()); 26 | assertEquals("20190101", toDate8(utf8Slice("2019-01-01 00:00:00.000000")).toStringUtf8()); 27 | assertEquals("20190101", toDate8(utf8Slice("2019-01-01 00:00:00.0000000")).toStringUtf8()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestIP2RegionFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.wgzhao.presto.udfs; 20 | 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static com.wgzhao.presto.udfs.scalar.IP2RegionFunction.ip2region; 24 | import static io.airlift.slice.Slices.utf8Slice; 25 | import static org.junit.jupiter.api.Assertions.assertEquals; 26 | import static org.junit.jupiter.api.Assertions.assertNull; 27 | 28 | public class TestIP2RegionFunctions 29 | { 30 | @Test 31 | public void testIP2Region() 32 | { 33 | assertNull(ip2region(null)); 34 | assertNull(ip2region(utf8Slice(""))); 35 | assertNull(ip2region(utf8Slice("a.b.c"))); 36 | assertNull(ip2region(utf8Slice("192.168.1.a"))); 37 | assertEquals("中国||北京|北京市|腾讯", ip2region(utf8Slice("119.29.29.29")).toStringUtf8()); 38 | assertEquals("中国", ip2region(utf8Slice("119.29.29.29"), utf8Slice("g")).toStringUtf8()); 39 | assertEquals("腾讯", ip2region(utf8Slice("119.29.29.29"), utf8Slice("i")).toStringUtf8()); 40 | assertEquals("China", ip2region(utf8Slice("119.29.29.29"), utf8Slice("en")).toStringUtf8()); 41 | assertEquals("CN", ip2region(utf8Slice("119.29.29.29"), utf8Slice("m2")).toStringUtf8()); 42 | assertEquals("CHN", ip2region(utf8Slice("119.29.29.29"), utf8Slice("m3")).toStringUtf8()); 43 | assertEquals("156", ip2region(utf8Slice("119.29.29.29"), utf8Slice("digit")).toStringUtf8()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestIPFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.wgzhao.presto.udfs; 20 | 21 | import com.wgzhao.presto.udfs.scalar.IPFunction; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import static io.airlift.slice.Slices.utf8Slice; 25 | import static org.junit.jupiter.api.Assertions.assertEquals; 26 | import static org.junit.jupiter.api.Assertions.assertNull; 27 | 28 | public class TestIPFunctions 29 | { 30 | @Test 31 | public void testIPToInt() 32 | { 33 | assertEquals(3232235777L, IPFunction.ip2int(utf8Slice("192.168.1.1"))); 34 | assertEquals(16843008L , IPFunction.ip2int(utf8Slice("1.1.1.0"))); 35 | assertEquals(16777217L, IPFunction.ip2int(utf8Slice("1.0.0.1"))); 36 | assertNull(IPFunction.ip2int(null)); 37 | assertNull(IPFunction.ip2int(utf8Slice("abc"))); 38 | assertNull(IPFunction.ip2int(utf8Slice("a.b.c.d"))); 39 | assertNull(IPFunction.ip2int(utf8Slice("1.1.1.a"))); 40 | } 41 | 42 | @Test 43 | public void testIntToIP() 44 | { 45 | assertNull(IPFunction.int2ip(null)); 46 | assertNull(IPFunction.int2ip(-12L)); 47 | assertEquals("192.168.1.1", IPFunction.int2ip(3232235777L).toStringUtf8()); 48 | assertEquals("1.1.1.0", IPFunction.int2ip(16843008L).toStringUtf8()); 49 | assertEquals("1.0.0.1", IPFunction.int2ip(16777217L).toStringUtf8()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestIdCardFunctions.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs; 2 | 3 | import com.wgzhao.presto.udfs.scalar.IdCardFunctions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.airlift.slice.Slices.utf8Slice; 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertFalse; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | public class TestIdCardFunctions 12 | { 13 | private final String illegalId = "430203198607230123"; 14 | private final String validStr = "610923195408220390"; 15 | 16 | @Test 17 | public void testIdCheck() { 18 | 19 | boolean isIdCard = IdCardFunctions.idCheck(utf8Slice(illegalId)); 20 | assertFalse(isIdCard); 21 | isIdCard = IdCardFunctions.idCheck(utf8Slice(validStr)); 22 | assertTrue(isIdCard); 23 | } 24 | 25 | @Test 26 | public void testIdGetBirthday() 27 | { 28 | assertEquals(0L, IdCardFunctions.idCheckWithBirthday(utf8Slice(illegalId))); 29 | assertEquals(19540822L, IdCardFunctions.idCheckWithBirthday(utf8Slice(validStr))); 30 | } 31 | 32 | @Test 33 | public void testBirthday() 34 | { 35 | Long birthFromIdCard = IdCardFunctions.getBirthFromIdCard(utf8Slice(validStr)); 36 | assertEquals(19540822L, birthFromIdCard); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestNumberFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.wgzhao.presto.udfs; 20 | 21 | import com.wgzhao.presto.udfs.scalar.ExtendedNumberFunctions; 22 | import io.airlift.slice.Slice; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import static io.airlift.slice.Slices.utf8Slice; 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertNull; 28 | import static org.junit.jupiter.api.Assertions.assertTrue; 29 | 30 | public class TestNumberFunctions 31 | { 32 | long num = 103543; 33 | String numStr = String.valueOf(num); 34 | String chineseNum = "十万三千五百四十三"; 35 | String chineseCapNum = "拾万叁仟伍佰肆拾叁"; 36 | 37 | @Test 38 | public void testNumToCh() 39 | { 40 | Slice res; 41 | 42 | res = ExtendedNumberFunctions.convertArabiaNumberToChineseNumber(num, true); 43 | assertEquals(chineseNum, res.toStringUtf8()); 44 | res = ExtendedNumberFunctions.convertArabiaNumberToChineseNumber(utf8Slice(numStr), true); 45 | assertEquals(chineseNum, res.toStringUtf8()); 46 | 47 | res = ExtendedNumberFunctions.convertArabiaNumberToChineseNumber(num); 48 | assertEquals(chineseCapNum, res.toStringUtf8()); 49 | res = ExtendedNumberFunctions.convertArabiaNumberToChineseNumber(utf8Slice(numStr)); 50 | assertEquals(chineseCapNum, res.toStringUtf8()); 51 | 52 | } 53 | 54 | @Test 55 | public void testChToNum() 56 | { 57 | long res; 58 | assertNull(ExtendedNumberFunctions.convertChineseNumberToArabiaNumber(null)); 59 | assertNull(ExtendedNumberFunctions.convertChineseNumberToArabiaNumber(utf8Slice("abc"))); 60 | assertNull(ExtendedNumberFunctions.convertChineseNumberToArabiaNumber(utf8Slice(""))); 61 | res = ExtendedNumberFunctions.convertChineseNumberToArabiaNumber(utf8Slice("壹" + chineseCapNum)); 62 | assertEquals(num, res); 63 | res = ExtendedNumberFunctions.convertChineseNumberToArabiaNumber(utf8Slice("一" + chineseNum)); 64 | assertEquals(num, res); 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestPinyinFunction.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs; 2 | 3 | import com.wgzhao.presto.udfs.scalar.PinyinFunction; 4 | import io.airlift.slice.Slice; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static io.airlift.slice.Slices.utf8Slice; 9 | import static org.junit.jupiter.api.Assertions.assertNull; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | public class TestPinyinFunction 13 | { 14 | 15 | @Test 16 | public void testPinyin() 17 | { 18 | Slice result; 19 | result = PinyinFunction.pinyin(utf8Slice("中")); 20 | assertEquals( "zhong", result.toStringUtf8()); 21 | 22 | result = PinyinFunction.pinyin(null); 23 | assertNull(result); 24 | 25 | result = PinyinFunction.pinyin(utf8Slice("")); 26 | assertEquals("", result.toStringUtf8()); 27 | 28 | result = PinyinFunction.pinyin(utf8Slice("english")); 29 | assertEquals("english", result.toStringUtf8()); 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestStringFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.wgzhao.presto.udfs; 20 | 21 | import com.wgzhao.presto.udfs.scalar.ExtendedStringFunctions; 22 | import io.airlift.slice.Slice; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import javax.script.ScriptEngineFactory; 26 | import javax.script.ScriptEngineManager; 27 | 28 | import java.util.List; 29 | 30 | import static io.airlift.slice.Slices.utf8Slice; 31 | import static org.junit.jupiter.api.Assertions.assertEquals; 32 | import static org.junit.jupiter.api.Assertions.assertNull; 33 | 34 | public class TestStringFunctions 35 | { 36 | @Test 37 | public void testToChinese() 38 | { 39 | Slice res; 40 | res = ExtendedStringFunctions.convertDigitalToChinese(utf8Slice("2002")); 41 | assertEquals("贰零零贰", res.toStringUtf8()); 42 | res = ExtendedStringFunctions.convertDigitalToChinese(utf8Slice("2002"), true); 43 | assertEquals("二〇〇二", res.toStringUtf8()); 44 | } 45 | 46 | @Test 47 | public void testEval() 48 | { 49 | double res; 50 | res = ExtendedStringFunctions.eval(utf8Slice("23+12*1")); 51 | assertEquals(35, res); 52 | assertNull(ExtendedStringFunctions.eval(utf8Slice("1+2i"))); 53 | assertNull(ExtendedStringFunctions.eval(utf8Slice("23-"))); 54 | } 55 | 56 | @Test 57 | public void listAllScriptManager() 58 | { 59 | ScriptEngineManager sem = new ScriptEngineManager(); 60 | List factories = sem.getEngineFactories(); 61 | for (ScriptEngineFactory factory : factories) 62 | System.out.println(factory.getEngineName() + " " + factory.getEngineVersion() + " " + factory.getNames()); 63 | if (factories.isEmpty()) 64 | System.out.println("No Script Engines found"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestTradeDateFunctions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | *

10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | *

12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.wgzhao.presto.udfs; 20 | 21 | import io.airlift.slice.Slice; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import static com.wgzhao.presto.udfs.scalar.TradeDateFunctions.udfAddNormalDays; 25 | import static com.wgzhao.presto.udfs.scalar.TradeDateFunctions.udfAddTradeDays; 26 | import static com.wgzhao.presto.udfs.scalar.TradeDateFunctions.udfIsTradeDate; 27 | import static com.wgzhao.presto.udfs.scalar.TradeDateFunctions.udfLastTradeDate; 28 | import static io.airlift.slice.Slices.utf8Slice; 29 | import static org.junit.jupiter.api.Assertions.assertEquals; 30 | import static org.junit.jupiter.api.Assertions.assertFalse; 31 | import static org.junit.jupiter.api.Assertions.assertNull; 32 | import static org.junit.jupiter.api.Assertions.assertTrue; 33 | 34 | public class TestTradeDateFunctions 35 | { 36 | private Slice res; 37 | private final Slice tradeDate = utf8Slice("20210906"); 38 | private final Slice nonTradeDate = utf8Slice("20210904"); 39 | @Test 40 | public void testLastTradeDate() 41 | { 42 | res = udfLastTradeDate(tradeDate); 43 | assertEquals("20210903", res.toStringUtf8()); 44 | } 45 | 46 | @Test 47 | public void testIsTradeDate() 48 | { 49 | assertTrue(udfIsTradeDate(tradeDate)); 50 | assertFalse(udfIsTradeDate(utf8Slice("20210904"))); 51 | assertFalse(udfIsTradeDate(null)); 52 | } 53 | 54 | @Test 55 | public void testAddTradeDays() 56 | { 57 | res = udfAddTradeDays(tradeDate, 4L); 58 | assertEquals("20210910", res.toStringUtf8()); 59 | 60 | res = udfAddTradeDays(nonTradeDate, 1L); 61 | assertEquals("20210906", res.toStringUtf8()); 62 | 63 | assertNull(udfAddTradeDays(null, 1L)); 64 | assertNull(udfAddTradeDays(utf8Slice(""), 1L)); 65 | 66 | assertNull(udfAddTradeDays(utf8Slice("19900101"), 1L)); 67 | assertNull(udfAddTradeDays(utf8Slice("20460104"), 1L)); 68 | } 69 | 70 | @Test 71 | public void testAddNormalDays() 72 | { 73 | res = udfAddNormalDays(tradeDate, 1L); 74 | assertEquals("20210907", res.toStringUtf8()); 75 | 76 | res = udfAddNormalDays(nonTradeDate, 1L); 77 | assertEquals("20210903", res.toStringUtf8()); 78 | 79 | assertNull(udfAddNormalDays(null, 1L)); 80 | assertNull(udfAddNormalDays(utf8Slice(""), 1L)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/wgzhao/presto/udfs/TestXmlFunctions.java: -------------------------------------------------------------------------------- 1 | package com.wgzhao.presto.udfs; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.wgzhao.presto.udfs.scalar.XmlFunctions; 5 | import io.airlift.slice.Slice; 6 | import io.trino.spi.block.Block; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import static io.airlift.slice.Slices.utf8Slice; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertFalse; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | public class TestXmlFunctions 15 | { 16 | 17 | @Test 18 | public void testNullXPath() 19 | { 20 | String xml = "" + 21 | "b1b2" + 22 | "b3c1" + 23 | "c2"; 24 | String xpath = "a/text()"; 25 | final Block result = XmlFunctions.xpath(utf8Slice(xml), utf8Slice(xpath)); 26 | assertEquals(0, result.getPositionCount()); 27 | } 28 | @Test 29 | public void testXPathString() { 30 | String xml = "" + 31 | "" + 32 | " " + 33 | " " + 34 | " fd_3630dadf1ad2d0" + 35 | " 疑似不规范反馈" + 36 | " " + 37 | " " + 38 | " Type" + 39 | " 疑似非本人开户" + 40 | " " + 41 | " " + 42 | " Department_Name" + 43 | " " + 44 | " " + 45 | " name" + 46 | " 长沙人民东路证券营业部" + 47 | " " + 48 | " " + 49 | " " + 50 | " " + 51 | ""; 52 | String xpath = "//string[text()=\"name\"]/following-sibling::string/text()"; 53 | Slice result2 = XmlFunctions.xpathString(utf8Slice(xml), utf8Slice(xpath)); 54 | assert result2 != null; 55 | assertEquals( "长沙人民东路证券营业部", result2.toStringUtf8()); 56 | } 57 | 58 | @Test 59 | public void testNullMultiResultXPath() { 60 | String xml = "b1b2b3c1c2"; 61 | String xpath = "a/b/text()"; 62 | final Block result = XmlFunctions.xpath(utf8Slice(xml), utf8Slice(xpath)); 63 | final ImmutableList expected = ImmutableList.of("b1", "b2", "b3"); 64 | Slice actual = result.getSlice(0, 0, result.getPositionCount()); 65 | System.out.println(actual.toStringUtf8()); 66 | } 67 | 68 | @Test 69 | public void testXpathBoolean() { 70 | String xml = "1"; 71 | String path1 = "a/b"; 72 | String path2 = "a/b = 2"; 73 | Boolean result = XmlFunctions.xpathBoolean(utf8Slice(xml), utf8Slice(path1)); 74 | assertTrue(result); 75 | result = XmlFunctions.xpathBoolean(utf8Slice(xml), utf8Slice(path2)); 76 | assertFalse(result); 77 | } 78 | 79 | @Test 80 | public void testXpathDouble() 81 | { 82 | assertEquals( 83 | XmlFunctions.xpathDouble(utf8Slice("12"),utf8Slice("sum(a/b)")), 84 | 3.0); 85 | } 86 | 87 | @Test 88 | public void testXpathLong() 89 | { 90 | assertEquals( 91 | XmlFunctions.xpathLong(utf8Slice("12"),utf8Slice("sum(a/b)")), 92 | 3); 93 | } 94 | 95 | 96 | @Test 97 | public void testXpathString() 98 | { 99 | assertEquals( 100 | XmlFunctions.xpathString(utf8Slice("b1b2"), 101 | utf8Slice("a/b[@id=\"b_2\"]")), 102 | utf8Slice("b2")); 103 | } 104 | } 105 | --------------------------------------------------------------------------------