├── rebar.config ├── attic ├── firebird.conf └── errmsgs.c ├── src ├── efirebirdsql.app.src ├── efirebirdsql_socket.erl ├── efirebirdsql_charset.erl ├── efirebirdsql.erl ├── efirebirdsql_server.erl ├── efirebirdsql_srp.erl ├── efirebirdsql_decfloat.erl ├── efirebirdsql_conv.erl ├── efirebirdsql_protocol.erl ├── efirebirdsql_op.erl └── efirebirdsql_tz_map.erl ├── Makefile ├── .github └── workflows │ ├── test_fb25.yml │ └── test_fb3.yml ├── LICENSE ├── test ├── efirebirdsql_charset_tests.erl ├── efirebirdsql_conv_tests.erl ├── efirebirdsql_srp_tests.erl ├── efirebirdsql_protocol_tests.erl └── efirebirdsql_tests.erl ├── include └── efirebirdsql.hrl └── README.rst /rebar.config: -------------------------------------------------------------------------------- 1 | {eunit_opts, [verbose]}. 2 | {hex, [{doc, edoc}]}. 3 | -------------------------------------------------------------------------------- /attic/firebird.conf: -------------------------------------------------------------------------------- 1 | RemoteBindAddress = 0.0.0.0 2 | 3 | DefaultTimeZone = GMT 4 | AuthServer = Srp256,Srp,Legacy_Auth 5 | WireCryptPlugin = ChaCha64, ChaCha, Arc4 6 | WireCrypt = Enabled 7 | -------------------------------------------------------------------------------- /src/efirebirdsql.app.src: -------------------------------------------------------------------------------- 1 | {application,efirebirdsql, [ 2 | {description,"Firebird Client"}, 3 | {vsn,"0.9.11"}, 4 | {modules,[]}, 5 | {registered,[]}, 6 | {applications,[kernel,stdlib]}, 7 | {env,[]}, 8 | {included_applications,[]}, 9 | {licenses,["MIT"]}, 10 | {links,[{"Github", "https://github.com/nakagami/efirebirdsql"}]} 11 | ]}. 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR = rebar3 2 | 3 | all: compile 4 | 5 | compile: 6 | @$(REBAR) update 7 | @$(REBAR) upgrade --all 8 | @$(REBAR) compile 9 | 10 | clean: 11 | @$(REBAR) clean 12 | @rm -f build.plt 13 | @rm -f rebar.lock 14 | 15 | test: compile 16 | @$(REBAR) eunit 17 | 18 | dialyzer: build.plt compile 19 | dialyzer --plt $< _build/default/lib/efirebirdsql/ebin/ 20 | 21 | build.plt: 22 | dialyzer -q --build_plt --apps erts kernel stdlib crypto --output_plt $@ 23 | 24 | publish: 25 | @$(REBAR) hex publish 26 | -------------------------------------------------------------------------------- /.github/workflows/test_fb25.yml: -------------------------------------------------------------------------------- 1 | name: Erlang CI Fb25 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | name: Build and test 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout sources 17 | uses: actions/checkout@v3 18 | - name: Setup FirebirdSQL container 19 | uses: juarezr/firebirdsql-github-action@v1.2.0 20 | with: 21 | version: '2.5-ss' 22 | isc_password: "masterkey" 23 | - name: Set up Erlang 24 | uses: erlef/setup-beam@v1 25 | with: 26 | otp-version: '26.0' 27 | rebar3-version: '3' 28 | - name: Run tests 29 | run: rebar3 eunit 30 | -------------------------------------------------------------------------------- /.github/workflows/test_fb3.yml: -------------------------------------------------------------------------------- 1 | name: Erlang CI Fb3 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up Firebird 18 | run: | 19 | sudo apt install firebird3.0-server -y 20 | sudo cp attic/firebird.conf /etc/firebird/3.0 21 | sudo systemctl restart firebird3.0 22 | sudo chmod 0664 /etc/firebird/3.0/SYSDBA.password 23 | grep '=' /etc/firebird/3.0/SYSDBA.password |sed 's/^/export /' >test_user.env 24 | - name: Set up Erlang 25 | uses: erlef/setup-beam@v1 26 | with: 27 | otp-version: '26.0' 28 | rebar3-version: '3' 29 | - name: Run tests 30 | run: | 31 | source test_user.env 32 | rebar3 eunit 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Hajime Nakagami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test/efirebirdsql_charset_tests.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2022 Hajime Nakagami 3 | 4 | -module(efirebirdsql_charset_tests). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | -include("efirebirdsql.hrl"). 8 | 9 | default_charset_test() -> 10 | DbName = lists:flatten(io_lib:format("/tmp/~p.fdb", [erlang:system_time()])), 11 | {ok, C} = efirebirdsql:connect( 12 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, 13 | [{createdb, true}]), 14 | ok = efirebirdsql:execute(C, <<"select RDB$CHARACTER_SET_NAME from RDB$DATABASE">>), 15 | ?assertEqual({ok, [{<<"RDB$CHARACTER_SET_NAME">>, <<"UTF8">>}]}, efirebirdsql:fetchone(C)), 16 | 17 | ok = efirebirdsql:close(C), 18 | ok. 19 | 20 | charset_test() -> 21 | DbName = lists:flatten(io_lib:format("/tmp/~p.fdb", [erlang:system_time()])), 22 | {ok, C} = efirebirdsql:connect( 23 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, 24 | [{createdb, true}, {charset, cp932}]), 25 | ok = efirebirdsql:execute(C, <<"select RDB$CHARACTER_SET_NAME from RDB$DATABASE">>), 26 | ?assertEqual({ok, [{<<"RDB$CHARACTER_SET_NAME">>, <<"SJIS_0208">>}]}, efirebirdsql:fetchone(C)), 27 | 28 | ok = efirebirdsql:close(C), 29 | ok. 30 | -------------------------------------------------------------------------------- /src/efirebirdsql_socket.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2016-2021 Hajime Nakagami 3 | 4 | -module(efirebirdsql_socket). 5 | 6 | -export([send/2, recv/2, recv_align/2, recv_null_bitmap/2]). 7 | 8 | -include("efirebirdsql.hrl"). 9 | 10 | send(Conn, Data) when Conn#conn.write_state =:= undefined -> 11 | gen_tcp:send(Conn#conn.sock, Data); 12 | send(Conn, Message) -> 13 | Encrypted = crypto:crypto_update(Conn#conn.write_state, Message), 14 | gen_tcp:send(Conn#conn.sock, Encrypted). 15 | 16 | recv(_Conn, Len) when Len =:= 0 -> 17 | {ok, []}; 18 | recv(Conn, Len) when Conn#conn.read_state =:= undefined -> 19 | gen_tcp:recv(Conn#conn.sock, Len); 20 | recv(Conn, Len) -> 21 | {T, Encrypted} = gen_tcp:recv(Conn#conn.sock, Len), 22 | Message = crypto:crypto_update(Conn#conn.read_state, Encrypted), 23 | {T, Message}. 24 | 25 | recv_align(Conn, Len) -> 26 | {T, V} = recv(Conn, Len), 27 | if 28 | Len rem 4 =/= 0 -> recv(Conn, 4 - (Len rem 4)); 29 | true -> nil 30 | end, 31 | {T, V}. 32 | 33 | recv_null_bitmap(_Conn, BitLen) when BitLen =:= 0 -> 34 | <<>>; 35 | recv_null_bitmap(Conn, BitLen) -> 36 | Div8 = BitLen div 8, 37 | Len = if 38 | BitLen rem 8 =:= 0 -> Div8; 39 | BitLen rem 8 =/= 0 -> Div8 + 1 40 | end, 41 | {ok, Buf} = recv_align(Conn, Len), 42 | <> = Buf, 43 | Bitmap. 44 | -------------------------------------------------------------------------------- /include/efirebirdsql.hrl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2016-2022 Hajime Nakagami 3 | 4 | -record(column, { 5 | name :: binary() | undefined, 6 | seq :: pos_integer() | undefined, 7 | type :: atom() | undefined, 8 | scale :: -1 | pos_integer() | undefined, 9 | length :: -1 | pos_integer() | undefined, 10 | null_ind :: true | false |undefined 11 | }). 12 | 13 | -type column() :: #column{}. 14 | 15 | -record(conn, { 16 | sock, 17 | user :: string(), 18 | password :: string(), 19 | charset :: atom(), 20 | auto_commit :: boolean(), 21 | client_private :: integer(), 22 | client_public :: integer(), 23 | auth_data :: string() | undefined, 24 | auth_plugin :: string(), 25 | wire_crypt :: boolean(), 26 | read_state, % RC4 crypto stream state 27 | write_state, % RC4 crypto stream state 28 | db_handle, 29 | trans_handle, 30 | accept_version, 31 | timezone :: string() | nil 32 | }). 33 | 34 | -type conn() :: #conn{}. 35 | 36 | -record(stmt, { 37 | sql :: binary() | undefined, 38 | stmt_handle = nil, 39 | stmt_type = nil, 40 | xsqlvars = [], 41 | rows = nil, %% segment rows values 42 | more_data = false, %% has more rows 43 | closed = true 44 | }). 45 | 46 | -type stmt() :: #stmt{}. 47 | 48 | -record(state, { 49 | parameters = [], 50 | connection, 51 | statement, 52 | error_no, 53 | error_message 54 | }). 55 | 56 | -type state() :: #state{}. 57 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | efirebirdsql 3 | ============= 4 | 5 | Erlang Firebird client library. 6 | 7 | Requirements 8 | ---------------------- 9 | 10 | * Firebird 2.5 or higher 11 | 12 | Examples 13 | ----------- 14 | 15 | Simple query and fetch all results:: 16 | 17 | {ok, C} = efirebirdsql:connect( 18 | "server", "username", "password", "/path/to/database", []), 19 | ok = efirebirdsql:execute(C, <<"select * from foo">>), 20 | {ok, Results} = efirebirdsql:fetchall(C). 21 | 22 | Fetch one by one:: 23 | 24 | ok = efirebirdsql:execute(C, <<"select * from foo">>), 25 | {ok, R1} = efirebirdsql:fetchone(C), 26 | {ok, R2} = efirebirdsql:fetchone(C), 27 | {ok, R3} = efirebirdsql:fetchone(C). 28 | 29 | Separate start_link() and connect():: 30 | 31 | C = start_link(), 32 | ok = efirebirdsql:connect(C, 33 | "server", "username", "password", "/path/to/database", []), 34 | 35 | Commit and rollback transaction:: 36 | 37 | {ok, C} = efirebirdsql:connect( 38 | "server", "username", "password", "/path/to/database", [{auto_commit, false}]), 39 | ok = efirebirdsql:execute(C, <<"update foo set column='A'">>), 40 | ok = efirebirdsql:commit(), 41 | ok = efirebirdsql:execute(C, <<"update foo set column='B'">>), 42 | ok = efirebirdsql:rollback(). 43 | 44 | 45 | See also test/efirebirdsql_tests.erl 46 | 47 | Available connect option parameters 48 | ----------------------------------- 49 | 50 | .. csv-table:: 51 | :header: Name,Comment,Default,Note 52 | 53 | port, Port number, 3050 54 | charset, Charset, utf_8 55 | auto_commit, Autocommit flag, true 56 | auth_plugin, Authentication plugin name, \"Srp\", Firebird 3.0+ 57 | wire_crypt, Wire encryption flag, true, Firebird 3.0+ 58 | -------------------------------------------------------------------------------- /test/efirebirdsql_conv_tests.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2021 Hajime Nakagami 3 | 4 | -module(efirebirdsql_conv_tests). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | params_to_blr_test() -> 9 | % Firebird 2.5 10 | {Blr, Value} = efirebirdsql_conv:params_to_blr(11, maps:new(), [nil]), 11 | ?assertEqual("0502040002000E00000700FF4C", efirebirdsql_srp:to_hex(Blr)), 12 | ?assertEqual("FFFFFFFF", efirebirdsql_srp:to_hex(Value)), 13 | 14 | % Firebird 3+ 15 | {Blr1, Value1} = efirebirdsql_conv:params_to_blr(13, maps:new(), [nil]), 16 | ?assertEqual("0502040002000E00000700FF4C", efirebirdsql_srp:to_hex(Blr1)), 17 | ?assertEqual("01000000", efirebirdsql_srp:to_hex(Value1)), 18 | {Blr2, Value2} = efirebirdsql_conv:params_to_blr(13, maps:new(), ["foo", 1]), 19 | ?assertEqual("0502040004000E0300070008000700FF4C", efirebirdsql_srp:to_hex(Blr2)), 20 | ?assertEqual("00000000666F6F0000000001", efirebirdsql_srp:to_hex(Value2)), 21 | {Blr3, Value3} = efirebirdsql_conv:params_to_blr(13, maps:new(), [nil, nil, nil]), 22 | ?assertEqual("0502040006000E000007000E000007000E00000700FF4C", efirebirdsql_srp:to_hex(Blr3)), 23 | ?assertEqual("07000000", efirebirdsql_srp:to_hex(Value3)), 24 | {Blr4, Value4} = efirebirdsql_conv:params_to_blr(13, maps:new(), [nil, "foo", nil]), 25 | ?assertEqual("0502040006000E000007000E030007000E00000700FF4C", efirebirdsql_srp:to_hex(Blr4)), 26 | ?assertEqual("05000000666F6F00", efirebirdsql_srp:to_hex(Value4)), 27 | {Blr5, Value5} = efirebirdsql_conv:params_to_blr(13, maps:new(), ["foo", nil]), 28 | ?assertEqual("0502040004000E030007000E00000700FF4C", efirebirdsql_srp:to_hex(Blr5)), 29 | ?assertEqual("02000000666F6F00", efirebirdsql_srp:to_hex(Value5)). 30 | -------------------------------------------------------------------------------- /src/efirebirdsql_charset.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2022- Hajime Nakagami 3 | 4 | -module(efirebirdsql_charset). 5 | 6 | -export([get_database_charset/1]). 7 | 8 | -spec get_database_charset(atom()) -> string(). 9 | get_database_charset(utf_8) -> "UTF8"; 10 | get_database_charset(cp932) -> "SJIS_0208"; 11 | get_database_charset(euc_jp) -> "EUCJ_0208"; 12 | get_database_charset(cp727) -> "DOS737"; 13 | get_database_charset(cp437) -> "DOS437"; 14 | get_database_charset(cp850) -> "DOS850"; 15 | get_database_charset(cp865) -> "DOS865"; 16 | get_database_charset(cp860) -> "DOS860"; 17 | get_database_charset(cp863) -> "DOS863"; 18 | get_database_charset(cp775) -> "DOS775"; 19 | get_database_charset(cp862) -> "DOS862"; 20 | get_database_charset(cp864) -> "DOS864"; 21 | get_database_charset(iso8859_1) -> "ISO8859_1"; 22 | get_database_charset(iso8859_2) -> "ISO8859_2"; 23 | get_database_charset(iso8859_3) -> "ISO8859_3"; 24 | get_database_charset(iso8859_4) -> "ISO8859_4"; 25 | get_database_charset(iso8859_5) -> "ISO8859_5"; 26 | get_database_charset(iso8859_6) -> "ISO8859_6"; 27 | get_database_charset(iso8859_7) -> "ISO8859_7"; 28 | get_database_charset(iso8859_8) -> "ISO8859_8"; 29 | get_database_charset(iso8859_9) -> "ISO8859_9"; 30 | get_database_charset(iso8859_13) -> "ISO8859_13"; 31 | get_database_charset(euc_kr) -> "KSC_5601"; 32 | get_database_charset(cp852) -> "DOS852"; 33 | get_database_charset(cp857) -> "DOS857"; 34 | get_database_charset(cp861) -> "DOS861"; 35 | get_database_charset(cp866) -> "DOS866"; 36 | get_database_charset(cp869) -> "DOS869"; 37 | get_database_charset(cp1250) -> "WIN1250"; 38 | get_database_charset(cp1251) -> "WIN1251"; 39 | get_database_charset(cp1252) -> "WIN1252"; 40 | get_database_charset(cp1253) -> "WIN1253"; 41 | get_database_charset(cp1254) -> "WIN1254"; 42 | get_database_charset(cp950) -> "BIG_5"; 43 | get_database_charset(cp936) -> "GB_2312"; 44 | get_database_charset(cp1255) -> "WIN1255"; 45 | get_database_charset(cp1256) -> "WIN1256"; 46 | get_database_charset(cp1257) -> "WIN1257"; 47 | get_database_charset(koi8_r) -> "KOI8R"; 48 | get_database_charset(koi8_u) -> "KOI8U"; 49 | get_database_charset(cp1258) -> "WIN1258"; 50 | get_database_charset(_) -> "UTF8". 51 | -------------------------------------------------------------------------------- /attic/errmsgs.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2009-2016, 2023-2024 Hajime Nakagami 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | *******************************************************************************/ 23 | 24 | // 1. Get copy of Firebird 5 sources or at least src/include from Firebird 5 sources 25 | // 2. cc -I/path/to/firebird/src/include errmsgs.c 26 | // 3. ./a.out 27 | // 4. perl -pi -e 's/\@\d+/~s/g' ../src/efirebirdsql_errmsgs.erl 28 | // 5. perl -pi -e 's/";$/~n";/g' ../src/efirebirdsql_errmsgs.erl 29 | 30 | #include 31 | #include 32 | #include "firebird/impl/msg_helper.h" 33 | 34 | typedef unsigned short USHORT; 35 | typedef USHORT ISC_USHORT; 36 | typedef intptr_t ISC_STATUS; 37 | typedef long SLONG; 38 | 39 | FILE *fp; 40 | 41 | #define stringify_literal(x) #x 42 | 43 | #define FB_IMPL_MSG_NO_SYMBOL(facility, number, text) 44 | 45 | #define FB_IMPL_MSG_SYMBOL(facility, number, symbol, text) 46 | 47 | #define FB_IMPL_MSG(facility, number, symbol, sqlCode, sqlClass, sqlSubClass, text) \ 48 | output_message(make_isc_code(FB_IMPL_MSG_FACILITY_##facility, number), stringify_literal(text)); 49 | 50 | int make_isc_code(int facility, int code) { 51 | ISC_USHORT t1 = facility; 52 | t1 &= 0x1F; 53 | ISC_STATUS t2 = t1; 54 | t2 <<= 16; 55 | ISC_STATUS t3 = code; 56 | code &= 0x3FFF; 57 | return t2 | t3 | ((ISC_STATUS) 0x14000000); 58 | } 59 | 60 | void process_messages() { 61 | #include "firebird/impl/msg/all.h" 62 | } 63 | 64 | void output_message(int code, char* msg) { 65 | fprintf(fp, "get_error_msg(%d) -> %s;\n", code, msg); 66 | } 67 | 68 | int main(int argc, char *argv[]) 69 | { 70 | int i; 71 | fp = fopen("../src/efirebirdsql_errmsgs.erl", "w"); 72 | 73 | fprintf(fp, "\ 74 | \%% The contents of this file are subject to the Interbase Public\n\ 75 | \%% License Version 1.0 (the \"License\"); you may not use this file\n\ 76 | \%% except in compliance with the License. You may obtain a copy\n\ 77 | \%% of the License at http://www.Inprise.com/IPL.html\n\ 78 | \%% \n\ 79 | \%% Software distributed under the License is distributed on an\n\ 80 | \%% \"AS IS\" basis, WITHOUT WARRANTY OF ANY KIND, either express\n\ 81 | \%% or implied. See the License for the specific language governing\n\ 82 | \%% rights and limitations under the License.\n\n"); 83 | fprintf(fp, "-module(efirebirdsql_errmsgs).\n\n-export([get_error_msg/1]).\n\n"); 84 | process_messages(); 85 | fprintf(fp, "get_error_msg(_) -> \"\".\n"); 86 | 87 | fclose(fp); 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /test/efirebirdsql_srp_tests.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2015,2019 Hajime Nakagami 3 | 4 | -module(efirebirdsql_srp_tests). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | random_test() -> 9 | Username = "SYSDBA", 10 | Password = "masterkey", 11 | Salt = efirebirdsql_srp:get_salt(), 12 | ClientPrivate = efirebirdsql_srp:get_private_key(), 13 | ClientPublic = efirebirdsql_srp:client_public(ClientPrivate), 14 | V = efirebirdsql_srp:get_verifier(Username, Password, Salt), 15 | 16 | ServerPrivate = efirebirdsql_srp:get_private_key(), 17 | ServerPublic = efirebirdsql_srp:server_public(V, ServerPrivate), 18 | ServerSession = efirebirdsql_srp:server_session( 19 | Username, Password, Salt, ClientPublic, ServerPublic, ServerPrivate), 20 | {_M, ClientSession} = efirebirdsql_srp:client_proof( 21 | Username, Password, Salt, ClientPublic, ServerPublic, ClientPrivate, sha), 22 | ?assertEqual(ServerSession, ClientSession). 23 | 24 | bin_hex_test() -> 25 | ?assertEqual(efirebirdsql_srp:to_hex(<<1,2,3,255>>), "010203FF"), 26 | ?assertEqual(efirebirdsql_srp:to_hex([1,2,3,255]), "010203FF"). 27 | 28 | client_public_test() -> 29 | Private = 16#60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393, 30 | Public = efirebirdsql_srp:client_public(Private), 31 | ?assertEqual( 32 | Public, 33 | 16#712C5F8A2DB82464C4D640AE971025AA50AB64906D4F044F822E8AF8A58ADABBDBE1EFABA00BCCD4CDAA8A955BC43C3600BEAB9EBB9BD41ACC56E37F1A48F17293F24E876B53EEA6A60712D3F943769056B63202416827B400E162A8C0938D482274307585E0BC1D9DD52EFA7330B28E41B7CFCEFD9E8523FD11440EE5DE93A8 34 | ), 35 | SpecificData = efirebirdsql_srp:to_hex(Public), 36 | ?assertEqual( 37 | SpecificData, 38 | "712C5F8A2DB82464C4D640AE971025AA50AB64906D4F044F822E8AF8A58ADABBDBE1EFABA00BCCD4CDAA8A955BC43C3600BEAB9EBB9BD41ACC56E37F1A48F17293F24E876B53EEA6A60712D3F943769056B63202416827B400E162A8C0938D482274307585E0BC1D9DD52EFA7330B28E41B7CFCEFD9E8523FD11440EE5DE93A8" 39 | ). 40 | 41 | srp_sha1_test() -> 42 | Username = "SYSDBA", 43 | Password = "masterkey", 44 | Salt = efirebirdsql_srp:get_debug_salt(), 45 | ClientPrivate = efirebirdsql_srp:get_debug_private_key(), 46 | ClientPublic = efirebirdsql_srp:client_public(ClientPrivate), 47 | V = efirebirdsql_srp:get_verifier(Username, Password, Salt), 48 | 49 | ServerPrivate = efirebirdsql_srp:get_debug_private_key(), 50 | ServerPublic = efirebirdsql_srp:server_public(V, ServerPrivate), 51 | ServerSession = efirebirdsql_srp:server_session( 52 | Username, Password, Salt, ClientPublic, ServerPublic, ServerPrivate), 53 | {M, ClientSession} = efirebirdsql_srp:client_proof( 54 | Username, Password, Salt, ClientPublic, ServerPublic, ClientPrivate, sha), 55 | ?assertEqual(ServerSession, ClientSession), 56 | ?assertEqual(efirebirdsql_srp:to_hex(M), "8C12324BB6E9E683A3EE62E13905B95D69F028A9"). 57 | 58 | srp_sha256_test() -> 59 | Username = "SYSDBA", 60 | Password = "masterkey", 61 | Salt = efirebirdsql_srp:get_debug_salt(), 62 | ClientPrivate = efirebirdsql_srp:get_debug_private_key(), 63 | ClientPublic = efirebirdsql_srp:client_public(ClientPrivate), 64 | V = efirebirdsql_srp:get_verifier(Username, Password, Salt), 65 | 66 | ServerPrivate = efirebirdsql_srp:get_debug_private_key(), 67 | ServerPublic = efirebirdsql_srp:server_public(V, ServerPrivate), 68 | ServerSession = efirebirdsql_srp:server_session( 69 | Username, Password, Salt, ClientPublic, ServerPublic, ServerPrivate), 70 | {M, ClientSession} = efirebirdsql_srp:client_proof( 71 | Username, Password, Salt, ClientPublic, ServerPublic, ClientPrivate, sha256), 72 | ?assertEqual(ServerSession, ClientSession), 73 | ?assertEqual(efirebirdsql_srp:to_hex(M), "4675C18056C04B00CC2B991662324C22C6F08BB90BEB3677416B03469A770308"). 74 | -------------------------------------------------------------------------------- /src/efirebirdsql.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2016-2022 Hajime Nakagami 3 | 4 | -module(efirebirdsql). 5 | 6 | -export([start_link/0]). 7 | -export([connect/5, connect/6, ping/1, prepare/2, execute/1, execute/2, execute/3, 8 | description/1, fetchone/1, fetchall/1, commit/1, rollback/1, 9 | close/1, get_last_error/1, cancel/1, sync/1]). 10 | 11 | -export_type([connection/0, connect_option/0]). 12 | 13 | -include("efirebirdsql.hrl"). 14 | 15 | -type connection() :: pid(). 16 | -type connect_option() :: 17 | {port, PortNumber :: inet:port_number()} | 18 | {charset, Charset :: atom()} | 19 | {timeout, Timeout :: integer()} | 20 | {createdb, IsCreateDB :: boolean()} | 21 | {auto_commit, AutoCommit :: boolean()} | 22 | {pagesize, PageSize :: integer()} | 23 | {auth_plugin, AuthPlugin :: list()} | 24 | {timezone, TimeZone :: string()}. 25 | 26 | -spec start_link() -> {ok, pid()}. 27 | start_link() -> 28 | efirebirdsql_server:start_link(). 29 | 30 | -spec connect(connection(), list(), list(), list(), list(), [connect_option()]) 31 | -> {ok, connection()} | {error, Reason :: binary}. 32 | connect(C, Host, Username, Password, Database, Ops) -> 33 | case gen_server:call(C, {connect, Host, Username, Password, Database, Ops}, infinity) of 34 | ok -> {ok, C}; 35 | {error, _} -> 36 | {ok, _ErrNo, Msg} = gen_server:call(C, get_last_error, infinity), 37 | {error, Msg} 38 | end. 39 | 40 | -spec connect(list(), list(), list(), list(), [connect_option()]) 41 | -> {ok, connection()} | {error, Reason :: binary()}. 42 | connect(Host, Username, Password, Database, Ops) -> 43 | {ok, C} = start_link(), 44 | connect(C, Host, Username, Password, Database, Ops). 45 | 46 | -spec ping(connection()) 47 | -> ok | error. 48 | ping(C) -> 49 | gen_server:call(C, ping, infinity). 50 | 51 | -spec prepare(connection(), binary()) 52 | -> ok | {error, Reason :: binary()}. 53 | prepare(C, QueryString) -> 54 | case gen_server:call(C, {prepare, QueryString}, infinity) of 55 | {error, _} -> 56 | {ok, _ErrNo, Msg} = gen_server:call(C, get_last_error, infinity), 57 | {error, Msg}; 58 | R -> R 59 | end. 60 | 61 | -spec execute(connection(), binary(), list()) 62 | -> ok | {error, Reason :: binary()}. 63 | execute(C, QueryString, Params) -> 64 | case R = prepare(C, QueryString) of 65 | ok -> execute(C, Params); 66 | {error, _} -> R 67 | end. 68 | 69 | -spec execute(connection(), binary() | list()) 70 | -> ok | {error, Reason :: binary()}. 71 | execute(C, QueryString) when is_binary(QueryString) -> 72 | execute(C, QueryString, []); 73 | execute(C, Params) when is_list(Params) -> 74 | case gen_server:call(C, {execute, Params}, infinity) of 75 | ok -> ok; 76 | {error, _} -> 77 | {ok, _ErrNo, Msg} = gen_server:call(C, get_last_error, infinity), 78 | {error, Msg} 79 | end. 80 | 81 | -spec execute(connection()) 82 | -> ok | {error, Reason :: binary()}. 83 | execute(C) -> 84 | execute(C, []). 85 | 86 | -spec description(connection()) -> list() | nil. 87 | description(C) -> 88 | gen_server:call(C, description, infinity). 89 | 90 | -spec fetchone(connection()) 91 | -> {ok, list()} | {error, Reason :: binary()}. 92 | fetchone(C) -> 93 | case gen_server:call(C, fetchone, infinity) of 94 | {error, _} -> 95 | {ok, _ErrNo, Msg} = gen_server:call(C, get_last_error, infinity), 96 | {error, Msg}; 97 | R -> R 98 | end. 99 | 100 | -spec fetchall(connection()) 101 | -> {ok, list()} | {error, Reason :: binary()}. 102 | fetchall(C) -> 103 | case gen_server:call(C, fetchall, infinity) of 104 | {error, _} -> 105 | {ok, _ErrNo, Msg} = gen_server:call(C, get_last_error, infinity), 106 | {error, Msg}; 107 | R -> R 108 | end. 109 | 110 | -spec commit(connection()) 111 | -> ok | {error, Reason :: binary()}. 112 | commit(C) -> 113 | gen_server:call(C, commit, infinity). 114 | 115 | -spec rollback(connection()) 116 | -> ok | {error, Reason :: binary()}. 117 | rollback(C) -> 118 | gen_server:call(C, rollback, infinity). 119 | 120 | -spec close(efirebirdsql:connection()) 121 | -> ok | {error, Reason :: binary()}. 122 | close(C) -> 123 | gen_server:call(C, close, infinity), 124 | catch gen_server:cast(C, stop), 125 | ok. 126 | 127 | -spec get_last_error(connection()) -> {integer(), binary()}. 128 | get_last_error(C) -> 129 | {ok, ErrNo, Msg} = gen_server:call(C, get_last_error, infinity), 130 | {ErrNo, Msg}. 131 | 132 | cancel(C) -> 133 | gen_server:cast(C, cancel). 134 | 135 | sync(C) -> 136 | gen_server:call(C, sync). 137 | 138 | -------------------------------------------------------------------------------- /test/efirebirdsql_protocol_tests.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2019 Hajime Nakagami 3 | 4 | -module(efirebirdsql_protocol_tests). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | -include("efirebirdsql.hrl"). 8 | 9 | tmp_dbname() -> 10 | lists:flatten(io_lib:format("/tmp/~p.fdb", [erlang:system_time()])). 11 | 12 | protocol_test() -> 13 | {ok, Conn} = efirebirdsql_protocol:connect( 14 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD","masterkey"), tmp_dbname(), 15 | [{createdb, true}, {auth_plugin, "Srp"}]), 16 | ?assertEqual(Conn#conn.auto_commit, true), 17 | 18 | {ok, C} = efirebirdsql_protocol:begin_transaction(true, Conn), 19 | {ok, Stmt} = efirebirdsql_protocol:allocate_statement(C), 20 | 21 | {ok, Stmt2} = efirebirdsql_protocol:prepare_statement( 22 | <<"SELECT rdb$relation_name, rdb$owner_name FROM rdb$relations WHERE rdb$system_flag=?">>, C, Stmt), 23 | {ok, Stmt3} = efirebirdsql_protocol:execute(C, Stmt2, [1]), 24 | _Description = efirebirdsql_protocol:description(Stmt3), 25 | 26 | {ok, Stmt4} = efirebirdsql_protocol:prepare_statement( 27 | <<"SELECT RDB$RELATION_ID, RDB$EXTERNAL_FILE FROM rdb$relations WHERE rdb$system_flag=?">>, C, Stmt3), 28 | {ok, Stmt5} = efirebirdsql_protocol:execute(C, Stmt4, [1]), 29 | 30 | {ok, _Rows, Stmt6} = efirebirdsql_protocol:fetchall(C, Stmt5), 31 | ?assertEqual( 32 | efirebirdsql_protocol:columns(Stmt6), 33 | 34 | [{<<"RDB$RELATION_ID">>,short,0,2,true}, 35 | {<<"RDB$EXTERNAL_FILE">>,varying,0,255,true}] 36 | ), 37 | 38 | {ok, Stmt7} = efirebirdsql_protocol:prepare_statement(<<" 39 | CREATE TABLE foo ( 40 | a INTEGER NOT NULL, 41 | b VARCHAR(30) NOT NULL UNIQUE, 42 | c VARCHAR(1024), 43 | d DECIMAL(16,3) DEFAULT -0.123, 44 | e DATE DEFAULT '1967-08-11', 45 | f TIMESTAMP DEFAULT '1967-08-11 23:45:01', 46 | g TIME DEFAULT '23:45:01', 47 | h0 BLOB SUB_TYPE 0, 48 | h1 BLOB SUB_TYPE 1, 49 | i DOUBLE PRECISION DEFAULT 1.0, 50 | j FLOAT DEFAULT 2.0, 51 | PRIMARY KEY (a), 52 | CONSTRAINT CHECK_A CHECK (a <> 0) 53 | )">>, C, Stmt6), 54 | {ok, Stmt8} = efirebirdsql_protocol:execute(C, Stmt7, []), 55 | 56 | {ok, nil, Stmt9} = efirebirdsql_protocol:fetchall(C, Stmt8), 57 | 58 | {ok, Stmt10} = efirebirdsql_protocol:prepare_statement( 59 | <<"INSERT INTO foo(a, b) VALUES(?, ?)">>, C, Stmt9), 60 | {ok, Stmt11} = efirebirdsql_protocol:execute(C, Stmt10, [1, "b"]), 61 | ?assertEqual(Stmt11#stmt.rows, nil), 62 | {ok, 1} = efirebirdsql_protocol:rowcount(C, Stmt11), 63 | 64 | {ok, Stmt12} = efirebirdsql_protocol:prepare_statement( 65 | <<"SELECT * FROM foo WHERE f = ?">>, C, Stmt11), 66 | {ok, Stmt13} = efirebirdsql_protocol:execute(C, Stmt12, [{{1967, 8, 11}, {23, 45, 1, 0}}]), 67 | {ok, Rows, Stmt14} = efirebirdsql_protocol:fetchall(C, Stmt13), 68 | ?assertEqual(length(Rows), 1), 69 | {ok, 1} = efirebirdsql_protocol:rowcount(C, Stmt14), 70 | 71 | {ok, Stmt15} = efirebirdsql_protocol:prepare_statement( 72 | <<"UPDATE foo SET b=? WHERE a=?">>, C, Stmt14), 73 | {ok, Stmt16} = efirebirdsql_protocol:execute(C, Stmt15, ["c", 1]), 74 | {ok, 1} = efirebirdsql_protocol:rowcount(C, Stmt16), 75 | 76 | {ok, Stmt17} = efirebirdsql_protocol:unallocate_statement(<<"SELECT * FROM foo">>), 77 | {ok, Stmt18} = efirebirdsql_protocol:execute(C, Stmt17, []), 78 | ?assertEqual(Stmt18#stmt.closed, false), 79 | {ok, Rows2, Stmt19} = efirebirdsql_protocol:fetchall(C, Stmt18), 80 | ?assertEqual(Stmt19#stmt.closed, true), 81 | ?assertEqual(length(Rows2), 1), 82 | {ok, 1} = efirebirdsql_protocol:rowcount(C, Stmt19), 83 | 84 | {ok, Stmt20} = efirebirdsql_protocol:free_statement(C, Stmt19, drop), 85 | ?assertEqual(Stmt20#stmt.stmt_handle, nil), 86 | ?assertEqual(Stmt20#stmt.sql, <<"SELECT * FROM foo">>), 87 | {ok, Stmt21} = efirebirdsql_protocol:execute(C, Stmt20, []), 88 | ?assertEqual(Stmt21#stmt.closed, false), 89 | {ok, Rows3, Stmt22} = efirebirdsql_protocol:fetchall(C, Stmt21), 90 | ?assertEqual(Stmt22#stmt.closed, true), 91 | ?assertEqual(Rows2, Rows3), 92 | {ok, 1} = efirebirdsql_protocol:rowcount(C, Stmt22), 93 | 94 | ok = efirebirdsql_protocol:rollback_retaining(C), 95 | {ok, _} = efirebirdsql_protocol:close(C). 96 | 97 | 98 | connect_test(_DbName, 0) -> 99 | ok; 100 | connect_test(DbName, Count) -> 101 | {ok, C} = efirebirdsql_protocol:connect( 102 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, 103 | [{auth_plugin, "Srp"}]), 104 | efirebirdsql_protocol:close(C), 105 | connect_test(DbName, Count-1). 106 | connect_test() -> 107 | DbName = tmp_dbname(), 108 | {ok, C} = efirebirdsql_protocol:connect( 109 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, 110 | [{createdb, true}, {auth_plugin, "Srp"}]), 111 | efirebirdsql_protocol:close(C), 112 | connect_test(DbName, 10). 113 | 114 | connect_error_test() -> 115 | {error, ErrNo, Reason, _Conn} = efirebirdsql_protocol:connect("localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), "something_wrong_database", []), 116 | ?assertEqual(ErrNo, 335544734), 117 | ?assertNotEqual(Reason, nil). 118 | -------------------------------------------------------------------------------- /src/efirebirdsql_server.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | 3 | %%% Copyright (c) 2016-2021 Hajime Nakagami 4 | 5 | -module(efirebirdsql_server). 6 | 7 | -behavior(gen_server). 8 | 9 | -export([start_link/0, get_parameter/2]). 10 | -export([handle_call/3, handle_cast/2, handle_info/2]). 11 | -export([init/1, code_change/3, terminate/2]). 12 | 13 | -include("efirebirdsql.hrl"). 14 | 15 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 16 | %%% -- client interface -- 17 | -spec start_link() -> {ok, pid()}. 18 | start_link() -> 19 | gen_server:start_link(?MODULE, [], []). 20 | 21 | get_parameter(C, Name) when is_list(Name) -> 22 | gen_server:call(C, {get_parameter, list_to_binary(Name)}, infinity); 23 | get_parameter(C, Name) when is_list(Name) -> 24 | gen_server:call(C, {get_parameter, Name}, infinity). 25 | 26 | 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 | %%% -- gen_server implementation -- 29 | 30 | init([]) -> 31 | {ok, #state{}}. 32 | 33 | -spec allocate_statement(conn(), state()) -> {ok, state()} | {error, state()}. 34 | allocate_statement(Conn, State) -> 35 | case efirebirdsql_protocol:allocate_statement(Conn) of 36 | {ok, Stmt} -> {ok, State#state{connection=Conn, statement=Stmt}}; 37 | {error, ErrNo, Reason} -> {error, State#state{connection=Conn, error_no=ErrNo, error_message=Reason}} 38 | end. 39 | 40 | handle_call({connect, Host, Username, Password, Database, Options}, _From, State) -> 41 | AutoCommit = proplists:get_value(auto_commit, Options, true), 42 | case efirebirdsql_protocol:connect(Host, Username, Password, Database, Options) of 43 | {ok, Conn} -> 44 | case efirebirdsql_protocol:begin_transaction(AutoCommit, Conn) of 45 | {ok, C2} -> 46 | case allocate_statement(C2, State) of 47 | {ok, NewState} -> {reply, ok, NewState}; 48 | {error, NewState} -> {reply, {error, allocate_statement}, NewState} 49 | end; 50 | {error, ErrNo, Reason, C2} -> 51 | {reply, {error, begin_transaction}, State#state{connection=C2, error_no=ErrNo, error_message=Reason}} 52 | end; 53 | {error, ErrNo, Reason, Conn} -> 54 | {reply, {error, connect}, State#state{connection=Conn, error_no=ErrNo, error_message=Reason}} 55 | end; 56 | handle_call(ping, _From, State) -> 57 | Conn = State#state.connection, 58 | {reply, efirebirdsql_protocol:ping(Conn), State}; 59 | handle_call({transaction, Options}, _From, State) -> 60 | Conn = State#state.connection, 61 | AutoCommit = proplists:get_value(auto_commit, Options, true), 62 | case efirebirdsql_protocol:begin_transaction(AutoCommit, Conn) of 63 | {ok, C2} -> {reply, ok, State#state{connection=C2}}; 64 | {error, ErrNo, Reason, C2} -> {reply, {error, begin_transaction}, State#state{connection=C2, error_no=ErrNo, error_message=Reason}} 65 | end; 66 | handle_call(commit, _From, State) -> 67 | Conn = State#state.connection, 68 | case efirebirdsql_protocol:commit_retaining(Conn) of 69 | ok -> {reply, ok, State}; 70 | {error, ErrNo, Reason} -> {reply, {error, commit}, State#state{error_no=ErrNo, error_message=Reason}} 71 | end; 72 | handle_call(rollback, _From, State) -> 73 | Conn = State#state.connection, 74 | case efirebirdsql_protocol:rollback_retaining(Conn) of 75 | ok -> {reply, ok, State}; 76 | {error, ErrNo, Reason} -> {reply, {error, rollback}, State#state{error_no=ErrNo, error_message=Reason}} 77 | end; 78 | handle_call(close, _From, State) -> 79 | Conn = State#state.connection, 80 | case efirebirdsql_protocol:rollback(Conn) of % clear pending transaction 81 | ok -> 82 | case efirebirdsql_protocol:close(Conn) of 83 | {ok, C2} -> {reply, ok, State#state{connection=C2}}; 84 | {error, ErrNo, Reason, C2} -> {reply, {error, close}, State#state{connection=C2, error_no=ErrNo, error_message=Reason}} 85 | end; 86 | {error, ErrNo, Reason} -> 87 | {reply, {error, rollback}, State#state{error_no=ErrNo, error_message=Reason}} 88 | end; 89 | handle_call({prepare, Sql}, _From, State) -> 90 | case efirebirdsql_protocol:prepare_statement(Sql, State#state.connection, State#state.statement) of 91 | {ok, Stmt} -> {reply, ok, State#state{connection=State#state.connection, statement=Stmt}}; 92 | {error, ErrNo, Reason} -> {reply, {error, prepare}, State#state{connection=State#state.connection, error_no=ErrNo, error_message=Reason}} 93 | end; 94 | handle_call({execute, Params}, _From, State) -> 95 | case efirebirdsql_protocol:execute(State#state.connection, State#state.statement, Params) of 96 | {ok, Stmt} -> {reply, ok, State#state{statement=Stmt}}; 97 | {error, ErrNo, Reason} -> {reply, {error, execute}, State#state{error_no=ErrNo, error_message=Reason}} 98 | end; 99 | handle_call(fetchone, _From, State) -> 100 | {ConvertedRow, Stmt} = efirebirdsql_protocol:fetchone(State#state.connection, State#state.statement), 101 | {reply, {ok, ConvertedRow}, State#state{statement=Stmt}}; 102 | handle_call(fetchall, _From, State) -> 103 | {ok, Rows, S2} = efirebirdsql_protocol:fetchall( 104 | State#state.connection, State#state.statement), 105 | {reply, {ok, Rows}, State#state{connection=State#state.connection, statement=S2}}; 106 | handle_call(description, _From, State) -> 107 | case State#state.statement#stmt.stmt_type of 108 | isc_info_sql_stmt_select 109 | -> {reply, efirebirdsql_protocol:description(State#state.statement), State}; 110 | _ 111 | -> {reply, nil, State} 112 | end; 113 | handle_call({get_parameter, Name}, _From, State) -> 114 | Value1 = case lists:keysearch(Name, 1, State#state.parameters) of 115 | {value, {Name, Value}} -> Value; 116 | false -> undefined 117 | end, 118 | {reply, {ok, Value1}, State}; 119 | handle_call(get_last_error, _From, State) -> 120 | {reply, {ok, State#state.error_no, State#state.error_message}, State}. 121 | 122 | handle_cast(_Msg, State) -> 123 | {noreply, State}. 124 | 125 | handle_info({inet_reply, _, ok}, State) -> 126 | {noreply, State}. 127 | 128 | terminate(_Reason, _State) -> 129 | ok. 130 | 131 | code_change(_OldVsn, State, _Extra) -> 132 | {ok, State}. 133 | -------------------------------------------------------------------------------- /src/efirebirdsql_srp.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2016-2021 Hajime Nakagami 3 | 4 | -module(efirebirdsql_srp). 5 | 6 | -export([get_debug_private_key/0, get_debug_salt/0, 7 | get_verifier/3, client_proof/7, client_session/6, server_session/6, get_salt/0, 8 | get_private_key/0, client_public/1, server_public/2, to_hex/1]). 9 | 10 | 11 | -spec get_debug_private_key() -> integer(). 12 | get_debug_private_key() -> 13 | 16#60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393. 14 | 15 | -spec get_debug_salt() -> binary(). 16 | get_debug_salt() -> 17 | int_to_bin(16#02E268803000000079A478A700000002D1A6979000000026E1601C000000054F, 32). 18 | 19 | -spec int_to_bin(integer()) -> binary(). 20 | int_to_bin(Int) -> 21 | Len0 = length(erlang:integer_to_list(Int, 16)), 22 | Len1 = Len0 + (Len0 rem 2), 23 | Bits = Len1 * 4, 24 | <>. 25 | -spec int_to_bin(integer(), integer()) -> binary(). 26 | int_to_bin(Int, BytesLen) -> 27 | Bits = BytesLen * 8, 28 | <>. 29 | 30 | -spec trim_list(list()) -> list(). 31 | trim_list(List) when is_list(List) -> 32 | [R | Rest] = List, 33 | if R =:= 0 -> trim_list(Rest); R =/= 0 -> List end. 34 | -spec pad(integer(), integer()) -> binary(). 35 | pad(Int, MaxBytesLen) -> 36 | List = trim_list(binary_to_list(int_to_bin(Int, MaxBytesLen))), 37 | list_to_binary(List). 38 | 39 | -spec bin_to_int(binary()) -> integer(). 40 | bin_to_int(Bin) -> 41 | Bits = byte_size(Bin) * 8, 42 | <> = Bin, 43 | Val. 44 | 45 | %% N: A large prime 46 | get_prime() -> 47 | 16#E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7. 48 | 49 | %% g: A generator modulo N 50 | get_generator() -> 2. 51 | 52 | %% get key size (bits) 53 | get_key_size() -> 128. 54 | 55 | %% k = H(N, g): Multiplier parameter 56 | -spec get_k() -> integer(). 57 | get_k() -> 58 | 1277432915985975349439481660349303019122249719989. 59 | 60 | -spec remainder(integer(), integer()) -> integer(). 61 | remainder(A, B) -> 62 | Rem = A rem B, 63 | if Rem < 0 -> Rem + B; Rem >= 0 -> Rem end. 64 | 65 | %% x = H(salt, H(username, :, password)) 66 | -spec get_user_hash(binary(), binary(), binary()) -> binary(). 67 | get_user_hash(User, Pass, Salt) -> 68 | crypto:hash(sha, [Salt, crypto:hash(sha, [User, <<$:>>, Pass])]). 69 | 70 | %% v = g^x 71 | -spec get_verifier(list(), list(), binary()) -> integer(). 72 | get_verifier(Username, Password, Salt) -> 73 | User = list_to_binary(Username), 74 | Pass = list_to_binary(Password), 75 | DerivedKey = get_user_hash(User, Pass, Salt), 76 | V = crypto:mod_pow(get_generator(), bin_to_int(DerivedKey), get_prime()), 77 | bin_to_int(V). 78 | 79 | %% salt 32 bytes binary 80 | -spec get_salt() -> binary(). 81 | get_salt() -> 82 | Bytes = 32, 83 | Bits = Bytes * 8, 84 | <> = crypto:strong_rand_bytes(Bytes), 85 | Result. 86 | 87 | %% get private key 88 | -spec get_private_key() -> integer(). 89 | get_private_key() -> 90 | Bits = get_key_size(), 91 | <> = crypto:strong_rand_bytes(get_key_size() div 8), 92 | bin_to_int(PrivateKeyBin). 93 | 94 | %% client public key 95 | -spec client_public(integer()) -> integer(). 96 | client_public(PrivateKey) -> 97 | bin_to_int(crypto:mod_pow(get_generator(), PrivateKey, get_prime())). 98 | 99 | %% Server public key 100 | -spec server_public(integer(), integer()) -> integer(). 101 | server_public(V, PrivateKey) -> 102 | GB = bin_to_int(crypto:mod_pow(get_generator(), PrivateKey, get_prime())), 103 | KV = remainder(get_k() * V, get_prime()), 104 | remainder(KV + GB, get_prime()). 105 | 106 | %% client session key 107 | -spec client_session(list(), list(), binary(), integer(), integer(), integer()) -> binary(). 108 | client_session(Username, Password, Salt, ClientPublic, ServerPublic, ClientPrivate) -> 109 | User = list_to_binary(Username), 110 | Pass = list_to_binary(Password), 111 | U = bin_to_int(crypto:hash(sha, [pad(ClientPublic, get_key_size()), pad(ServerPublic, get_key_size())])), 112 | X = get_user_hash(User, Pass, Salt), 113 | GX = bin_to_int(crypto:mod_pow(get_generator(), X, get_prime())), 114 | KGX = remainder(get_k() * GX, get_prime()), 115 | Diff = remainder(ServerPublic - KGX, get_prime()), 116 | UX = (U * bin_to_int(X)) rem get_prime(), 117 | AUX = (ClientPrivate + UX) rem get_prime(), 118 | SessionSecret = crypto:mod_pow(Diff, AUX, get_prime()), 119 | 120 | crypto:hash(sha, SessionSecret). 121 | 122 | %% server session key 123 | -spec server_session(list(), list(), binary(), integer(), integer(), integer()) -> binary(). 124 | server_session(Username, Password, Salt, ClientPublic, ServerPublic, ServerPrivate) -> 125 | U = bin_to_int(crypto:hash(sha, [int_to_bin(ClientPublic, get_key_size()), int_to_bin(ServerPublic, get_key_size())])), 126 | V = get_verifier(Username, Password, Salt), 127 | VU = bin_to_int(crypto:mod_pow(V, U, get_prime())), 128 | AVU = remainder(ClientPublic * VU, get_prime()), 129 | SessionSecret = crypto:mod_pow(AVU, ServerPrivate, get_prime()), 130 | crypto:hash(sha, SessionSecret). 131 | 132 | -spec client_proof(list(), list(), binary(), integer(), integer(), integer(), atom()) -> {binary(), binary()}. 133 | client_proof(Username, Password, Salt, ClientPublic, ServerPublic, ClientPrivate, Algo) 134 | -> 135 | User = list_to_binary(Username), 136 | K = client_session(Username, Password, Salt, ClientPublic, ServerPublic, ClientPrivate), 137 | 138 | N1 = bin_to_int(crypto:hash(sha, int_to_bin(get_prime()))), 139 | N2 = bin_to_int(crypto:hash(sha, int_to_bin(get_generator()))), 140 | 141 | M = crypto:hash(Algo, [ 142 | crypto:mod_pow(N1, N2, get_prime()), 143 | crypto:hash(sha, User), 144 | Salt, 145 | pad(ClientPublic, get_key_size()), 146 | pad(ServerPublic, get_key_size()), 147 | K 148 | ]), 149 | {M, K}. 150 | 151 | %% convert to hex string 152 | -spec to_hex(binary() | list() | integer()) -> list(). 153 | to_hex(Bin) when is_binary(Bin) -> 154 | lists:flatten([io_lib:format("~2.16.0B",[X]) || <> <= Bin ]); 155 | to_hex(L) when is_list(L) -> 156 | to_hex(list_to_binary(L)); 157 | to_hex(I) when is_integer(I) -> 158 | to_hex(int_to_bin(I)). 159 | -------------------------------------------------------------------------------- /src/efirebirdsql_decfloat.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2018-2021 Hajime Nakagami 3 | 4 | -module(efirebirdsql_decfloat). 5 | 6 | -export([decimal_fixed_to_decimal/2, decimal64_to_decimal/1, decimal128_to_decimal/1]). 7 | 8 | -spec dpd_to_int(integer()) -> integer(). 9 | dpd_to_int(Dpd) -> 10 | B0 = if Dpd band 2#0000000001 =/= 0 -> 1; true -> 0 end, 11 | B1 = if Dpd band 2#0000000010 =/= 0 -> 1; true -> 0 end, 12 | B2 = if Dpd band 2#0000000100 =/= 0 -> 1; true -> 0 end, 13 | B3 = if Dpd band 2#0000001000 =/= 0 -> 1; true -> 0 end, 14 | B4 = if Dpd band 2#0000010000 =/= 0 -> 1; true -> 0 end, 15 | B5 = if Dpd band 2#0000100000 =/= 0 -> 1; true -> 0 end, 16 | B6 = if Dpd band 2#0001000000 =/= 0 -> 1; true -> 0 end, 17 | B7 = if Dpd band 2#0010000000 =/= 0 -> 1; true -> 0 end, 18 | B8 = if Dpd band 2#0100000000 =/= 0 -> 1; true -> 0 end, 19 | B9 = if Dpd band 2#1000000000 =/= 0 -> 1; true -> 0 end, 20 | D = if 21 | B3 =:= 0 -> [ 22 | B2 * 4 + B1 * 2 + B0, 23 | B6 * 4 + B5 * 2 + B4, 24 | B9 * 4 + B8 * 2 + B7 25 | ]; 26 | {B3, B2, B1} =:= {1, 0, 0} -> [ 27 | 8 + B0, 28 | B6 * 4 + B5 * 2 + B4, 29 | B9 * 4 + B8 * 2 + B7 30 | ]; 31 | {B3, B2, B1} =:= {1, 0, 1} -> [ 32 | B6 * 4 + B5 * 2 + B0, 33 | 8 + B4, 34 | B9 * 4 + B8 * 2 + B7 35 | ]; 36 | {B3, B2, B1} =:= {1, 1, 0} -> [ 37 | B9 * 4 + B8 * 2 + B0, 38 | B6 * 4 + B5 * 2 + B4, 39 | 8 + B7 40 | ]; 41 | {B6, B5, B3, B2, B1} =:= {0, 0, 1, 1, 1} -> [ 42 | B9 * 4 + B8 * 2 + B0, 43 | 8 + B4, 44 | 8 + B7 45 | ]; 46 | {B6, B5, B3, B2, B1} =:= {0, 1, 1, 1, 1} -> [ 47 | 8 + B0, 48 | B9 * 4 + B8 * 2 + B4, 49 | 8 + B7 50 | ]; 51 | {B6, B5, B3, B2, B1} =:= {1, 0, 1, 1, 1} -> [ 52 | 8 + B0, 53 | 8 + B4, 54 | B9 * 4 + B8 * 2 + B7 55 | ]; 56 | {B6, B5, B3, B2, B1} =:= {1, 1, 1, 1, 1} -> [ 57 | 8 + B0, 58 | 8 + B4, 59 | 8 + B7 60 | ] 61 | end, 62 | lists:nth(3, D) * 100 + lists:nth(2, D) * 10 + lists:nth(1, D). 63 | 64 | -spec calc_significand_segments(integer(), list()) -> integer(). 65 | calc_significand_segments(Prefix, []) -> Prefix; 66 | calc_significand_segments(Prefix, Segments) -> 67 | [Dpd | Tail] = Segments, 68 | calc_significand_segments(Prefix * 1000 + dpd_to_int(Dpd), Tail). 69 | 70 | -spec segments_bits(integer(), integer(), list()) -> list(). 71 | segments_bits(_, 0, Segments) -> Segments; 72 | segments_bits(DpdBits, NumSegments, Segments) -> 73 | NextSegment = DpdBits band 2#1111111111, 74 | segments_bits(DpdBits bsr 10, NumSegments - 1, [NextSegment | Segments]). 75 | 76 | -spec calc_significand(non_neg_integer(), non_neg_integer(), integer()) -> non_neg_integer(). 77 | calc_significand(Prefix, DpdBits, NumBits) -> 78 | %% https://en.wikipedia.org/wiki/Decimal128_floating-point_format#Densely_packed_decimal_significand_field 79 | NumSegments = NumBits div 10, 80 | Segments = segments_bits(DpdBits, NumSegments, []), 81 | calc_significand_segments(Prefix, Segments). 82 | 83 | -spec decimal128_to_sign_digits_exponent(binary()) -> {integer(), integer(), integer()} | list(). 84 | decimal128_to_sign_digits_exponent(Bin) -> 85 | %% https://en.wikipedia.org/wiki/Decimal128_floating-point_format 86 | <> = Bin, 87 | if 88 | Combination band 2#11111000000000000 =:= 2#11111000000000000, Sign =:= 0 -> "NaN"; 89 | Combination band 2#11111000000000000 =:= 2#11111000000000000, Sign =:= 1 -> "-NaN"; 90 | Combination band 2#11111000000000000 =:= 2#11110000000000000, Sign =:= 0 -> "-Infinity"; 91 | Combination band 2#11111000000000000 =:= 2#11110000000000000, Sign =:= 1 -> "Infinity"; 92 | true -> 93 | Exponent = if 94 | Combination band 2#11000000000000000 =:= 2#00000000000000000 -> 95 | 2#00000000000000 + Combination band 2#111111111111; 96 | Combination band 2#11000000000000000 =:= 2#01000000000000000 -> 97 | 2#01000000000000 + Combination band 2#111111111111; 98 | Combination band 2#11000000000000000 =:= 2#10000000000000000 -> 99 | 2#10000000000000 + Combination band 2#111111111111; 100 | Combination band 2#11110000000000000 =:= 2#11000000000000000 -> 101 | 2#00000000000000 + Combination band 2#111111111111; 102 | Combination band 2#11110000000000000 =:= 2#11010000000000000 -> 103 | 2#01000000000000 + Combination band 2#111111111111; 104 | Combination band 2#11110000000000000 =:= 2#11100000000000000 -> 105 | 2#10000000000000 + Combination band 2#111111111111 106 | end - 6176, 107 | Prefix = if 108 | Combination band 2#11000000000000000 =:= 2#00000000000000000 -> 109 | (Combination bsr 12) band 2#111; 110 | Combination band 2#11000000000000000 =:= 2#01000000000000000 -> 111 | (Combination bsr 12) band 2#111; 112 | Combination band 2#11000000000000000 =:= 2#10000000000000000 -> 113 | (Combination bsr 12) band 2#111; 114 | Combination band 2#11110000000000000 =:= 2#11000000000000000 -> 115 | 8 + (Combination bsr 12) band 2#1; 116 | Combination band 2#11110000000000000 =:= 2#11010000000000000 -> 117 | 8 + (Combination bsr 12) band 2#1; 118 | Combination band 2#11110000000000000 =:= 2#11100000000000000 -> 119 | 8 + (Combination bsr 12) band 2#1 120 | end, 121 | Digits = calc_significand(Prefix, DpdBits, 110), 122 | {Sign, Digits, Exponent} 123 | end. 124 | 125 | %% ------------------------------------------------------------------------------------ 126 | 127 | -spec decimal_fixed_to_decimal(binary(), integer()) -> list(). 128 | decimal_fixed_to_decimal(Bin, Scale) -> 129 | case decimal128_to_sign_digits_exponent(Bin) of 130 | {Sign, V, _} -> efirebirdsql_conv:parse_number(Sign, V, Scale); 131 | V -> V 132 | end. 133 | 134 | 135 | -spec decimal64_to_decimal(binary()) -> list(). 136 | decimal64_to_decimal(Bin) -> 137 | %% https://en.wikipedia.org/wiki/Decimal64_floating-point_format 138 | <> = Bin, 139 | if 140 | Combination =:= 2#11111, Sign =:= 0 -> "NaN"; 141 | Combination =:= 2#11111, Sign =:= 1 -> "-NaN"; 142 | Combination =:= 2#11110, Sign =:= 0 -> "Infinity"; 143 | Combination =:= 2#11110, Sign =:= 1 -> "-Infinity"; 144 | true -> 145 | Exponent = if 146 | Combination band 2#11000 =:= 2#00000 -> 147 | 2#0000000000 + BaseExponent; 148 | Combination band 2#11000 =:= 2#01000 -> 149 | 2#0100000000 + BaseExponent; 150 | Combination band 2#11000 =:= 2#10000 -> 151 | 2#1000000000 + BaseExponent; 152 | Combination band 2#11000 =:= 2#11000 -> 153 | 2#0000000000 + BaseExponent; 154 | Combination band 2#11000 =:= 2#11010 -> 155 | 2#0100000000 + BaseExponent; 156 | Combination band 2#11000 =:= 2#11100 -> 157 | 2#1000000000 + BaseExponent 158 | end - 398, 159 | Prefix = if 160 | Combination band 2#11000 =:= 2#00000 -> 161 | Combination band 2#111; 162 | Combination band 2#11000 =:= 2#01000 -> 163 | Combination band 2#111; 164 | Combination band 2#11000 =:= 2#10000 -> 165 | Combination band 2#111; 166 | Combination band 2#11000 =:= 2#11000 -> 167 | 8 + (Combination band 2#1); 168 | Combination band 2#11000 =:= 2#11100 -> 169 | 8 + (Combination band 2#1); 170 | Combination band 2#11000 =:= 2#11100 -> 171 | 8 + (Combination band 2#1) 172 | end, 173 | Digits = calc_significand(Prefix, DpdBits, 50), 174 | efirebirdsql_conv:parse_number(Sign, Digits, Exponent) 175 | end. 176 | 177 | -spec decimal128_to_decimal(binary()) -> list(). 178 | decimal128_to_decimal(Bin) -> 179 | case decimal128_to_sign_digits_exponent(Bin) of 180 | {Sign, V, Scale} -> efirebirdsql_conv:parse_number(Sign, V, Scale); 181 | V -> V 182 | end. 183 | -------------------------------------------------------------------------------- /src/efirebirdsql_conv.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2016-2021 Hajime Nakagami 3 | 4 | -module(efirebirdsql_conv). 5 | 6 | -export([byte2/1, byte4/2, byte4/1, pad4/1, list_to_xdr_string/1, 7 | list_to_xdr_bytes/1, parse_date/1, 8 | parse_time/1, parse_timestamp/1, parse_time_tz/1, parse_timestamp_tz/1, 9 | parse_number/2, parse_number/3, params_to_blr/3]). 10 | 11 | %%% little endian 2byte 12 | -spec byte2(integer()) -> list(). 13 | byte2(N) -> 14 | LB = binary:encode_unsigned(N, little), 15 | LB2 = case size(LB) of 16 | 1 -> << LB/binary, <<0>>/binary >>; 17 | 2 -> LB 18 | end, 19 | binary_to_list(LB2). 20 | 21 | %%% big endian number list fill 4 byte alignment 22 | -spec byte4(integer(), atom()) -> list(). 23 | byte4(N, big) -> 24 | LB4 = <>, 25 | binary_to_list(LB4); 26 | byte4(N, little) -> 27 | LB4 = <>, 28 | binary_to_list(LB4). 29 | 30 | byte4(N) -> 31 | byte4(N, big). 32 | 33 | %%% big endian number list fill 8 byte alignment 34 | -spec byte8(integer(), atom()) -> list(). 35 | byte8(N, big) -> 36 | LB8 = <>, 37 | binary_to_list(LB8). 38 | 39 | byte8(N) -> 40 | byte8(N, big). 41 | 42 | %%% 4 byte padding 43 | -spec pad4(list()) -> list(). 44 | pad4(L) -> 45 | case length(lists:flatten(L)) rem 4 of 46 | 0 -> []; 47 | 1 -> [0, 0, 0]; 48 | 2 -> [0, 0]; 49 | 3 -> [0] 50 | end. 51 | 52 | -spec list_to_xdr_string(list()) -> list(). 53 | list_to_xdr_string(L) -> 54 | lists:flatten([byte4(length(L)), L, pad4(L)]). 55 | 56 | list_to_xdr_bytes(L) -> 57 | list_to_xdr_string(L). 58 | 59 | parse_date(RawValue) -> 60 | L = size(RawValue) * 8, 61 | <> = RawValue, 62 | NDay1 = Num + 678882, 63 | Century = (4 * NDay1 -1) div 146097, 64 | NDay2 = 4 * NDay1 - 1 - 146097 * Century, 65 | Day1 = NDay2 div 4, 66 | 67 | NDay3 = (4 * Day1 + 3) div 1461, 68 | Day2 = 4 * Day1 + 3 - 1461 * NDay3, 69 | Day3 = (Day2 + 4) div 4, 70 | 71 | Month1 = (5 * Day3 - 3) div 153, 72 | Day4 = 5 * Day3 - 3 - 153 * Month1, 73 | Day5 = (Day4 + 5) div 5, 74 | Year1 = 100 * Century + NDay3, 75 | Month2 = if Month1 < 10 -> Month1 + 3; true -> Month1 - 9 end, 76 | Year2 = if Month1 < 10 -> Year1; true -> Year1 + 1 end, 77 | {Year2, Month2, Day5}. 78 | 79 | parse_time(RawValue) -> 80 | L = size(RawValue) * 8, 81 | <> = RawValue, 82 | S = N div 10000, 83 | M = S div 60, 84 | H = M div 60, 85 | {H, M rem 60, S rem 60, (N rem 10000) * 100}. 86 | 87 | parse_timestamp(RawValue) -> 88 | <> = RawValue, 89 | {parse_date(YMD), parse_time(HMSN)}. 90 | 91 | -spec parse_time_tz(binary()) -> {{integer(), integer(), integer(), integer()}, binary(), binary()}. 92 | parse_time_tz(RawValue) -> 93 | <> = RawValue, 94 | case TimeZoneID of 95 | 0 -> 96 | TimeZone = "UTC", 97 | Offset = "UTC"; 98 | _ -> 99 | TimeZone = maps:get(TimeZoneID, efirebirdsql_tz_map:timezone_name_by_id()), 100 | Offset = maps:get(OffsetID, efirebirdsql_tz_map:timezone_name_by_id()) 101 | end, 102 | {parse_time(HMSN), TimeZone, Offset}. 103 | 104 | -spec parse_timestamp_tz(binary()) -> {{integer(), integer(), integer()}, {integer(), integer(), integer(), integer()}, binary(), binary()}. 105 | parse_timestamp_tz(RawValue) -> 106 | <> = RawValue, 107 | case TimeZoneID of 108 | 0 -> 109 | TimeZone = "UTC", 110 | Offset = "UTC"; 111 | _ -> 112 | TimeZone = maps:get(TimeZoneID, efirebirdsql_tz_map:timezone_name_by_id()), 113 | Offset = maps:get(OffsetID, efirebirdsql_tz_map:timezone_name_by_id()) 114 | end, 115 | {parse_date(YMD), parse_time(HMSN), TimeZone, Offset}. 116 | 117 | fill0(S, 0) -> S; 118 | fill0(S, N) -> fill0([48 | S], N-1). 119 | to_decimal(N, Scale) when N < 0 -> 120 | lists:flatten(["-", to_decimal(-N, Scale)]); 121 | to_decimal(N, Scale) when N >= 0 -> 122 | Shift = if Scale < 0 -> -Scale; Scale >= 0 -> 0 end, 123 | V = if Scale =< 0 -> N; Scale > 0 -> N * trunc(math:pow(10, Scale)) end, 124 | S = integer_to_list(V), 125 | S2 = if length(S) =< Shift -> 126 | fill0(S, Shift - length(S) + 1); 127 | length(S) > Shift -> S 128 | end, 129 | {I, F} = lists:split(length(S2) - Shift, S2), 130 | lists:flatten([I, ".", F]). 131 | 132 | pow10(N) -> pow10(10, N). 133 | pow10(V, 0) -> V; 134 | pow10(V, N) -> pow10(V * 10, N-1). 135 | 136 | parse_number(RawValue, Scale) when Scale =:= 0 -> 137 | L = size(RawValue) * 8, 138 | <> = RawValue, 139 | V; 140 | parse_number(RawValue, Scale) when Scale > 0 -> 141 | L = size(RawValue) * 8, 142 | <> = RawValue, 143 | integer_to_list(V * pow10(Scale)); 144 | parse_number(RawValue, Scale) when Scale < 0 -> 145 | L = size(RawValue) * 8, 146 | <> = RawValue, 147 | to_decimal(V, Scale). 148 | parse_number(Sign, V, Scale) -> 149 | case Sign of 150 | 0 -> to_decimal(V, Scale); 151 | 1 -> to_decimal(-V, Scale) 152 | end. 153 | 154 | %% Convert execute() parameters to BLR and values. 155 | param_to_date(Year, Month, Day) -> 156 | I = Month + 9, 157 | JY = Year + I div 12 - 1, 158 | JM = I rem 12, 159 | C = JY div 100, 160 | JY2 = JY - 100 * C, 161 | J = (146097 * C) div 4 + (1461 * JY2) div 4 + (153 * JM + 2) div 5 + Day - 678882, 162 | byte4(J). 163 | 164 | param_to_time(Hour, Minute, Second, Microsecond) -> 165 | byte4((Hour*3600 + Minute*60 + Second) * 10000 + Microsecond div 100). 166 | 167 | param_to_blr(V, _) when is_integer(V) and (V =< 16#7FFFFFFF) and (V >= -16#80000000) -> 168 | {[8, 0, 7, 0], byte4(V)}; 169 | param_to_blr(V, _) when is_integer(V) -> 170 | {[16, 0, 7, 0], byte8(V)}; 171 | param_to_blr(V, _) when is_binary(V) -> 172 | B = binary_to_list(V), 173 | {lists:flatten([14, byte2(length(B)), 7, 0]), 174 | lists:flatten([B, pad4(B)])}; 175 | param_to_blr(V, TimeZoneIdByName) when is_list(V) -> 176 | %% decimal 177 | param_to_blr(list_to_binary(V), TimeZoneIdByName); 178 | param_to_blr(V, TimeZoneIdByName) when is_float(V) -> 179 | %% float 180 | param_to_blr(float_to_binary(V), TimeZoneIdByName); 181 | param_to_blr({{Hour, Minute, Second, Microsecond}, TimeZone}, TimeZoneIdByName) -> 182 | %% time with timezone 183 | TimeZoneId = maps:get(TimeZone, TimeZoneIdByName), 184 | {[28, 7, 0], lists:flatten( 185 | [param_to_time(Hour, Minute, Second, Microsecond), byte4(TimeZoneId, big)] 186 | )}; 187 | param_to_blr({{Year, Month, Day}, {Hour, Minute, Second, Microsecond}, TimeZone}, TimeZoneIdByName) -> 188 | %% timestamp with timezone 189 | TimeZoneId = maps:get(TimeZone, TimeZoneIdByName), 190 | {[29, 7, 0], lists:flatten([param_to_date(Year, Month, Day), 191 | param_to_time(Hour, Minute, Second, Microsecond), byte4(TimeZoneId, big)])}; 192 | param_to_blr({Year, Month, Day}, _) -> 193 | %% date 194 | {[12, 7, 0], lists:flatten(param_to_date(Year, Month, Day))}; 195 | param_to_blr({Hour, Minute, Second, Microsecond}, _) -> 196 | %% time 197 | {[13, 7, 0], lists:flatten(param_to_time(Hour, Minute, Second, Microsecond))}; 198 | param_to_blr({{Year, Month, Day}, {Hour, Minute, Second, Microsecond}}, _) -> 199 | %% timestamp 200 | {[35, 7, 0], lists:flatten([param_to_date(Year, Month, Day), 201 | param_to_time(Hour, Minute, Second, Microsecond)])}; 202 | param_to_blr(true, _) -> 203 | {[23, 7, 0], [1, 0, 0, 0]}; 204 | param_to_blr(false, _) -> 205 | {[23, 7, 0], [0, 0, 0, 0]}. 206 | 207 | null_indicator_bits([], _Idx, Indicator) -> 208 | Indicator; 209 | null_indicator_bits(Params, Idx, Indicator) -> 210 | [X | RestParams] = Params, 211 | V = if X =:= nil -> (1 bsl Idx); X =/= nil -> 0 end, 212 | null_indicator_bits(RestParams, Idx + 1, Indicator + V). 213 | 214 | null_indicator_bits(Params) -> 215 | null_indicator_bits(Params, 0, 0). 216 | 217 | null_bitmap(Params) -> 218 | Bitmap = null_indicator_bits(Params), 219 | Len = if 220 | length(Params) rem 8 =:= 0 -> 221 | length(Params) div 8; 222 | length(Params) rem 8 =/= 0 -> 223 | length(Params) div 8 + 1 224 | end, 225 | L = binary_to_list(<>), 226 | L ++ pad4(L). 227 | 228 | params_to_blr(_AcceptVersion, _TimeZoneIdByName, [], Blr, Value) -> 229 | {lists:reverse(Blr), lists:reverse(Value)}; 230 | params_to_blr(AcceptVersion, TimeZoneIdByName, Params, Blr, Value) -> 231 | [V | RestParams] = Params, 232 | 233 | {NewBlr, NewValue} = if 234 | AcceptVersion >= 13 -> 235 | if 236 | V =:= nil -> {[14, 0, 0, 7, 0], []}; 237 | V =/= nil -> param_to_blr(V, TimeZoneIdByName) 238 | end; 239 | AcceptVersion < 13 -> 240 | if 241 | V =:= nil -> 242 | {[14, 0, 0, 7, 0], [255, 255, 255, 255]}; 243 | V =/= nil -> 244 | {B, V2} = param_to_blr(V, TimeZoneIdByName), 245 | {B, V2 ++ [0, 0, 0, 0]} 246 | end 247 | end, 248 | 249 | params_to_blr(AcceptVersion, TimeZoneIdByName, RestParams, [NewBlr | Blr], [NewValue | Value]). 250 | 251 | params_to_blr(AcceptVersion, TimeZoneIdByName, Params) -> 252 | {BlrBody, Value} = params_to_blr(AcceptVersion, TimeZoneIdByName, Params, [], []), 253 | L = length(Params) * 2, 254 | Blr = lists:flatten([[5, 2, 4, 0], byte2(L), BlrBody, [255, 76]]), 255 | NullBitmap = if 256 | AcceptVersion >= 13 -> null_bitmap(Params); 257 | AcceptVersion < 13 -> [] 258 | end, 259 | {Blr, lists:flatten(NullBitmap ++ Value)}. 260 | -------------------------------------------------------------------------------- /test/efirebirdsql_tests.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2015-2019 Hajime Nakagami 3 | 4 | -module(efirebirdsql_tests). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | -include("efirebirdsql.hrl"). 8 | 9 | tmp_dbname() -> 10 | lists:flatten(io_lib:format("/tmp/~p.fdb", [erlang:system_time()])). 11 | 12 | get_major_version(Conn) -> 13 | ok = efirebirdsql:execute(Conn, <<"select cast(LEFT(rdb$get_context('SYSTEM', 'ENGINE_VERSION'), 1) as int) from rdb$database;">>), 14 | {ok, [{_, MajorVersion}]} = efirebirdsql:fetchone(Conn), 15 | MajorVersion. 16 | 17 | create_test_db(DbName) -> 18 | %% crete new database 19 | {ok, C} = efirebirdsql:connect( 20 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, 21 | [{createdb, true}, {timezone, "Asia/Tokyo"}]), 22 | C. 23 | 24 | create_test_tables(C) -> 25 | %% crete new database 26 | ok = efirebirdsql:execute(C, <<" 27 | CREATE TABLE foo ( 28 | a INTEGER NOT NULL, 29 | b VARCHAR(30) NOT NULL UNIQUE, 30 | c CHAR(1024), 31 | d DECIMAL(16,3) DEFAULT -0.123, 32 | e DATE DEFAULT '1967-08-11', 33 | f TIMESTAMP DEFAULT '1967-08-11 23:45:01', 34 | g TIME DEFAULT '23:45:01', 35 | h0 BLOB SUB_TYPE 0, 36 | h1 BLOB SUB_TYPE 1, 37 | i DOUBLE PRECISION DEFAULT 1.0, 38 | j FLOAT DEFAULT 2.0, 39 | k BIGINT DEFAULT 123456789999999999, 40 | PRIMARY KEY (a), 41 | CONSTRAINT CHECK_A CHECK (a <> 0) 42 | ) 43 | ">>), 44 | ok = efirebirdsql:execute(C, <<"insert into foo(a, b, c, h0, h1) values (?,?,?,?,?)">>, [1, <<"b">>, <<"c">>, nil, <<"blob">>]), 45 | ok = efirebirdsql:execute(C, <<"insert into foo(a, b, c, h1, k) values (?,?,?,?,?)">>, [2, <<"B">>, <<"C">>, <<"BLOB">>, -1]), 46 | 47 | ok = efirebirdsql:execute(C, <<" 48 | CREATE PROCEDURE foo_proc 49 | AS 50 | BEGIN 51 | END 52 | ">>), 53 | ok = efirebirdsql:execute(C, <<" 54 | CREATE PROCEDURE bar_proc (param_a INTEGER, param_b VARCHAR(30)) 55 | RETURNS (out1 INTEGER, out2 VARCHAR(30)) 56 | AS 57 | BEGIN 58 | out1 = param_a; 59 | out2 = param_b; 60 | END 61 | ">>). 62 | 63 | description() -> 64 | [{<<"A">>,long,0,4,false}, 65 | {<<"B">>,varying,0,120,false}, 66 | {<<"C">>,text,0,4096,true}, 67 | {<<"D">>,int64,-3,8,true}, 68 | {<<"E">>,date,0,4,true}, 69 | {<<"F">>,timestamp,0,8,true}, 70 | {<<"G">>,time,0,4,true}, 71 | {<<"H0">>,blob,0,8,true}, 72 | {<<"H1">>,blob,4,8,true}, 73 | {<<"I">>,double,0,8,true}, 74 | {<<"J">>,float,0,4,true}, 75 | {<<"K">>,int64,0,8,true}]. 76 | 77 | alias_description() -> 78 | [{<<"ALIAS_NAME">>,long,0,4,false}]. 79 | 80 | result1() -> 81 | [{<<"A">>,1}, 82 | {<<"B">>,<<"b">>}, 83 | {<<"C">>,<<"c">>}, 84 | {<<"D">>,"-0.123"}, 85 | {<<"E">>,{1967,8,11}}, 86 | {<<"F">>,{{1967,8,11},{23,45,1,0}}}, 87 | {<<"G">>,{23,45,1,0}}, 88 | {<<"H0">>,nil}, 89 | {<<"H1">>,<<"blob">>}, 90 | {<<"I">>,1.0}, 91 | {<<"J">>,2.0}, 92 | {<<"K">>,123456789999999999}]. 93 | 94 | result2() -> 95 | [{<<"A">>,2}, 96 | {<<"B">>,<<"B">>}, 97 | {<<"C">>,<<"C">>}, 98 | {<<"D">>,"-0.123"}, 99 | {<<"E">>,{1967,8,11}}, 100 | {<<"F">>,{{1967,8,11},{23,45,1,0}}}, 101 | {<<"G">>,{23,45,1,0}}, 102 | {<<"H0">>,nil}, 103 | {<<"H1">>,<<"BLOB">>}, 104 | {<<"I">>,1.0}, 105 | {<<"J">>,2.0}, 106 | {<<"K">>,-1}]. 107 | 108 | basic_test() -> 109 | %% connect to bad database 110 | {error, ErrMsg} = efirebirdsql:connect( 111 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), "something_wrong_database", []), 112 | ?assertNotEqual(ErrMsg, nil), 113 | DbName = tmp_dbname(), 114 | CreatedConn = create_test_db(DbName), 115 | create_test_tables(CreatedConn), 116 | efirebirdsql:close(CreatedConn), 117 | 118 | {ok, C} = efirebirdsql:connect( 119 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, []), 120 | ok = efirebirdsql:ping(C), 121 | 122 | %% execute bad query 123 | {error, ErrMsg2} = efirebirdsql:prepare(C, <<"bad query statement">>), 124 | {error, ErrMsg2} = efirebirdsql:execute(C, <<"bad query statement">>), 125 | 126 | %% field name 127 | ok = efirebirdsql:execute(C, <<"select a alias_name from foo">>), 128 | ?assertEqual(efirebirdsql:description(C), alias_description()), 129 | 130 | %% fetchall 131 | ok = efirebirdsql:execute(C, <<"select * from foo order by a">>), 132 | ?assertEqual(efirebirdsql:description(C), description()), 133 | {ok, ResultAll} = efirebirdsql:fetchall(C), 134 | ?assertEqual(ResultAll, [result1(), result2()]), 135 | 136 | %% prepare and multi execute 137 | ok = efirebirdsql:prepare(C, <<"select * from foo order by a">>), 138 | ok = efirebirdsql:execute(C, []), 139 | {ok, ResultAll} = efirebirdsql:fetchall(C), 140 | ok = efirebirdsql:execute(C, []), 141 | {ok, ResultAll} = efirebirdsql:fetchall(C), 142 | 143 | %% fetch one by one 144 | ok = efirebirdsql:execute(C, <<"select * from foo order by a">>), 145 | {ok, ResultOne} = efirebirdsql:fetchone(C), 146 | ?assertEqual(ResultOne, result1()), 147 | {ok, ResultTwo} = efirebirdsql:fetchone(C), 148 | ?assertEqual(ResultTwo, result2()), 149 | 150 | %% query with parameter 151 | ok = efirebirdsql:execute(C, <<"select * from foo where a=?">>, [1]), 152 | {ok, ResultA1} = efirebirdsql:fetchall(C), 153 | ?assertEqual(ResultA1, [result1()]), 154 | ok = efirebirdsql:execute(C, <<"select * from foo where b=?">>, [<<"B">>]), 155 | {ok, ResultB2} = efirebirdsql:fetchall(C), 156 | ?assertEqual(ResultB2, [result2()]), 157 | 158 | ok = efirebirdsql:close(C), 159 | 160 | %% commit and rollback 161 | {ok, C2} = efirebirdsql:connect( 162 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, 163 | [{auto_commit, false}]), 164 | ok = efirebirdsql:execute(C2, <<"update foo set c='C'">>), 165 | ok = efirebirdsql:execute(C2, <<"select * from foo where c='C'">>), 166 | {ok, ResultAll2} = efirebirdsql:fetchall(C2), 167 | ?assertEqual(length(ResultAll2), 2), 168 | ok = efirebirdsql:rollback(C2), 169 | ok = efirebirdsql:execute(C2, <<"select * from foo where c='C'">>), 170 | {ok, ResultAll3} = efirebirdsql:fetchall(C2), 171 | ?assertEqual(length(ResultAll3), 1), 172 | 173 | %% prepare and execute parameterized query 174 | ok = efirebirdsql:prepare(C2, <<"select * from foo where c=?">>), 175 | ok = efirebirdsql:execute(C2, [<<"C">>]), 176 | {ok, ResultAll4} = efirebirdsql:fetchall(C2), 177 | ?assertEqual(length(ResultAll4), 1), 178 | ok = efirebirdsql:execute(C2, [<<"c">>]), 179 | {ok, ResultAll5} = efirebirdsql:fetchall(C2), 180 | ?assertEqual(length(ResultAll5), 1), 181 | ok = efirebirdsql:prepare(C2, <<"select * from foo">>), 182 | ok = efirebirdsql:execute(C2), 183 | {ok, ResultAll6} = efirebirdsql:fetchall(C2), 184 | ?assertEqual(length(ResultAll6), 2), 185 | 186 | %% insert .. returning 187 | ok = efirebirdsql:execute(C2, <<"insert into foo(a, b) values (3, 'c') returning a, b">>), 188 | {ok, ResultReturning} = efirebirdsql:fetchone(C2), 189 | ?assertEqual(ResultReturning, [{<<"A">>,3}, {<<"B">>,<<"c">>}]), 190 | 191 | %% fetch null value 192 | ok = efirebirdsql:execute(C2, <<"select a,c from foo where A=3">>), 193 | {ok, ResultNull} = efirebirdsql:fetchone(C2), 194 | ?assertEqual(ResultNull, [{<<"A">>,3}, {<<"C">>,nil}]), 195 | 196 | %% procedure call 197 | ok = efirebirdsql:execute(C2, <<"EXECUTE PROCEDURE foo_proc">>), 198 | ok = efirebirdsql:execute(C2, <<"EXECUTE PROCEDURE bar_proc(4, 'd')">>), 199 | {ok, ResultProcedure} = efirebirdsql:fetchone(C2), 200 | ?assertEqual(ResultProcedure, [{<<"OUT1">>,4}, {<<"OUT2">>,<<"d">>}]), 201 | 202 | %% Fetch null value issue #6 203 | {ok, C3} = efirebirdsql:connect( 204 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), tmp_dbname(), 205 | [{createdb, true}]), 206 | ok = efirebirdsql:execute(C3, <<"create table TestTable (ID int, testvalue int)">>), 207 | ok = efirebirdsql:execute(C3, <<"insert into TestTable (ID, testvalue) values (2, null)">>), 208 | ok = efirebirdsql:execute(C3, <<"select * from TestTable">>), 209 | {ok, ResultHasNull} = efirebirdsql:fetchall(C3), 210 | ?assertEqual(ResultHasNull, [[{<<"ID">>,2}, {<<"TESTVALUE">>,nil}]]). 211 | 212 | many_column_test() -> 213 | DbName = tmp_dbname(), 214 | C = create_test_db(DbName), 215 | ok = efirebirdsql:execute(C, <<" 216 | CREATE TABLE ABCDEFGHIJKLMNOPQRSTUV( 217 | A varchar(10), 218 | B varchar(10), 219 | C varchar(10), 220 | D varchar(10), 221 | E varchar(10), 222 | F varchar(10), 223 | G varchar(10), 224 | H varchar(10), 225 | I varchar(10), 226 | J varchar(10), 227 | K varchar(10), 228 | L varchar(10), 229 | M varchar(10), 230 | N varchar(10), 231 | O varchar(10), 232 | P varchar(10), 233 | Q varchar(10), 234 | R varchar(10), 235 | S varchar(10), 236 | T varchar(10), 237 | U varchar(10), 238 | V varchar(10), 239 | W varchar(10), 240 | X varchar(10), 241 | Y varchar(10), 242 | Z varchar(10), 243 | RFCEMPRESA varchar(20) NOT NULL, 244 | NOSUCURSAL integer NOT NULL, 245 | TIPO integer NOT NULL, 246 | SERIE varchar(5) NOT NULL, 247 | NODOCTO integer NOT NULL, 248 | LINEA integer NOT NULL, 249 | CODART varchar(20), 250 | NOMART varchar(80), 251 | CLAVEPRODSERV varchar(10), 252 | UNIDADCLAVE varchar(10), 253 | UNIDADNOMBRE varchar(80), 254 | CANT1 double precision, 255 | CATN2 double precision, 256 | PUNIT double precision, 257 | MONTO double precision, 258 | IMPTO1 double precision, 259 | IMPTO2 double precision, 260 | PIMPTO1 double precision, 261 | PIMPTO2 double precision, 262 | TIMPTO1 varchar(10), 263 | TIMPTO2 varchar(10), 264 | TFIMPTO1 varchar(10), 265 | TFIMPTO2 varchar(10), 266 | PDESCTO double precision, 267 | IDESCTO double precision 268 | ) 269 | ">>), 270 | ok = efirebirdsql:execute(C, <<" 271 | SELECT * FROM ABCDEFGHIJKLMNOPQRSTUV 272 | ">>), 273 | ?assertEqual(length(efirebirdsql:description(C)), 51), 274 | {ok, Result} = efirebirdsql:fetchall(C), 275 | ?assertEqual(length(Result), 0). 276 | 277 | fb3_test() -> 278 | DbName = tmp_dbname(), 279 | CreatedConn = create_test_db(DbName), 280 | FirebirdMajorVersion = get_major_version(CreatedConn), 281 | if 282 | FirebirdMajorVersion >= 3 -> 283 | create_test_tables(CreatedConn), 284 | efirebirdsql:close(CreatedConn), 285 | {ok, C} = efirebirdsql:connect( 286 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, []), 287 | ok = efirebirdsql:execute(C, <<"select True AS C from rdb$relations">>), 288 | ?assertEqual({ok, [{<<"C">>, true}]}, efirebirdsql:fetchone(C)), 289 | ok = efirebirdsql:execute(C, <<"select False AS C from rdb$relations">>), 290 | ?assertEqual({ok, [{<<"C">>, true}]}, efirebirdsql:fetchone(C)), 291 | 292 | ok = efirebirdsql:commit(C), 293 | ok = efirebirdsql:close(C); 294 | FirebirdMajorVersion < 3 -> 295 | ok 296 | end. 297 | 298 | 299 | create_fb4_test_tables(C) -> 300 | %% crete new database 301 | ok = efirebirdsql:execute(C, <<" 302 | CREATE TABLE dec_test ( 303 | d DECIMAL(20, 2), 304 | df64 DECFLOAT(16), 305 | df128 DECFLOAT(34), 306 | s varchar(32)) 307 | ">>), 308 | ok = efirebirdsql:execute(C, <<"insert into dec_test(d, df64, df128, s) values (0.0, 0.0, 0.0, '0.0')">>), 309 | ok = efirebirdsql:execute(C, <<"insert into dec_test(d, df64, df128, s) values (1.0, 1.0, 1.0, '1.0')">>), 310 | ok = efirebirdsql:execute(C, <<"insert into dec_test(d, df64, df128, s) values (20.0, 20.0, 20.0, '20.0')">>), 311 | ok = efirebirdsql:execute(C, <<"insert into dec_test(d, df64, df128, s) values (-1.0, -1.0, -1.0, '-1.0')">>), 312 | ok = efirebirdsql:execute(C, <<"insert into dec_test(d, df64, df128, s) values (-20.0, -20.0, -20.0, '-20.0')">>), 313 | 314 | ok = efirebirdsql:execute(C, <<" 315 | CREATE TABLE tz_test ( 316 | id INTEGER NOT NULL, 317 | t TIME WITH TIME ZONE DEFAULT '12:34:56', 318 | ts TIMESTAMP WITH TIME ZONE DEFAULT '1967-08-11 23:45:01', 319 | PRIMARY KEY (id) 320 | ) 321 | ">>), 322 | ok = efirebirdsql:execute(C, <<"insert into tz_test (id) values (1)">>), 323 | ok = efirebirdsql:execute(C, <<"insert into tz_test (id, t, ts) values (2, '12:34:56 Asia/Seoul', '1967-08-11 23:45:01.0000 Asia/Seoul')">>), 324 | ok = efirebirdsql:execute(C, <<"insert into tz_test (id, t, ts) values (3, '03:34:56 UTC', '1967-08-11 14:45:01.0000 UTC')">>). 325 | 326 | fb4_test() -> 327 | DbName = tmp_dbname(), 328 | CreatedConn = create_test_db(DbName), 329 | FirebirdMajorVersion = get_major_version(CreatedConn), 330 | if 331 | FirebirdMajorVersion >= 4 -> 332 | create_fb4_test_tables(CreatedConn), 333 | efirebirdsql:close(CreatedConn), 334 | {ok, C} = efirebirdsql:connect( 335 | "localhost", os:getenv("ISC_USER", "sysdba"), os:getenv("ISC_PASSWORD", "masterkey"), DbName, [{timezone, "Asia/Tokyo"}]), 336 | ok = efirebirdsql:execute(C, <<"select * from dec_test">>), 337 | {ok, ResultDecFloat} = efirebirdsql:fetchall(C), 338 | ?assertEqual([ 339 | [{<<"D">>,"0.00"}, {<<"DF64">>,"0.0"}, {<<"DF128">>,"0.0"}, {<<"S">>, <<"0.0">>}], 340 | [{<<"D">>,"1.00"}, {<<"DF64">>,"1.0"}, {<<"DF128">>,"1.0"}, {<<"S">>, <<"1.0">>}], 341 | [{<<"D">>,"20.00"}, {<<"DF64">>,"20.0"}, {<<"DF128">>,"20.0"}, {<<"S">>, <<"20.0">>}], 342 | [{<<"D">>,"-1.00"}, {<<"DF64">>,"-1.0"}, {<<"DF128">>,"-1.0"}, {<<"S">>, <<"-1.0">>}], 343 | [{<<"D">>,"-20.00"}, {<<"DF64">>,"-20.0"}, {<<"DF128">>,"-20.0"}, {<<"S">>, <<"-20.0">>}] 344 | ], ResultDecFloat), 345 | 346 | ok = efirebirdsql:execute(C, <<"select * from tz_test">>), 347 | {ok, ResultTimeZone} = efirebirdsql:fetchall(C), 348 | ?assertEqual(ResultTimeZone, [ 349 | [{<<"ID">>,1}, {<<"T">>,{{3,34,56,0},<<"GMT">>,<<"Asia/Tokyo">>}}, {<<"TS">>,{{1967,8,11},{14,45,1,0}, <<"GMT">>,<<"Asia/Tokyo">>}}], 350 | [{<<"ID">>,2}, {<<"T">>,{{3,34,56,0},<<"GMT">>,<<"Asia/Seoul">>}}, {<<"TS">>,{{1967,8,11},{14,45,1,0}, <<"GMT">>,<<"Asia/Seoul">>}}], 351 | [{<<"ID">>,3}, {<<"T">>,{{3,34,56,0},<<"GMT">>,<<"UTC">>}}, {<<"TS">>,{{1967,8,11},{14,45,1,0}, <<"GMT">>,<<"UTC">>}}] 352 | % ]), 353 | % ok = efirebirdsql:execute(C, <<"select * from tz_test where T=? and TS=?">>, 354 | % [{{12,34,56, 0}, <<"Asia/Seoul">>}, {{1967,8,11},{23,45,1,0}, <<"Asia/Seoul">>}]), 355 | % {ok, ResultTimeZone2} = efirebirdsql:fetchall(C), 356 | % ?assertEqual(ResultTimeZone2, ResultTimeZone); 357 | ]); 358 | FirebirdMajorVersion < 4 -> 359 | ok 360 | end. 361 | -------------------------------------------------------------------------------- /src/efirebirdsql_protocol.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | 3 | %%% Copyright (c) 2016-2022 Hajime Nakagami 4 | 5 | -module(efirebirdsql_protocol). 6 | %%% -define(DEBUG_FORMAT(X,Y), io:format(standard_error, X, Y)). 7 | -define(DEBUG_FORMAT(X,Y), ok). 8 | 9 | -export([connect/5, close/1, begin_transaction/2]). 10 | -export([unallocate_statement/1, allocate_statement/1, allocate_statement/2]). 11 | -export([prepare_statement/3, prepare_statement/2, free_statement/3]). 12 | -export([columns/1]). 13 | -export([execute/2, execute/3, rowcount/2, exec_immediate/2, ping/1]). 14 | -export([fetchone/2, fetchall/2]). 15 | -export([description/1]). 16 | -export([commit_retaining/1, rollback_retaining/1, commit/1, rollback/1]). 17 | 18 | -include("efirebirdsql.hrl"). 19 | 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | %% Utility functions in module 22 | 23 | -spec connect_database(conn(), string(), list(), boolean(), integer()) -> {ok, conn()} | {error, integer(), binary(), conn()}. 24 | connect_database(Conn, Host, Database, IsCreateDB, PageSize) -> 25 | efirebirdsql_socket:send(Conn, 26 | efirebirdsql_op:op_connect(Host, Conn#conn.user, Conn#conn.client_public, Conn#conn.auth_plugin, Conn#conn.wire_crypt, Database)), 27 | case efirebirdsql_op:get_connect_response(Conn) of 28 | {ok, NewConn} -> 29 | case IsCreateDB of 30 | true -> 31 | efirebirdsql_socket:send(NewConn, efirebirdsql_op:op_create(NewConn, Database, PageSize)); 32 | false -> 33 | efirebirdsql_socket:send(NewConn, efirebirdsql_op:op_attach(NewConn, Database)) 34 | end, 35 | case efirebirdsql_op:get_response(NewConn) of 36 | {op_response, Handle, _} -> {ok, NewConn#conn{db_handle=Handle}}; 37 | {op_fetch_response, _, _} -> {error, <<"Unknown op_fetch_response">>, NewConn}; 38 | {op_sql_response, _} -> {error, <<"Unknown op_sql_response">>, NewConn}; 39 | {error, ErrNo, Msg} -> {error, ErrNo, Msg, NewConn} 40 | end; 41 | {error, Reason, NewConn} -> 42 | {error, Reason, NewConn} 43 | end. 44 | 45 | -spec ready_fetch_segment(conn(), stmt()) -> stmt(). 46 | ready_fetch_segment(Conn, Stmt) when Stmt#stmt.rows =:= [], Stmt#stmt.more_data =:= true -> 47 | StmtHandle = Stmt#stmt.stmt_handle, 48 | XSqlVars = Stmt#stmt.xsqlvars, 49 | efirebirdsql_socket:send(Conn, 50 | efirebirdsql_op:op_fetch(StmtHandle, XSqlVars)), 51 | {ok, Rows, MoreData} = efirebirdsql_op:get_fetch_response(Conn, Stmt), 52 | Stmt2 = Stmt#stmt{rows=Rows, more_data=MoreData}, 53 | {ok, Stmt3} = if 54 | MoreData =:= true -> {ok, Stmt2}; 55 | MoreData =:= false -> free_statement(Conn, Stmt2, close) 56 | end, 57 | Stmt3; 58 | ready_fetch_segment(_Conn, Stmt) -> 59 | Stmt. 60 | 61 | fetchrow(Conn, Stmt) -> 62 | Rows = Stmt#stmt.rows, 63 | case Rows of 64 | [] -> 65 | {nil, Stmt}; 66 | _ -> 67 | [R | Rest] = Rows, 68 | ConvertedRow = efirebirdsql_op:convert_row(Conn, Stmt#stmt.xsqlvars, R), 69 | {ConvertedRow, Stmt#stmt{rows=Rest}} 70 | end. 71 | 72 | 73 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 74 | % public functions 75 | 76 | -spec connect(string(), string(), string(), string(), list()) -> {ok, conn()} | {error, integer(), binary(), conn()}. 77 | connect(Host, Username, Password, Database, Options) -> 78 | ?DEBUG_FORMAT("connect()~n", []), 79 | SockOptions = [{active, false}, {packet, raw}, binary], 80 | Port = proplists:get_value(port, Options, 3050), 81 | Charset = proplists:get_value(charset, Options, utf_8), 82 | IsCreateDB = proplists:get_value(createdb, Options, false), 83 | PageSize = proplists:get_value(pagesize, Options, 4096), 84 | AutoCommit = proplists:get_value(auto_commit, Options, true), 85 | Private = efirebirdsql_srp:get_private_key(), 86 | Public = efirebirdsql_srp:client_public(Private), 87 | case gen_tcp:connect(Host, Port, SockOptions) of 88 | {ok, Sock} -> 89 | Conn = #conn{ 90 | sock=Sock, 91 | user=string:to_upper(Username), 92 | password=Password, 93 | charset=Charset, 94 | auto_commit=AutoCommit, 95 | client_private=Private, 96 | client_public=Public, 97 | auth_plugin=proplists:get_value(auth_plugin, Options, "Srp256"), 98 | wire_crypt=proplists:get_value(wire_crypt, Options, true), 99 | timezone=proplists:get_value(timezone, Options, nil) 100 | }, 101 | case Conn#conn.auto_commit of 102 | true -> 103 | case connect_database(Conn, Host, Database, IsCreateDB, PageSize) of 104 | {ok, C2} -> 105 | case begin_transaction(AutoCommit, C2) of 106 | {ok, C3} -> {ok, C3}; 107 | {error, ErrNo, Reason, C3} -> {error, ErrNo, Reason, C3} 108 | end; 109 | {error, ErrNo, Reason, C2} -> 110 | {error, ErrNo, Reason, C2} 111 | end; 112 | false -> 113 | connect_database(Conn, Host, Database, IsCreateDB, PageSize) 114 | end; 115 | {error, Reason} -> 116 | {error, 0, atom_to_binary(Reason, latin1), #conn{ 117 | user=string:to_upper(Username), 118 | password=Password, 119 | client_private=Private, 120 | client_public=Public, 121 | auth_plugin=proplists:get_value(auth_plugin, Options, "Srp256"), 122 | wire_crypt=proplists:get_value(wire_crypt, Options, true), 123 | auto_commit=proplists:get_value(auto_commit, Options, false), 124 | timezone=proplists:get_value(timezone, Options, nil) 125 | }} 126 | end. 127 | 128 | -spec close(conn()) -> {ok, conn()} | {error, integer(), binary(), conn()}. 129 | close(Conn) -> 130 | ?DEBUG_FORMAT("close() db_handle=~p~n", [Conn#conn.db_handle]), 131 | efirebirdsql_socket:send(Conn, 132 | efirebirdsql_op:op_detach(Conn#conn.db_handle)), 133 | case efirebirdsql_op:get_response(Conn) of 134 | {op_response, _, _} -> 135 | gen_tcp:close(Conn#conn.sock), 136 | {ok, Conn#conn{sock=undefined}}; 137 | {error, ErrNo, Msg} -> 138 | if 139 | ErrNo =:= 335544357 -> % cannot disconnect database with open transactions 140 | gen_tcp:close(Conn#conn.sock), 141 | {ok, Conn#conn{sock=undefined}}; 142 | ErrNo =/= 335544357 -> 143 | ?DEBUG_FORMAT("close() error ~p~p~n", [ErrNo, Msg]), 144 | {error, ErrNo, Msg, Conn} 145 | end 146 | end. 147 | 148 | %% Transaction 149 | -spec begin_transaction(boolean(), conn()) -> {ok, conn()} | {error, integer(), binary(), conn()}. 150 | begin_transaction(AutoCommit, Conn) -> 151 | ?DEBUG_FORMAT("begin_transaction() db_handle=~p~n", [Conn#conn.db_handle]), 152 | %% ISOLATION_LEVEL_READ_COMMITED 153 | %% isc_tpb_version3,isc_tpb_write,isc_tpb_wait,isc_tpb_read_committed,isc_tpb_rec_version 154 | Tpb = if 155 | AutoCommit =:= true -> [3, 9, 6, 15, 17, 16]; % +isc_tpb_autocommit 156 | AutoCommit =:= false -> [3, 9, 6, 15, 17] 157 | end, 158 | efirebirdsql_socket:send(Conn, 159 | efirebirdsql_op:op_transaction(Conn#conn.db_handle, Tpb)), 160 | case efirebirdsql_op:get_response(Conn) of 161 | {op_response, Handle, _} -> {ok, Conn#conn{trans_handle=Handle}}; 162 | {error, ErrNo, Msg} -> {error, ErrNo, Msg, Conn} 163 | end. 164 | 165 | %% get stmt it still not allocate statement handle but register sql 166 | -spec unallocate_statement(binary()) -> {ok, stmt()}. 167 | unallocate_statement(Sql) -> 168 | ?DEBUG_FORMAT("unallocate_statement() sql=~p~n", [Sql]), 169 | {ok, #stmt{sql=Sql}}. 170 | 171 | %% allocate, prepare and free statement 172 | 173 | -spec allocate_statement(conn(), stmt()) -> {ok, stmt()} | {error, integer(), binary()}. 174 | allocate_statement(Conn, Stmt) -> 175 | ?DEBUG_FORMAT("allocate_statement() db_handle=~p~n", [Conn#conn.db_handle]), 176 | efirebirdsql_socket:send(Conn, 177 | efirebirdsql_op:op_allocate_statement(Conn#conn.db_handle)), 178 | case efirebirdsql_op:get_response(Conn) of 179 | {op_response, Handle, _} -> {ok, Stmt#stmt{stmt_handle=Handle}}; 180 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 181 | end. 182 | 183 | -spec allocate_statement(conn()) -> {ok, stmt()} | {error, integer(), binary()}. 184 | allocate_statement(Conn) -> 185 | allocate_statement(Conn, #stmt{}). 186 | 187 | -spec prepare_statement(binary(), conn(), stmt()) -> {ok, stmt()} | {error, integer(), binary()}. 188 | prepare_statement(Sql, Conn, Stmt) -> 189 | ?DEBUG_FORMAT("prepare_statement() stmt_handle=~p:~p~n", [Stmt#stmt.stmt_handle, Sql]), 190 | {ok, Stmt2} = case Stmt#stmt.closed of 191 | true -> {ok, Stmt}; 192 | false -> free_statement(Conn, Stmt, close) 193 | end, 194 | efirebirdsql_socket:send(Conn, 195 | efirebirdsql_op:op_prepare_statement(Conn#conn.trans_handle, Stmt2#stmt.stmt_handle, Sql)), 196 | efirebirdsql_op:get_prepare_statement_response(Conn, Stmt2#stmt{sql=Sql}). 197 | 198 | -spec prepare_statement(conn(), stmt()) -> {ok, stmt()} | {error, integer(), binary()}. 199 | prepare_statement(Conn, Stmt) -> 200 | {ok, Stmt2} = if 201 | Stmt#stmt.stmt_handle =:= nil -> allocate_statement(Conn, Stmt); 202 | Stmt#stmt.stmt_handle =/= nil -> {ok, Stmt} 203 | end, 204 | prepare_statement(Stmt2#stmt.sql, Conn, Stmt2). 205 | 206 | -spec free_statement(conn(), stmt(), atom()) -> {ok, stmt()} | {error, integer(), binary()}. 207 | free_statement(Conn, Stmt, Type) -> 208 | ?DEBUG_FORMAT("free_statement() stmt_handle=~p:type=~p:~p~n", [Stmt#stmt.stmt_handle, Type, Stmt#stmt.sql]), 209 | efirebirdsql_socket:send(Conn, 210 | efirebirdsql_op:op_free_statement(Stmt#stmt.stmt_handle, Type)), 211 | case efirebirdsql_op:get_response(Conn) of 212 | {op_response, _, _} -> if 213 | Type =:= drop -> {ok, #stmt{sql=Stmt#stmt.sql}}; 214 | Type =:= close -> {ok, Stmt#stmt{closed=true}} 215 | end; 216 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 217 | end. 218 | 219 | -spec columns(stmt()) -> list(). 220 | columns(Columns, []) -> 221 | lists:reverse(Columns); 222 | columns(Columns, XSQLVars) -> 223 | [C | Rest] = XSQLVars, 224 | columns([{C#column.name, C#column.type, C#column.scale, C#column.length, C#column.null_ind} | Columns], Rest). 225 | columns(Stmt) -> 226 | columns([], Stmt#stmt.xsqlvars). 227 | 228 | 229 | %% Execute 230 | 231 | execute(Conn, Stmt, Params, isc_info_sql_stmt_exec_procedure) -> 232 | efirebirdsql_socket:send(Conn, efirebirdsql_op:op_execute2(Conn, Stmt, Params)), 233 | {Row, C3} = efirebirdsql_op:get_sql_response(Conn, Stmt), 234 | case efirebirdsql_op:get_response(C3) of 235 | {op_response, _, _} -> {ok, Stmt#stmt{rows=[Row], more_data=false}}; 236 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 237 | end; 238 | execute(Conn, Stmt, Params, isc_info_sql_stmt_select) -> 239 | efirebirdsql_socket:send(Conn, efirebirdsql_op:op_execute(Conn, Stmt, Params)), 240 | case efirebirdsql_op:get_response(Conn) of 241 | {op_response, _, _} -> 242 | {ok, Stmt#stmt{rows=[], more_data=true, closed=false}}; 243 | {error, ErrNo, Msg} -> 244 | {error, ErrNo, Msg} 245 | end; 246 | execute(Conn, Stmt, Params, _StmtType) -> 247 | efirebirdsql_socket:send(Conn, efirebirdsql_op:op_execute(Conn, Stmt, Params)), 248 | case efirebirdsql_op:get_response(Conn) of 249 | {op_response, _, _} -> {ok, Stmt#stmt{rows=nil, more_data=false}}; 250 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 251 | end. 252 | 253 | -spec execute(conn(), stmt(), list()) -> {ok, stmt()} | {error, integer(), binary()}. 254 | execute(Conn, Stmt, Params) when Stmt#stmt.stmt_handle =:= nil -> 255 | ?DEBUG_FORMAT("execute() stmt_type=~p,stmt_handle=~p:~p:~p~n", [Stmt#stmt.stmt_type, Stmt#stmt.stmt_handle, Stmt#stmt.sql, Params]), 256 | case prepare_statement(Conn, Stmt) of 257 | {ok, Stmt2} -> execute(Conn, Stmt2, Params); 258 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 259 | end; 260 | execute(Conn, Stmt, Params) when Stmt#stmt.stmt_handle =/= nil -> 261 | ?DEBUG_FORMAT("execute() stmt_type=~p,stmt_handle=~p:~p:~p~n", [Stmt#stmt.stmt_type, Stmt#stmt.stmt_handle, Stmt#stmt.sql, Params]), 262 | execute(Conn, Stmt, Params, Stmt#stmt.stmt_type). 263 | 264 | execute(Conn, Stmt) -> 265 | execute(Conn, Stmt, []). 266 | 267 | -spec rowcount(conn(), stmt()) -> {ok, integer()} | {error, integer(), binary()}. 268 | rowcount(_Conn, Stmt) when Stmt#stmt.stmt_type =:= isc_info_sql_stmt_ddl -> 269 | {ok, 0}; 270 | rowcount(Conn, Stmt) -> 271 | efirebirdsql_socket:send(Conn, 272 | efirebirdsql_op:op_info_sql(Stmt#stmt.stmt_handle, [23])), % 23:isc_info_sql_records 273 | case efirebirdsql_op:get_response(Conn) of 274 | {op_response, _, Buf} -> 275 | << _:48, Count1:32/little-unsigned, _:24, Count2:32/little-unsigned, _:24, Count3:32/little-unsigned, _:24, Count4:32/little-unsigned, _Rest/binary >> = Buf, 276 | Count = if 277 | Stmt#stmt.stmt_type =:= isc_info_sql_stmt_select -> Count3; 278 | Stmt#stmt.stmt_type =/= isc_info_sql_stmt_select -> Count1+Count2+Count4 279 | end, 280 | {ok, Count}; 281 | {error, ErrNo, Msg} -> 282 | {error, ErrNo, Msg} 283 | end. 284 | 285 | -spec exec_immediate(binary(), conn()) -> ok | {error, integer(), binary()}. 286 | exec_immediate(Sql, Conn) -> 287 | ?DEBUG_FORMAT("exec_immediate()~n", []), 288 | efirebirdsql_socket:send(Conn, 289 | efirebirdsql_op:op_exec_immediate(Conn, Sql)), 290 | case efirebirdsql_op:get_response(Conn) of 291 | {op_response, _, _} -> ok; 292 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 293 | end. 294 | 295 | -spec ping(conn()) -> ok | error. 296 | ping(Conn) -> 297 | ?DEBUG_FORMAT("ping()~n", []), 298 | % Firebird 3.0+ 299 | % efirebirdsql_socket:send(Conn, 300 | % efirebirdsql_op:op_ping()), 301 | % case efirebirdsql_op:get_response(Conn) of 302 | % {op_response, _, _} -> ok; 303 | % _ -> error 304 | % end. 305 | 306 | % [isc_info_ods_version, isc_info_end] 307 | efirebirdsql_socket:send(Conn, 308 | efirebirdsql_op:op_info_database(Conn#conn.db_handle, [32, 1])), 309 | case efirebirdsql_op:get_response(Conn) of 310 | {op_response, _, _} -> ok; 311 | {error, _, _} -> error 312 | end. 313 | 314 | %% Fetch 315 | 316 | -spec fetchone(conn(), stmt()) -> {list() | nil, stmt()}. 317 | fetchone(Conn, Stmt) -> 318 | Stmt2 = ready_fetch_segment(Conn, Stmt), 319 | fetchrow(Conn, Stmt2). 320 | 321 | fetch_all(_Conn, Rows, nil, Stmt) -> 322 | {ok, lists:reverse(Rows), Stmt}; 323 | fetch_all(Conn, Rows, Row, Stmt) -> 324 | {NextRow, Stmt2} = fetchone(Conn, Stmt), 325 | fetch_all(Conn, [Row | Rows], NextRow, Stmt2). 326 | 327 | -spec fetchall(conn(), stmt()) -> {ok, list() | nil, stmt()}. 328 | fetchall(_Conn, Stmt) when Stmt#stmt.rows =:= nil -> 329 | {ok, nil, Stmt}; 330 | fetchall(Conn, Stmt) -> 331 | {NextRow, Stmt2} = fetchone(Conn, Stmt), 332 | fetch_all(Conn, [], NextRow, Stmt2). 333 | 334 | %% Description 335 | -spec description(stmt()) -> list(). 336 | description([], XSqlVar) -> 337 | lists:reverse(XSqlVar); 338 | description(InXSqlVars, XSqlVar) -> 339 | [H | T] = InXSqlVars, 340 | description(T, [{H#column.name, H#column.type, H#column.scale, 341 | H#column.length, H#column.null_ind} | XSqlVar]). 342 | description(Stmt) -> 343 | description(Stmt#stmt.xsqlvars, []). 344 | 345 | %% Commit and rollback 346 | -spec commit_retaining(conn()) -> ok | {error, integer(), binary()}. 347 | commit_retaining(Conn) -> 348 | ?DEBUG_FORMAT("commit_retaining() trans_handle=~p~n", [Conn#conn.trans_handle]), 349 | efirebirdsql_socket:send(Conn, 350 | efirebirdsql_op:op_commit_retaining(Conn#conn.trans_handle)), 351 | case efirebirdsql_op:get_response(Conn) of 352 | {op_response, _, _} -> ok; 353 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 354 | end. 355 | 356 | -spec rollback_retaining(conn()) -> ok | {error, integer(), binary()}. 357 | rollback_retaining(Conn) -> 358 | ?DEBUG_FORMAT("rollback_retaining() trans_handle=~p~n", [Conn#conn.trans_handle]), 359 | efirebirdsql_socket:send(Conn, 360 | efirebirdsql_op:op_rollback_retaining(Conn#conn.trans_handle)), 361 | case efirebirdsql_op:get_response(Conn) of 362 | {op_response, _, _} -> ok; 363 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 364 | end. 365 | 366 | -spec commit(conn()) -> ok | {error, integer(), binary()}. 367 | commit(Conn) -> 368 | ?DEBUG_FORMAT("commit() trans_handle=~p~n", [Conn#conn.trans_handle]), 369 | efirebirdsql_socket:send(Conn, 370 | efirebirdsql_op:op_commit(Conn#conn.trans_handle)), 371 | case efirebirdsql_op:get_response(Conn) of 372 | {op_response, _, _} -> ok; 373 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 374 | end. 375 | 376 | -spec rollback(conn()) -> ok | {error, integer(), binary()}. 377 | rollback(Conn) -> 378 | ?DEBUG_FORMAT("rollback() trans_handle=~p~n", [Conn#conn.trans_handle]), 379 | efirebirdsql_socket:send(Conn, 380 | efirebirdsql_op:op_rollback(Conn#conn.trans_handle)), 381 | case efirebirdsql_op:get_response(Conn) of 382 | {op_response, _, _} -> ok; 383 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 384 | end. 385 | -------------------------------------------------------------------------------- /src/efirebirdsql_op.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2016-2022 Hajime Nakagami 3 | 4 | -module(efirebirdsql_op). 5 | %%% -define(DEBUG_FORMAT(X,Y), io:format(standard_error, X, Y)). 6 | -define(DEBUG_FORMAT(X,Y), ok). 7 | 8 | -export([op_connect/6, op_attach/2, op_detach/1, op_create/3, op_transaction/2, 9 | op_info_database/2, 10 | op_allocate_statement/1, op_prepare_statement/3, op_free_statement/2, 11 | op_execute/3, op_execute2/3, op_exec_immediate/2, op_ping/0, op_info_sql/2, op_fetch/2, 12 | op_commit_retaining/1, op_commit/1, op_rollback_retaining/1, op_rollback/1, 13 | convert_row/3, get_response/1, get_connect_response/1, get_fetch_response/2, 14 | get_sql_response/2, get_prepare_statement_response/2]). 15 | 16 | -include("efirebirdsql.hrl"). 17 | 18 | -define(BUFSIZE, 1024). 19 | -define(INFO_SQL_SELECT_DESCRIBE_VARS, [ 20 | 4, %% isc_info_sql_select 21 | 7, %% isc_info_sql_describe_vars 22 | 9, %% isc_info_sql_sqlda_seq 23 | 11, %% isc_info_sql_type 24 | 12, %% isc_info_sql_sub_type 25 | 13, %% isc_info_sql_scale 26 | 14, %% isc_info_sql_length 27 | 15, %% isc_info_sql_null_ind, 28 | 16, %% isc_info_sql_field, 29 | 17, %% isc_info_sql_relation, 30 | 18, %% isc_info_sql_owner, 31 | 19, %% isc_info_sql_alias, 32 | 8 %% isc_info_sql_describe_end 33 | ]). 34 | 35 | pack_cnct_param(K, V) when is_list(V) -> 36 | lists:flatten([K, length(V), V]); 37 | pack_cnct_param(K, V) when is_binary(V) -> 38 | pack_cnct_param(K, binary_to_list(V)). 39 | 40 | %%% parameters separate per 254 bytes 41 | pack_specific_data_cnct_param(Acc, _, _, []) -> 42 | lists:flatten(lists:reverse(Acc)); 43 | pack_specific_data_cnct_param(Acc, Idx, K, V) -> 44 | pack_specific_data_cnct_param( 45 | [pack_cnct_param(K, [Idx | lists:sublist(V, 1, 254)]) | Acc], 46 | Idx + 1, 47 | K, 48 | if length(V) > 254 -> lists:nthtail(254, V); length(V) =< 254 -> [] end). 49 | 50 | pack_specific_data_cnct_param(K, V) -> 51 | pack_specific_data_cnct_param([], 0, K, V). 52 | 53 | -spec uid(string(), string(), integer(), string(), boolean()) -> list(). 54 | uid(Host, User, SpecificData, AuthPlugin, WireCrypt) -> 55 | Data = lists:flatten([ 56 | pack_cnct_param(9, User), %% CNCT_login 57 | pack_cnct_param(8, AuthPlugin), %% CNCT_plugin_name 58 | pack_cnct_param(10, "Srp256,Srp"), %% CNCT_plugin_list 59 | pack_specific_data_cnct_param(7, 60 | efirebirdsql_srp:to_hex(SpecificData)), %% CNCT_specific_data 61 | pack_cnct_param(11, 62 | [if WireCrypt=:=true -> 1; WireCrypt =/= true -> 0 end, 0, 0, 0] 63 | ), %% CNCT_client_crypt 64 | pack_cnct_param(1, User), %% CNCT_user 65 | pack_cnct_param(4, Host), %% CNCT_host 66 | pack_cnct_param(6, "") %% CNCT_user_verification 67 | ]), 68 | efirebirdsql_conv:list_to_xdr_bytes(Data). 69 | 70 | convert_scale(Scale) -> 71 | if 72 | Scale < 0 -> 256 + Scale; 73 | Scale >= 0 -> Scale 74 | end. 75 | 76 | calc_blr_item(XSqlVar) -> 77 | case XSqlVar#column.type of 78 | varying -> [37 | efirebirdsql_conv:byte2(XSqlVar#column.length)] ++ [7, 0]; 79 | text -> [14 | efirebirdsql_conv:byte2(XSqlVar#column.length)] ++ [7, 0]; 80 | long -> [8, convert_scale(XSqlVar#column.scale), 7, 0]; 81 | short -> [7, convert_scale(XSqlVar#column.scale), 7, 0]; 82 | int64 -> [16, convert_scale(XSqlVar#column.scale), 7, 0]; 83 | int128 -> [26, convert_scale(XSqlVar#column.scale), 7, 0]; 84 | quad -> [9, convert_scale(XSqlVar#column.scale), 7, 0]; 85 | double -> [27, 7, 0]; 86 | float -> [10, 7, 0]; 87 | d_float -> [11, 7, 0]; 88 | date -> [12, 7, 0]; 89 | time -> [13, 7, 0]; 90 | timestamp -> [35, 7, 0]; 91 | decimal_fixed -> [26, convert_scale(XSqlVar#column.scale), 7, 0]; 92 | decimal64 -> [24, 7, 0]; 93 | decimal128 -> [25, 7, 0]; 94 | time_tz -> [28, 7, 0]; 95 | timestamp_tz -> [29, 7, 0]; 96 | blob -> [9, 0, 7, 0]; 97 | array -> [9, 0, 7, 0]; 98 | boolean -> [23, 7, 0] 99 | end. 100 | 101 | calc_blr_items([], Blr) -> 102 | Blr; 103 | calc_blr_items(XSqlVars, Blr) -> 104 | [H | T] = XSqlVars, 105 | calc_blr_items(T, Blr ++ calc_blr_item(H)). 106 | 107 | calc_blr(XSqlVars) -> 108 | L = length(XSqlVars) * 2, 109 | lists:flatten([[5, 2, 4, 0], 110 | efirebirdsql_conv:byte2(L), 111 | calc_blr_items(XSqlVars, []), 112 | [255, 76]]). 113 | 114 | %%% create op_connect binary 115 | -spec op_connect(string(), string(), integer(), string(), boolean(), list()) -> binary(). 116 | op_connect(Host, User, ClientPublic, AuthPlugin, WireCrypt, Database) -> 117 | ?DEBUG_FORMAT("op_connect -> ", []), 118 | %% PROTOCOL_VERSION,ArchType(Generic)=1,MinAcceptType=0,MaxAcceptType=4,Weight 119 | Protocols = if AuthPlugin == "Legacy_Auth" -> [ 120 | [ 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2], 121 | [255, 255, 128, 11, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4], 122 | [255, 255, 128, 12, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 6] 123 | ]; 124 | true -> [ 125 | [ 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2], 126 | [255, 255, 128, 11, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4], 127 | [255, 255, 128, 12, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 6], 128 | [255, 255, 128, 13, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8], 129 | [255, 255, 128, 14, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 10], 130 | [255, 255, 128, 15, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 12], 131 | [255, 255, 128, 16, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 14], 132 | [255, 255, 128, 17, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 16] 133 | ] 134 | end, 135 | Buf = [ 136 | efirebirdsql_conv:byte4(op_val(op_connect)), 137 | efirebirdsql_conv:byte4(op_val(op_attach)), 138 | efirebirdsql_conv:byte4(3), %% CONNECT_VERSION 139 | efirebirdsql_conv:byte4(1), %% arch_generic, 140 | efirebirdsql_conv:list_to_xdr_string(Database), 141 | efirebirdsql_conv:byte4(length(Protocols)), 142 | uid(Host, User, ClientPublic, AuthPlugin, WireCrypt)], 143 | list_to_binary([Buf, lists:flatten(Protocols)]). 144 | 145 | %%% create op_attach binary 146 | op_attach(Conn, Database) -> 147 | ?DEBUG_FORMAT("op_attach -> ", []), 148 | Username = Conn#conn.user, 149 | Charset = efirebirdsql_charset:get_database_charset(Conn#conn.charset), 150 | Dpb = if 151 | Conn#conn.accept_version >= 13 -> 152 | AuthData = Conn#conn.auth_data, 153 | lists:flatten([ 154 | 1, %% isc_dpb_version = 1 155 | 48, length(Charset), Charset, %% isc_dpb_lc_ctype = 48 156 | 28, length(Username), Username, %% isc_dpb_user_name 28 157 | 84, length(AuthData), AuthData %% isc_dpb_specific_auth_data = 84 158 | ]); 159 | Conn#conn.accept_version < 13 -> 160 | Password = Conn#conn.password, 161 | lists:flatten([ 162 | 1, %% isc_dpb_version = 1 163 | 48, length(Charset), Charset, %% isc_dpb_lc_ctype = 48 164 | 28, length(Username), Username, %% isc_dpb_user_name 28 165 | 29, length(Password), Password %% isc_dpb_password = 29 166 | ]) 167 | end, 168 | Dpb2 = case Conn#conn.timezone of 169 | nil -> 170 | Dpb; 171 | TimeZone -> 172 | lists:flatten([Dpb, 173 | 91, length(TimeZone), TimeZone %% isc_dpb_session_time_zone = 91 174 | ]) 175 | end, 176 | 177 | list_to_binary(lists:flatten([ 178 | efirebirdsql_conv:byte4(op_val(op_attach)), 179 | efirebirdsql_conv:byte4(0), 180 | efirebirdsql_conv:list_to_xdr_string(Database), 181 | efirebirdsql_conv:list_to_xdr_bytes(Dpb2)])). 182 | 183 | op_detach(DbHandle) -> 184 | ?DEBUG_FORMAT("op_detatch -> ", []), 185 | list_to_binary([ 186 | efirebirdsql_conv:byte4(op_val(op_detach)), 187 | efirebirdsql_conv:byte4(DbHandle)]). 188 | 189 | %%% create op_connect binary 190 | op_create(Conn, Database, PageSize) -> 191 | ?DEBUG_FORMAT("op_create -> ", []), 192 | Username = Conn#conn.user, 193 | Charset = efirebirdsql_charset:get_database_charset(Conn#conn.charset), 194 | Dpb = if 195 | Conn#conn.accept_version >= 13 -> 196 | AuthData = Conn#conn.auth_data, 197 | lists:flatten([ 198 | 1, 199 | 68, length(Charset), Charset, %% isc_dpb_set_db_charset = 68 200 | 48, length(Charset), Charset, %% isc_dpb_lc_ctype = 48 201 | 28, length(Username), Username, %% isc_dpb_user_name 28 202 | 63, 4, efirebirdsql_conv:byte4(3, little), %% isc_dpb_sql_dialect = 63 203 | 24, 4, efirebirdsql_conv:byte4(1, little), %% isc_dpb_force_write = 24 204 | 54, 4, efirebirdsql_conv:byte4(1, little), %% isc_dpb_overwrite = 54 205 | 84, length(AuthData), AuthData, %% isc_dpb_specific_auth_data = 84 206 | 4, 4, efirebirdsql_conv:byte4(PageSize, little) %% isc_dpb_page_size = 4 207 | ]); 208 | Conn#conn.accept_version < 13 -> 209 | Password = Conn#conn.password, 210 | lists:flatten([ 211 | 1, 212 | 68, length(Charset), Charset, %% isc_dpb_set_db_charset = 68 213 | 48, length(Charset), Charset, %% isc_dpb_lc_ctype = 48 214 | 28, length(Username), Username, %% isc_dpb_user_name 28 215 | 29, length(Password), Password, %% isc_dpb_password = 29 216 | 63, 4, efirebirdsql_conv:byte4(3, little), %% isc_dpb_sql_dialect = 63 217 | 24, 4, efirebirdsql_conv:byte4(1, little), %% isc_dpb_force_write = 24 218 | 54, 4, efirebirdsql_conv:byte4(1, little), %% isc_dpb_overwrite = 54 219 | 4, 4, efirebirdsql_conv:byte4(PageSize, little) %% isc_dpb_page_size = 4 220 | ]) 221 | end, 222 | Dpb2 = case Conn#conn.timezone of 223 | nil -> 224 | Dpb; 225 | TimeZone -> 226 | lists:flatten([Dpb, 227 | 91, length(TimeZone), TimeZone %% isc_dpb_session_time_zone = 91 228 | ]) 229 | end, 230 | list_to_binary(lists:flatten([ 231 | efirebirdsql_conv:byte4(op_val(op_create)), 232 | efirebirdsql_conv:byte4(0), 233 | efirebirdsql_conv:list_to_xdr_string(Database), 234 | efirebirdsql_conv:list_to_xdr_bytes(Dpb2)])). 235 | 236 | %%% database information 237 | op_info_database(DbHandle, InfoRequest) -> 238 | ?DEBUG_FORMAT("op_info_database(db_handle=~p,request=~p) -> ", [DbHandle, InfoRequest]), 239 | list_to_binary([ 240 | efirebirdsql_conv:byte4(op_val(op_info_database)), 241 | efirebirdsql_conv:byte4(DbHandle), 242 | efirebirdsql_conv:byte4(0), 243 | efirebirdsql_conv:list_to_xdr_bytes(InfoRequest), 244 | efirebirdsql_conv:byte4(?BUFSIZE)]). 245 | 246 | %%% begin transaction 247 | op_transaction(DbHandle, Tpb) -> 248 | ?DEBUG_FORMAT("op_transaction(db_handle=~p) -> ", [DbHandle]), 249 | list_to_binary([ 250 | efirebirdsql_conv:byte4(op_val(op_transaction)), 251 | efirebirdsql_conv:byte4(DbHandle), 252 | efirebirdsql_conv:list_to_xdr_bytes(Tpb)]). 253 | 254 | %%% allocate statement 255 | op_allocate_statement(DbHandle) -> 256 | ?DEBUG_FORMAT("op_allocate_statement(db_handle=~p) -> ", [DbHandle]), 257 | list_to_binary([ 258 | efirebirdsql_conv:byte4(op_val(op_allocate_statement)), 259 | efirebirdsql_conv:byte4(DbHandle)]). 260 | 261 | %%% prepare statement 262 | op_prepare_statement(TransHandle, StmtHandle, Sql) -> 263 | ?DEBUG_FORMAT("op_prepare_statement(trans_handle=~p,stmt_handle=~p,~p) -> ", [TransHandle, StmtHandle, Sql]), 264 | DescItems = [21 | ?INFO_SQL_SELECT_DESCRIBE_VARS], %% isc_info_sql_stmt_type 265 | list_to_binary([ 266 | efirebirdsql_conv:byte4(op_val(op_prepare_statement)), 267 | efirebirdsql_conv:byte4(TransHandle), 268 | efirebirdsql_conv:byte4(StmtHandle), 269 | efirebirdsql_conv:byte4(3), 270 | efirebirdsql_conv:list_to_xdr_string(binary_to_list(Sql)), 271 | efirebirdsql_conv:list_to_xdr_bytes(DescItems), 272 | efirebirdsql_conv:byte4(?BUFSIZE)]). 273 | 274 | %%% free statement 275 | op_free_statement(StmtHandle, close) -> 276 | ?DEBUG_FORMAT("op_free_statement(close, stmt_handle=~p) -> ", [StmtHandle]), 277 | %% DSQL_close = 1 278 | %% DSQL_drop = 2 279 | list_to_binary([ 280 | efirebirdsql_conv:byte4(op_val(op_free_statement)), 281 | efirebirdsql_conv:byte4(StmtHandle), 282 | efirebirdsql_conv:byte4(1)]); 283 | op_free_statement(StmtHandle, drop) -> 284 | ?DEBUG_FORMAT("op_free_statement(drop, stmt_handle=~p) -> ", [StmtHandle]), 285 | %% DSQL_close = 1 286 | %% DSQL_drop = 2 287 | list_to_binary([ 288 | efirebirdsql_conv:byte4(op_val(op_free_statement)), 289 | efirebirdsql_conv:byte4(StmtHandle), 290 | efirebirdsql_conv:byte4(2)]). 291 | 292 | op_execute(Conn, Stmt, Params) -> 293 | TransHandle = Conn#conn.trans_handle, 294 | StmtHandle = Stmt#stmt.stmt_handle, 295 | ?DEBUG_FORMAT("op_execute(db_handle=~p,trans_handle=~p,stmt_handle=~p) -> ", [Conn#conn.db_handle, TransHandle, StmtHandle]), 296 | 297 | L = if 298 | length(Params) == 0 -> 299 | [ 300 | efirebirdsql_conv:byte4(op_val(op_execute)), 301 | efirebirdsql_conv:byte4(StmtHandle), 302 | efirebirdsql_conv:byte4(TransHandle), 303 | efirebirdsql_conv:list_to_xdr_bytes([]), 304 | efirebirdsql_conv:byte4(0), 305 | efirebirdsql_conv:byte4(0) 306 | ]; 307 | length(Params) > 0 -> 308 | {Blr, Value} = efirebirdsql_conv:params_to_blr( 309 | Conn#conn.accept_version, efirebirdsql_tz_map:timezone_id_by_name(), Params), 310 | [ 311 | efirebirdsql_conv:byte4(op_val(op_execute)), 312 | efirebirdsql_conv:byte4(StmtHandle), 313 | efirebirdsql_conv:byte4(TransHandle), 314 | efirebirdsql_conv:list_to_xdr_bytes(Blr), 315 | efirebirdsql_conv:byte4(0), 316 | efirebirdsql_conv:byte4(1), 317 | Value 318 | ] 319 | end, 320 | if 321 | Conn#conn.accept_version >= 16 -> list_to_binary([L, efirebirdsql_conv:byte4(0)]); 322 | Conn#conn.accept_version < 16 -> list_to_binary(L) 323 | end. 324 | 325 | 326 | op_execute2(Conn, Stmt, Params) -> 327 | TransHandle = Conn#conn.trans_handle, 328 | StmtHandle = Stmt#stmt.stmt_handle, 329 | ?DEBUG_FORMAT("op_execute2(trans_handle=~p,stmt_handle=~p -> ", [TransHandle, StmtHandle]), 330 | XSqlVars = Stmt#stmt.xsqlvars, 331 | 332 | OutputBlr = efirebirdsql_conv:list_to_xdr_bytes(calc_blr(XSqlVars)), 333 | L = if 334 | length(Params) == 0 -> 335 | [ 336 | efirebirdsql_conv:byte4(op_val(op_execute2)), 337 | efirebirdsql_conv:byte4(StmtHandle), 338 | efirebirdsql_conv:byte4(TransHandle), 339 | efirebirdsql_conv:list_to_xdr_bytes([]), 340 | efirebirdsql_conv:byte4(0), 341 | efirebirdsql_conv:byte4(0), 342 | OutputBlr, 343 | efirebirdsql_conv:byte4(0) 344 | ]; 345 | length(Params) > 0 -> 346 | {Blr, Value} = efirebirdsql_conv:params_to_blr( 347 | Conn#conn.accept_version, efirebirdsql_tz_map:timezone_id_by_name(), Params), 348 | [ 349 | efirebirdsql_conv:byte4(op_val(op_execute2)), 350 | efirebirdsql_conv:byte4(StmtHandle), 351 | efirebirdsql_conv:byte4(TransHandle), 352 | efirebirdsql_conv:list_to_xdr_bytes(Blr), 353 | efirebirdsql_conv:byte4(0), 354 | efirebirdsql_conv:byte4(1), 355 | Value, 356 | OutputBlr, 357 | efirebirdsql_conv:byte4(0) 358 | ] 359 | end, 360 | if 361 | Conn#conn.accept_version >= 16 -> list_to_binary([L, efirebirdsql_conv:byte4(0)]); 362 | Conn#conn.accept_version < 16 -> list_to_binary(L) 363 | end. 364 | 365 | op_exec_immediate(Conn, Sql) -> 366 | DbHandle = Conn#conn.db_handle, 367 | TransHandle = Conn#conn.trans_handle, 368 | ?DEBUG_FORMAT("op_exec_immediate(db_handle=~p,trans_handle=~p,~p) -> ", [DbHandle, TransHandle, Sql]), 369 | list_to_binary([ 370 | efirebirdsql_conv:byte4(op_val(op_exec_immediate)), 371 | efirebirdsql_conv:byte4(TransHandle), 372 | efirebirdsql_conv:byte4(DbHandle), 373 | efirebirdsql_conv:byte4(3), % dialect = 3 374 | efirebirdsql_conv:list_to_xdr_string(binary_to_list(Sql)), 375 | efirebirdsql_conv:list_to_xdr_bytes([]), 376 | efirebirdsql_conv:byte4(?BUFSIZE)]). 377 | 378 | op_ping() -> 379 | ?DEBUG_FORMAT("op_ping -> ", []), 380 | list_to_binary([ 381 | efirebirdsql_conv:byte4(op_val(op_ping))]). 382 | 383 | op_info_sql(StmtHandle, V) -> 384 | ?DEBUG_FORMAT("op_info_sql(~p,~p) -> ", [StmtHandle, V]), 385 | list_to_binary([ 386 | efirebirdsql_conv:byte4(op_val(op_info_sql)), 387 | efirebirdsql_conv:byte4(StmtHandle), 388 | efirebirdsql_conv:byte4(0), 389 | efirebirdsql_conv:list_to_xdr_bytes(V), 390 | efirebirdsql_conv:byte4(?BUFSIZE)]). 391 | 392 | op_fetch(StmtHandle, XSqlVars) -> 393 | ?DEBUG_FORMAT("op_fetch(stmt_handle=~p) -> ", [StmtHandle]), 394 | list_to_binary([ 395 | efirebirdsql_conv:byte4(op_val(op_fetch)), 396 | efirebirdsql_conv:byte4(StmtHandle), 397 | efirebirdsql_conv:list_to_xdr_bytes(calc_blr(XSqlVars)), 398 | efirebirdsql_conv:byte4(0), 399 | efirebirdsql_conv:byte4(400)]). 400 | 401 | %%% commit 402 | op_commit_retaining(TransHandle) -> 403 | ?DEBUG_FORMAT("op_commit_retaining(trans_handle=~p) -> ", [TransHandle]), 404 | list_to_binary([ 405 | efirebirdsql_conv:byte4(op_val(op_commit_retaining)), 406 | efirebirdsql_conv:byte4(TransHandle)]). 407 | 408 | op_commit(TransHandle) -> 409 | ?DEBUG_FORMAT("op_commit(trans_handle=~p) -> ", [TransHandle]), 410 | list_to_binary([ 411 | efirebirdsql_conv:byte4(op_val(op_commit)), 412 | efirebirdsql_conv:byte4(TransHandle)]). 413 | 414 | %%% rollback 415 | op_rollback_retaining(TransHandle) -> 416 | ?DEBUG_FORMAT("op_rollback_retaining(trans_handle=~p) -> ", [TransHandle]), 417 | list_to_binary([ 418 | efirebirdsql_conv:byte4(op_val(op_rollback_retaining)), 419 | efirebirdsql_conv:byte4(TransHandle)]). 420 | 421 | op_rollback(TransHandle) -> 422 | ?DEBUG_FORMAT("op_rollback(trans_handle=~p) -> ", [TransHandle]), 423 | list_to_binary([ 424 | efirebirdsql_conv:byte4(op_val(op_rollback)), 425 | efirebirdsql_conv:byte4(TransHandle)]). 426 | 427 | 428 | %%% blob 429 | op_open_blob(BlobId, TransHandle) -> 430 | ?DEBUG_FORMAT("op_open_blob(trans_handle=~p,blob_id~p) -> ", [TransHandle, BlobId]), 431 | H = list_to_binary([ 432 | efirebirdsql_conv:byte4(op_val(op_open_blob)), 433 | efirebirdsql_conv:byte4(TransHandle)]), 434 | <>. 435 | 436 | op_open_blob2(BlobId, TransHandle) -> 437 | ?DEBUG_FORMAT("op_open_blob2(trans_handle=~p,blob_id~p) -> ", [TransHandle, BlobId]), 438 | H = list_to_binary([ 439 | efirebirdsql_conv:byte4(op_val(op_open_blob2)), 440 | efirebirdsql_conv:byte4(0), 441 | efirebirdsql_conv:byte4(TransHandle)]), 442 | <>. 443 | 444 | op_get_segment(BlobHandle) -> 445 | ?DEBUG_FORMAT("op_get_segment(blob_handle=~p) -> ", [BlobHandle]), 446 | list_to_binary([ 447 | efirebirdsql_conv:byte4(op_val(op_get_segment)), 448 | efirebirdsql_conv:byte4(BlobHandle), 449 | efirebirdsql_conv:byte4(?BUFSIZE), 450 | efirebirdsql_conv:byte4(0)]). 451 | 452 | op_close_blob(BlobHandle) -> 453 | ?DEBUG_FORMAT("op_close_blob(blob_handle=~p) -> ", [BlobHandle]), 454 | list_to_binary([ 455 | efirebirdsql_conv:byte4(op_val(op_close_blob)), 456 | efirebirdsql_conv:byte4(BlobHandle)]). 457 | 458 | op_cont_auth(AuthData, PluginName, PluginNameList, Keys) -> 459 | ?DEBUG_FORMAT("op_cont_auth -> ", []), 460 | list_to_binary([ 461 | efirebirdsql_conv:byte4(op_val(op_cont_auth)), 462 | efirebirdsql_conv:list_to_xdr_string(AuthData), 463 | efirebirdsql_conv:list_to_xdr_string(PluginName), 464 | efirebirdsql_conv:list_to_xdr_string(PluginNameList), 465 | efirebirdsql_conv:list_to_xdr_string(Keys)]). 466 | 467 | op_crypt(PluginName) -> 468 | ?DEBUG_FORMAT("op_crypt -> ", []), 469 | list_to_binary([ 470 | efirebirdsql_conv:byte4(op_val(op_crypt)), 471 | efirebirdsql_conv:list_to_xdr_string(PluginName), 472 | efirebirdsql_conv:list_to_xdr_string("Symmetric")]). 473 | 474 | %%% parse status vector 475 | parse_status_vector_integer(Conn) -> 476 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 477 | NumArg. 478 | 479 | parse_status_vector_string(Conn) -> 480 | Len = parse_status_vector_integer(Conn), 481 | {ok, Bin} = efirebirdsql_socket:recv_align(Conn, Len), 482 | binary_to_list(Bin). 483 | 484 | parse_status_vector_args(Conn, ErrNo, Template, Args) -> 485 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 486 | case IscArg of 487 | 0 -> %% isc_arg_end 488 | {Conn, ErrNo, Template, Args}; 489 | 1 -> %% isc_arg_gds 490 | N = parse_status_vector_integer(Conn), 491 | Msg = efirebirdsql_errmsgs:get_error_msg(N), 492 | parse_status_vector_args(Conn, N, [Msg | Template], Args); 493 | 2 -> %% isc_arg_string 494 | V = parse_status_vector_string(Conn), 495 | parse_status_vector_args(Conn, ErrNo, Template, [V | Args]); 496 | 4 -> %% isc_arg_number 497 | V = parse_status_vector_integer(Conn), 498 | parse_status_vector_args(Conn, ErrNo, Template, [integer_to_list(V) | Args]); 499 | 5 -> %% isc_arg_interpreted 500 | V = parse_status_vector_string(Conn), 501 | parse_status_vector_args(Conn, ErrNo, [V | Template], Args); 502 | 19 -> %% isc_arg_sql_state 503 | _V = parse_status_vector_string(Conn), 504 | parse_status_vector_args(Conn, ErrNo, Template, Args) 505 | end. 506 | 507 | get_error_message(Conn) -> 508 | {_Conn, ErrNo, Msg, Arg} = parse_status_vector_args(Conn, 0, [], []), 509 | {ErrNo, iolist_to_binary(io_lib:format(lists:flatten(lists:reverse(Msg)), lists:reverse(Arg)))}. 510 | 511 | %% receive and parse response 512 | -spec get_response(conn()) -> 513 | {op_response, integer(), binary()} | 514 | {op_fetch_response, integer(), integer()} | 515 | {op_sql_response, integer()} | 516 | {error, integer(), binary()}. 517 | get_response(Conn) -> 518 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 519 | Op = op_name(OpCode), 520 | ?DEBUG_FORMAT("get_response() ~p", [Op]), 521 | case Op of 522 | op_response -> 523 | {ok, <>} = efirebirdsql_socket:recv(Conn, 16), 524 | ?DEBUG_FORMAT("=~p~n", [Handle]), 525 | Buf = if 526 | Len =/= 0 -> 527 | {ok, RecvBuf} = efirebirdsql_socket:recv_align(Conn, Len), 528 | RecvBuf; 529 | true -> 530 | <<>> 531 | end, 532 | {ErrNo, Msg} = get_error_message(Conn), 533 | case Msg of 534 | <<>> -> {Op, Handle, Buf}; 535 | _ -> {error, ErrNo, Msg} 536 | end; 537 | op_fetch_response -> 538 | {ok, <>} = efirebirdsql_socket:recv(Conn, 8), 539 | ?DEBUG_FORMAT(" status=~p,count=~p~n", [Status, Count]), 540 | {Op, Status, Count}; 541 | op_sql_response -> 542 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 543 | ?DEBUG_FORMAT(" count=~p~n", [Count]), 544 | {Op, Count}; 545 | op_dummy -> 546 | ?DEBUG_FORMAT("~n", []), 547 | get_response(Conn); 548 | _ -> 549 | {error, 0, <<"Unknown response">>} 550 | end. 551 | 552 | %% guess wirecrypt plugin 553 | 554 | split_plugin_names([]) -> 555 | [[]]; 556 | split_plugin_names(List) -> 557 | split_plugin_names(List, []). 558 | 559 | split_plugin_names([], Acc) -> 560 | [lists:reverse(Acc)]; 561 | split_plugin_names([32 | Rest], Acc) -> 562 | [lists:reverse(Acc) | split_plugin_names(Rest, [])]; 563 | split_plugin_names([Head | Rest], Acc) -> 564 | split_plugin_names(Rest, [Head | Acc]). 565 | 566 | plugin_names(List, []) -> 567 | List; 568 | plugin_names(List, Buf) -> 569 | [K | Rest1] = Buf, 570 | [Ln | Rest2] = Rest1, 571 | {V, Rest3} = lists:split(Ln, Rest2), 572 | if K =:= 1 -> 573 | plugin_names([V | List], Rest3); 574 | K =/= 1 -> 575 | plugin_names(List, Rest3) 576 | end. 577 | plugin_names(Buf) -> 578 | PluginNamesList = plugin_names([], Buf), 579 | [PluginNames | _] = PluginNamesList, 580 | split_plugin_names(PluginNames). 581 | 582 | plugin_nonce_list(List, []) -> 583 | List; 584 | plugin_nonce_list(List, Buf) -> 585 | [K | Rest1] = Buf, 586 | [Ln | Rest2] = Rest1, 587 | {V, Rest3} = lists:split(Ln, Rest2), 588 | if K =:= 3 -> 589 | plugin_nonce_list([V | List], Rest3); 590 | K =/= 3 -> 591 | plugin_nonce_list(List, Rest3) 592 | end. 593 | plugin_nonce_list(Buf) -> 594 | plugin_nonce_list([], Buf). 595 | 596 | is_nonce_prefix([], _List) -> true; 597 | is_nonce_prefix([H1 | T1], [H2 | T2]) when H1 =:= H2 -> is_nonce_prefix(T1, T2); 598 | is_nonce_prefix(_, _) -> false. 599 | 600 | find_nonce(Prefix, NonceList) -> 601 | case lists:dropwhile(fun(List) -> not is_nonce_prefix(Prefix, List) end, NonceList) of 602 | [] -> nil; 603 | [Match | _] -> lists:sublist(Match, 8, 12) 604 | end. 605 | 606 | guess_wire_crypt(Buf) -> 607 | AvailablePlugins = plugin_names(binary_to_list(Buf)), 608 | PluginNonceList = plugin_nonce_list(binary_to_list(Buf)), 609 | HasChaChaPlugin = lists:member("ChaCha", AvailablePlugins), 610 | HasArc4Plugin = lists:member("Arc4", AvailablePlugins), 611 | if 612 | HasChaChaPlugin =:= true -> 613 | {"ChaCha", find_nonce(string:concat("ChaCha", [0]), PluginNonceList)}; 614 | HasArc4Plugin =:= true -> 615 | {"Arc4", nil}; 616 | true -> 617 | {nil, nil} 618 | end. 619 | 620 | 621 | %% wire encryption 622 | 623 | wire_crypt(Conn, EncryptPlugin, SessionKey, IV) -> 624 | efirebirdsql_socket:send(Conn, op_crypt(EncryptPlugin)), 625 | C2 = case EncryptPlugin of 626 | "Arc4" -> 627 | Conn#conn{ 628 | read_state=crypto:crypto_init(rc4, SessionKey, false), 629 | write_state=crypto:crypto_init(rc4, SessionKey, true) 630 | }; 631 | "ChaCha" -> 632 | Key = crypto:hash(sha256, SessionKey), 633 | Conn#conn{ 634 | read_state=crypto:crypto_init(chacha20, Key, string:concat([0, 0, 0, 0], IV), false), 635 | write_state=crypto:crypto_init(chacha20, Key, string:concat([0, 0, 0, 0], IV), true) 636 | } 637 | end, 638 | {op_response, _, _} = get_response(C2), 639 | C2. 640 | 641 | get_auth_data([], PluginName, Conn) -> 642 | AuthData = efirebirdsql_srp:to_hex(Conn#conn.client_public), 643 | efirebirdsql_socket:send(Conn, 644 | op_cont_auth(AuthData, binary_to_list(PluginName), Conn#conn.auth_plugin, "")), 645 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 646 | op_cont_auth = op_name(OpCode), 647 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 648 | {ok, Data} = efirebirdsql_socket:recv_align(Conn, Len1), 649 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 650 | {ok, _PluginName} = efirebirdsql_socket:recv_align(Conn, Len2), 651 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 652 | {ok, _PluginList} = efirebirdsql_socket:recv_align(Conn, Len3), 653 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 654 | {ok, _Keys} = efirebirdsql_socket:recv_align(Conn, Len4), 655 | Data; 656 | get_auth_data(Data, _PluginName, _Conn) -> 657 | Data. 658 | 659 | %% receive and parse connect() response 660 | get_connect_response(op_accept, Conn) -> 661 | {ok, <<_AcceptVersionMasks:24, AcceptVersion:8, 662 | _AcceptArchtecture:32, _AcceptType:32>>} = efirebirdsql_socket:recv(Conn, 12), 663 | {ok, Conn#conn{accept_version=AcceptVersion}}; 664 | get_connect_response(Op, Conn) -> 665 | {ok, <<_AcceptVersionMasks:24, AcceptVersion:8, 666 | _AcceptArchtecture:32, _AcceptType:32>>} = efirebirdsql_socket:recv(Conn, 12), 667 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 668 | {ok, Data} = efirebirdsql_socket:recv_align(Conn, Len1), 669 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 670 | {ok, PluginName} = efirebirdsql_socket:recv_align(Conn, Len2), 671 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 672 | {ok, <<_:32>>} = efirebirdsql_socket:recv(Conn, 4), 673 | if IsAuthenticated == 0 -> 674 | ServerAuthData = get_auth_data(Data, PluginName, Conn), 675 | case binary_to_list(PluginName) of 676 | "Srp" -> 677 | <> = ServerAuthData, 678 | ServerPublic = binary_to_integer(Bin, 16), 679 | {AuthData, SessionKey} = efirebirdsql_srp:client_proof( 680 | Conn#conn.user, Conn#conn.password, Salt, 681 | Conn#conn.client_public, ServerPublic, Conn#conn.client_private, sha); 682 | "Srp256" -> 683 | <> = ServerAuthData, 684 | ServerPublic = binary_to_integer(Bin, 16), 685 | {AuthData, SessionKey} = efirebirdsql_srp:client_proof( 686 | Conn#conn.user, Conn#conn.password, Salt, 687 | Conn#conn.client_public, ServerPublic, Conn#conn.client_private, sha256); 688 | _ -> 689 | AuthData = nil, 690 | SessionKey = nil 691 | end; 692 | true -> 693 | AuthData = nil, 694 | SessionKey = nil 695 | end, 696 | C2 = Conn#conn{accept_version=AcceptVersion, auth_data=efirebirdsql_srp:to_hex(AuthData)}, 697 | case Op of 698 | op_cond_accept -> 699 | efirebirdsql_socket:send(C2, 700 | op_cont_auth(C2#conn.auth_data, C2#conn.auth_plugin, C2#conn.auth_plugin, "")), 701 | {op_response, _, Buf} = get_response(C2), 702 | {EncryptPlugin, IV} = guess_wire_crypt(Buf), 703 | NewConn = case (EncryptPlugin =/= nil) and (C2#conn.wire_crypt =:= true) and (SessionKey =/= nil) of 704 | true -> wire_crypt(C2, EncryptPlugin, SessionKey, IV); 705 | false -> C2 706 | end; 707 | _ -> 708 | NewConn = C2 709 | end, 710 | {ok, NewConn}. 711 | 712 | -spec get_connect_response(conn()) -> {ok, conn()} | {error, binary(), conn()}. 713 | get_connect_response(Conn) -> 714 | ?DEBUG_FORMAT("get_connect_response()~n", []), 715 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4), 716 | Op = op_name(OpCode), 717 | if 718 | (Op == op_accept) or (Op == op_cond_accept) or (Op == op_accept_data) -> 719 | get_connect_response(Op, Conn); 720 | Op == op_dummy -> 721 | get_connect_response(Conn); 722 | Op == op_response -> 723 | {ok, <<_Handle:32, _ObjectID:64, _Len:32>>} = efirebirdsql_socket:recv(Conn, 16), 724 | {ErrNo, Msg} = get_error_message(Conn), 725 | {error, ErrNo, Msg, Conn}; 726 | Op == op_reject -> 727 | {error, <<"Connect rejected">>, Conn}; 728 | true -> 729 | {error, <<"Unknown connect error">>, Conn} 730 | end. 731 | 732 | %% parse select items. 733 | more_select_describe_vars(Conn, Stmt, Start) -> 734 | %% isc_info_sql_sqlda_start + INFO_SQL_SELECT_DESCRIBE_VARS 735 | V = lists:flatten([20, 2, Start rem 256, Start div 256, ?INFO_SQL_SELECT_DESCRIBE_VARS]), 736 | efirebirdsql_socket:send(Conn, op_info_sql(Stmt#stmt.stmt_handle, V)), 737 | {op_response, _, Buf} = get_response(Conn), 738 | <<_:8/binary, DescVars/binary>> = Buf, 739 | DescVars. 740 | 741 | parse_select_item_elem_binary(DescVars) -> 742 | <> = DescVars, 743 | {V, Rest}. 744 | 745 | parse_select_item_elem_int(DescVars) -> 746 | {V, Rest} = parse_select_item_elem_binary(DescVars), 747 | L = size(V) * 8, 748 | <> = V, 749 | {Num, Rest}. 750 | 751 | parse_select_column(Conn, Stmt, Column, DescVars, NextIndex) -> 752 | %% Parse DescVars and return items info and rest DescVars 753 | <> = DescVars, 754 | case isc_info_sql_name(IscInfoNum) of 755 | isc_info_sql_sqlda_seq -> 756 | {Num, Rest2} = parse_select_item_elem_int(Rest), 757 | parse_select_column(Conn, Stmt, Column#column{seq=Num}, Rest2, Num); 758 | isc_info_sql_type -> 759 | {Num, Rest2} = parse_select_item_elem_int(Rest), 760 | parse_select_column(Conn, Stmt, Column#column{type=sql_type(Num)}, Rest2, NextIndex); 761 | isc_info_sql_sub_type -> 762 | {_Num, Rest2} = parse_select_item_elem_int(Rest), 763 | parse_select_column(Conn, Stmt, Column, Rest2, NextIndex); 764 | isc_info_sql_scale -> 765 | {Num, Rest2} = parse_select_item_elem_int(Rest), 766 | parse_select_column(Conn, Stmt, Column#column{scale=Num}, Rest2, NextIndex); 767 | isc_info_sql_length -> 768 | {Num, Rest2} = parse_select_item_elem_int(Rest), 769 | parse_select_column(Conn, Stmt, Column#column{length=Num}, Rest2, NextIndex); 770 | isc_info_sql_null_ind -> 771 | {Num, Rest2} = parse_select_item_elem_int(Rest), 772 | NullInd = if Num =/= 0 -> true; Num =:= 0 -> false end, 773 | parse_select_column(Conn, Stmt, Column#column{null_ind=NullInd}, Rest2, NextIndex); 774 | isc_info_sql_field -> 775 | {_S, Rest2} = parse_select_item_elem_binary(Rest), 776 | parse_select_column(Conn, Stmt, Column, Rest2, NextIndex); 777 | isc_info_sql_relation -> 778 | {_S, Rest2} = parse_select_item_elem_binary(Rest), 779 | parse_select_column(Conn, Stmt, Column, Rest2, NextIndex); 780 | isc_info_sql_owner -> 781 | {_S, Rest2} = parse_select_item_elem_binary(Rest), 782 | parse_select_column(Conn, Stmt, Column, Rest2, NextIndex); 783 | isc_info_sql_alias -> 784 | {S, Rest2} = parse_select_item_elem_binary(Rest), 785 | parse_select_column(Conn, Stmt, Column#column{name=S}, Rest2, NextIndex); 786 | isc_info_truncated -> 787 | Rest2 = more_select_describe_vars(Conn, Stmt, NextIndex), 788 | parse_select_column(Conn, Stmt, Column, Rest2, NextIndex); 789 | isc_info_sql_describe_end -> 790 | {Conn, Column, Rest, Column#column.seq+1}; 791 | isc_info_end -> 792 | {Conn, no_more_column} 793 | end. 794 | 795 | parse_select_columns(Conn, Stmt, XSqlVars, DescVars, NextIndex) -> 796 | case parse_select_column(Conn, Stmt, #column{}, DescVars, NextIndex) of 797 | {C2, XSqlVar, Rest, NextIndex2} -> 798 | parse_select_columns(C2, Stmt, [XSqlVar | XSqlVars], Rest, NextIndex2); 799 | {C2, no_more_column} -> 800 | {C2, lists:reverse(XSqlVars)} 801 | end. 802 | 803 | -spec get_prepare_statement_response(conn(), stmt()) -> {ok, stmt()} | {error, integer(), binary()}. 804 | get_prepare_statement_response(Conn, Stmt) -> 805 | case get_response(Conn) of 806 | {op_response, _, Buf} -> 807 | << _21:8, _Len:16, StmtType:32/little, Rest/binary>> = Buf, 808 | StmtName = isc_info_sql_stmt_name(StmtType), 809 | Stmt2 = Stmt#stmt{stmt_type=StmtName}, 810 | {_, XSqlVars} = case StmtName of 811 | isc_info_sql_stmt_select -> 812 | << _Skip:8/binary, DescVars/binary >> = Rest, 813 | parse_select_columns(Conn, Stmt2, [], DescVars, 1); 814 | isc_info_sql_stmt_exec_procedure -> 815 | << _Skip:8/binary, DescVars/binary >> = Rest, 816 | parse_select_columns(Conn, Stmt2, [], DescVars, 1); 817 | _ -> {Conn, []} 818 | end, 819 | {ok, Stmt2#stmt{xsqlvars=XSqlVars, rows=[]}}; 820 | {error, ErrNo, Msg} -> {error, ErrNo, Msg} 821 | end. 822 | 823 | get_blob_segment_list(<<>>, SegmentList) -> 824 | lists:reverse(SegmentList); 825 | get_blob_segment_list(Buf, SegmentList) -> 826 | <> = Buf, 827 | get_blob_segment_list(Rest, [V| SegmentList]). 828 | 829 | get_blob_segment(Conn, BlobHandle, SegmentList) -> 830 | efirebirdsql_socket:send(Conn, op_get_segment(BlobHandle)), 831 | {op_response, F, Buf} = get_response(Conn), 832 | NewList = lists:flatten([SegmentList, get_blob_segment_list(Buf, [])]), 833 | case F of 834 | 2 -> NewList; 835 | _ -> get_blob_segment(Conn, BlobHandle, NewList) 836 | end. 837 | 838 | get_blob_data(Conn, BlobId) -> 839 | efirebirdsql_socket:send(Conn, op_open_blob2(BlobId, Conn#conn.trans_handle)), 840 | {op_response, BlobHandle, _} = get_response(Conn), 841 | SegmentList = get_blob_segment(Conn, BlobHandle, []), 842 | efirebirdsql_socket:send(Conn, op_close_blob(BlobHandle)), 843 | {op_response, 0, _} = get_response(Conn), 844 | R = list_to_binary(SegmentList), 845 | {ok, R}. 846 | 847 | convert_raw_value(_Conn, _XSqlVar, nil) -> 848 | nil; 849 | convert_raw_value(Conn, XSqlVar, RawValue) -> 850 | CookedValue = case XSqlVar#column.type of 851 | long -> 852 | efirebirdsql_conv:parse_number(RawValue, XSqlVar#column.scale); 853 | short -> 854 | efirebirdsql_conv:parse_number(RawValue, XSqlVar#column.scale); 855 | int64 -> 856 | efirebirdsql_conv:parse_number(RawValue, XSqlVar#column.scale); 857 | int128 -> 858 | efirebirdsql_conv:parse_number(RawValue, XSqlVar#column.scale); 859 | quad -> 860 | efirebirdsql_conv:parse_number(RawValue, XSqlVar#column.scale); 861 | double -> 862 | L = size(RawValue) * 8, <> = RawValue, V; 863 | float -> 864 | L = size(RawValue) * 8, <> = RawValue, V; 865 | date -> 866 | efirebirdsql_conv:parse_date(RawValue); 867 | time -> 868 | efirebirdsql_conv:parse_time(RawValue); 869 | timestamp -> 870 | efirebirdsql_conv:parse_timestamp(RawValue); 871 | time_tz -> 872 | efirebirdsql_conv:parse_time_tz(RawValue); 873 | timestamp_tz -> 874 | efirebirdsql_conv:parse_timestamp_tz(RawValue); 875 | decimal_fixed -> 876 | efirebirdsql_decfloat:decimal_fixed_to_decimal(RawValue, XSqlVar#column.scale); 877 | decimal64 -> 878 | efirebirdsql_decfloat:decimal64_to_decimal(RawValue); 879 | decimal128 -> 880 | efirebirdsql_decfloat:decimal128_to_decimal(RawValue); 881 | blob -> 882 | {ok, B} = get_blob_data(Conn, RawValue), 883 | B; 884 | boolean -> 885 | if RawValue =/= <<0,0,0,0>> -> true; true -> false end; 886 | text -> 887 | list_to_binary(lists:reverse(lists:dropwhile(fun(32) -> true; (_) -> false end, lists:reverse(binary_to_list(RawValue))))); 888 | _ -> 889 | RawValue 890 | end, 891 | CookedValue. 892 | 893 | convert_row(_Conn, [], [], Converted) -> 894 | ?DEBUG_FORMAT("convert_row() ~p~n", [Converted]), 895 | lists:reverse(Converted); 896 | convert_row(Conn, XSqlVars, Row, Converted) -> 897 | [X | XRest] = XSqlVars, 898 | [R | RRest] = Row, 899 | V = convert_raw_value(Conn, X, R), 900 | convert_row(Conn, XRest, RRest, [{X#column.name, V} | Converted]). 901 | 902 | convert_row(Conn, XSqlVars, Row) -> 903 | convert_row(Conn, XSqlVars, Row, []). 904 | 905 | get_raw_value(Conn, XSqlVar) -> 906 | if 907 | XSqlVar#column.type =:= varying -> 908 | {ok, <>} = efirebirdsql_socket:recv(Conn, 4); 909 | true -> 910 | L = case XSqlVar#column.type of 911 | text -> XSqlVar#column.length; 912 | long -> 4; 913 | short -> 4; 914 | int64 -> 8; 915 | int128 -> 16; 916 | quad -> 8; 917 | double -> 8; 918 | float -> 4; 919 | date -> 4; 920 | time -> 4; 921 | timestamp -> 8; 922 | time_tz -> 8; 923 | timestamp_tz -> 12; 924 | decimal_fixed -> 16; 925 | decimal64 -> 8; 926 | decimal128 -> 16; 927 | blob -> 8; 928 | array -> 8; 929 | boolean -> 1 930 | end 931 | end, 932 | if 933 | L =:= 0 -> 934 | <<"">>; 935 | L > 0 -> 936 | {ok, V} = efirebirdsql_socket:recv_align(Conn, L), 937 | V 938 | end. 939 | 940 | get_raw_or_null_value(Conn, XSqlVar) -> 941 | V = get_raw_value(Conn, XSqlVar), 942 | {ok, NullFlag} = efirebirdsql_socket:recv(Conn, 4), 943 | case NullFlag of 944 | <<0,0,0,0>> -> {V, Conn}; 945 | _ -> {nil, Conn} 946 | end. 947 | 948 | get_row(Conn, [], Columns, _NullBitmap, _Idx) -> 949 | {lists:reverse(Columns), Conn}; 950 | get_row(Conn, XSqlVars, Columns, NullBitmap, Idx) -> 951 | [X | RestVars] = XSqlVars, 952 | if NullBitmap band (1 bsl Idx) =/= 0 -> 953 | V = nil; 954 | true -> 955 | V = get_raw_value(Conn, X) 956 | end, 957 | get_row(Conn, RestVars, [V | Columns], NullBitmap, Idx + 1). 958 | 959 | get_row(Conn, [], Columns) -> 960 | {lists:reverse(Columns), Conn}; 961 | get_row(Conn, XSqlVars, Columns) -> 962 | [X | RestVars] = XSqlVars, 963 | {V, C2} = get_raw_or_null_value(Conn, X), 964 | get_row(C2, RestVars, [V | Columns]). 965 | 966 | get_fetch_response(_Conn, _Stmt, Status, 0, _XSqlVars, Results) -> 967 | %% {list_of_response, more_data} 968 | {lists:reverse(Results), if Status =/= 100 -> true; Status =:= 100 -> false end}; 969 | get_fetch_response(Conn, Stmt, _Status, _Count, XSqlVars, Results) -> 970 | {Row, S3} = if 971 | Conn#conn.accept_version >= 13 -> 972 | NullBitmap = efirebirdsql_socket:recv_null_bitmap(Conn, length(Stmt#stmt.xsqlvars)), 973 | get_row(Conn, XSqlVars, [], NullBitmap, 0); 974 | Conn#conn.accept_version < 13 -> 975 | get_row(Conn, XSqlVars, []) 976 | end, 977 | NewResults = [Row | Results], 978 | {ok, <<_:32, NewStatus:32, NewCount:32>>} = efirebirdsql_socket:recv(S3, 12), 979 | get_fetch_response(Conn, Stmt, NewStatus, NewCount, XSqlVars, NewResults). 980 | 981 | -spec get_fetch_response(conn(), stmt()) -> {ok, list(), boolean()}. 982 | get_fetch_response(Conn, Stmt) -> 983 | {op_fetch_response, Status, Count} = get_response(Conn), 984 | {Results, MoreData} = get_fetch_response(Conn, Stmt, Status, Count, Stmt#stmt.xsqlvars, []), 985 | {ok, Results, MoreData}. 986 | 987 | get_sql_response(Conn, Stmt) -> 988 | {op_sql_response, Count} = get_response(Conn), 989 | case Count of 990 | 0 -> 991 | {[], conn}; 992 | _ -> 993 | if 994 | Conn#conn.accept_version >= 13 -> 995 | NullBitmap = efirebirdsql_socket:recv_null_bitmap(Conn, length(Stmt#stmt.xsqlvars)), 996 | get_row(Conn, Stmt#stmt.xsqlvars, [], NullBitmap, 0); 997 | Conn#conn.accept_version < 13 -> 998 | get_row(Conn, Stmt#stmt.xsqlvars, []) 999 | end 1000 | end. 1001 | 1002 | op_name(1) -> op_connect; 1003 | op_name(2) -> op_exit; 1004 | op_name(3) -> op_accept; 1005 | op_name(4) -> op_reject; 1006 | op_name(5) -> op_protocol; 1007 | op_name(6) -> op_disconnect; 1008 | op_name(9) -> op_response; 1009 | op_name(19) -> op_attach; 1010 | op_name(20) -> op_create; 1011 | op_name(21) -> op_detach; 1012 | op_name(29) -> op_transaction; 1013 | op_name(30) -> op_commit; 1014 | op_name(31) -> op_rollback; 1015 | op_name(35) -> op_open_blob; 1016 | op_name(36) -> op_get_segment; 1017 | op_name(37) -> op_put_segment; 1018 | op_name(39) -> op_close_blob; 1019 | op_name(40) -> op_info_database; 1020 | op_name(42) -> op_info_transaction; 1021 | op_name(44) -> op_batch_segments; 1022 | op_name(48) -> op_que_events; 1023 | op_name(49) -> op_cancel_events; 1024 | op_name(50) -> op_commit_retaining; 1025 | op_name(52) -> op_event; 1026 | op_name(53) -> op_connect_request; 1027 | op_name(56) -> op_open_blob2; 1028 | op_name(57) -> op_create_blob2; 1029 | op_name(62) -> op_allocate_statement; 1030 | op_name(63) -> op_execute; 1031 | op_name(64) -> op_exec_immediate; 1032 | op_name(65) -> op_fetch; 1033 | op_name(66) -> op_fetch_response; 1034 | op_name(67) -> op_free_statement; 1035 | op_name(68) -> op_prepare_statement; 1036 | op_name(70) -> op_info_sql; 1037 | op_name(71) -> op_dummy; 1038 | op_name(76) -> op_execute2; 1039 | op_name(78) -> op_sql_response; 1040 | op_name(81) -> op_drop_database; 1041 | op_name(82) -> op_service_attach; 1042 | op_name(83) -> op_service_detach; 1043 | op_name(84) -> op_service_info; 1044 | op_name(85) -> op_service_start; 1045 | op_name(86) -> op_rollback_retaining; 1046 | %% FB3 1047 | op_name(87) -> op_update_account_info; 1048 | op_name(88) -> op_authenticate_user; 1049 | op_name(89) -> op_partial; 1050 | op_name(90) -> op_trusted_auth; 1051 | op_name(91) -> op_cancel; 1052 | op_name(92) -> op_cont_auth; 1053 | op_name(93) -> op_ping; 1054 | op_name(94) -> op_accept_data; 1055 | op_name(95) -> op_abort_aux_connection; 1056 | op_name(96) -> op_crypt; 1057 | op_name(97) -> op_crypt_key_callback; 1058 | op_name(98) -> op_cond_accept. 1059 | 1060 | -spec op_val(atom()) -> integer(). 1061 | op_val(op_connect) -> 1; 1062 | op_val(op_exit) -> 2; 1063 | op_val(op_accept) -> 3; 1064 | op_val(op_reject) -> 4; 1065 | op_val(op_protocol) -> 5; 1066 | op_val(op_disconnect) -> 6; 1067 | op_val(op_response) -> 9; 1068 | op_val(op_attach) -> 19; 1069 | op_val(op_create) -> 20; 1070 | op_val(op_detach) -> 21; 1071 | op_val(op_transaction) -> 29; 1072 | op_val(op_commit) -> 30; 1073 | op_val(op_rollback) -> 31; 1074 | op_val(op_open_blob) -> 35; 1075 | op_val(op_get_segment) -> 36; 1076 | op_val(op_put_segment) -> 37; 1077 | op_val(op_close_blob) -> 39; 1078 | op_val(op_info_database) -> 40; 1079 | op_val(op_info_transaction) -> 42; 1080 | op_val(op_batch_segments) -> 44; 1081 | op_val(op_que_events) -> 48; 1082 | op_val(op_cancel_events) -> 49; 1083 | op_val(op_commit_retaining) -> 50; 1084 | op_val(op_event) -> 52; 1085 | op_val(op_connect_request) -> 53; 1086 | op_val(op_open_blob2) -> 56; 1087 | op_val(op_create_blob2) -> 57; 1088 | op_val(op_allocate_statement) -> 62; 1089 | op_val(op_execute) -> 63; 1090 | op_val(op_exec_immediate) -> 64; 1091 | op_val(op_fetch) -> 65; 1092 | op_val(op_fetch_response) -> 66; 1093 | op_val(op_free_statement) -> 67; 1094 | op_val(op_prepare_statement) -> 68; 1095 | op_val(op_info_sql) -> 70; 1096 | op_val(op_dummy) -> 71; 1097 | op_val(op_execute2) -> 76; 1098 | op_val(op_sql_response) -> 78; 1099 | op_val(op_drop_database) -> 81; 1100 | op_val(op_service_attach) -> 82; 1101 | op_val(op_service_detach) -> 83; 1102 | op_val(op_service_info) -> 84; 1103 | op_val(op_service_start) -> 85; 1104 | op_val(op_rollback_retaining) -> 86; 1105 | %% FB3 1106 | op_val(op_update_account_info) -> 87; 1107 | op_val(op_authenticate_user) -> 88; 1108 | op_val(op_partial) -> 89; 1109 | op_val(op_trusted_auth) -> 90; 1110 | op_val(op_cancel) -> 91; 1111 | op_val(op_cont_auth) -> 92; 1112 | op_val(op_ping) -> 93; 1113 | op_val(op_accept_data) -> 94; 1114 | op_val(op_abort_aux_connection) -> 95; 1115 | op_val(op_crypt) -> 96; 1116 | op_val(op_crypt_key_callback) -> 97; 1117 | op_val(op_cond_accept) -> 98. 1118 | 1119 | sql_type(X) when X rem 2 == 1 -> sql_type(X band 16#FFFE); 1120 | sql_type(452) -> text; 1121 | sql_type(448) -> varying; 1122 | sql_type(500) -> short; 1123 | sql_type(496) -> long; 1124 | sql_type(482) -> float; 1125 | sql_type(480) -> double; 1126 | sql_type(530) -> d_float; 1127 | sql_type(510) -> timestamp; 1128 | sql_type(520) -> blob; 1129 | sql_type(540) -> array; 1130 | sql_type(550) -> quad; 1131 | sql_type(560) -> time; 1132 | sql_type(570) -> date; 1133 | sql_type(580) -> int64; 1134 | sql_type(32752) -> int128; 1135 | sql_type(32754) -> timestamp_tz; 1136 | sql_type(32756) -> time_tz; 1137 | sql_type(32758) -> decimal_fixed; %% OBSOLATED 1138 | sql_type(32760) -> decimal64; 1139 | sql_type(32762) -> decimal128; 1140 | sql_type(32764) -> boolean; 1141 | sql_type(32766) -> nil. 1142 | 1143 | isc_info_sql_name(Num) -> 1144 | lists:nth(Num, [ 1145 | isc_info_end, isc_info_truncated, isc_info_error, isc_info_sql_select, 1146 | isc_info_sql_bind, isc_info_sql_num_variables, isc_info_sql_describe_vars, 1147 | isc_info_sql_describe_end, isc_info_sql_sqlda_seq, isc_info_sql_message_seq, 1148 | isc_info_sql_type, isc_info_sql_sub_type, isc_info_sql_scale, 1149 | isc_info_sql_length, isc_info_sql_null_ind, isc_info_sql_field, 1150 | isc_info_sql_relation, isc_info_sql_owner, isc_info_sql_alias, 1151 | isc_info_sql_sqlda_start, isc_info_sql_stmt_type, isc_info_sql_get_plan, 1152 | isc_info_sql_records, isc_info_sql_batch_fetch, 1153 | isc_info_sql_relation_alias]). 1154 | 1155 | isc_info_sql_stmt_name(Num) -> 1156 | lists:nth(Num, [ 1157 | isc_info_sql_stmt_select, isc_info_sql_stmt_insert, 1158 | isc_info_sql_stmt_update, isc_info_sql_stmt_delete, isc_info_sql_stmt_ddl, 1159 | isc_info_sql_stmt_get_segment, isc_info_sql_stmt_put_segment, 1160 | isc_info_sql_stmt_exec_procedure, isc_info_sql_stmt_start_trans, 1161 | isc_info_sql_stmt_commit, isc_info_sql_stmt_rollback, 1162 | isc_info_sql_stmt_select_for_upd, isc_info_sql_stmt_set_generator, 1163 | isc_info_sql_stmt_savepoint]). 1164 | 1165 | -------------------------------------------------------------------------------- /src/efirebirdsql_tz_map.erl: -------------------------------------------------------------------------------- 1 | %%% The MIT License (MIT) 2 | %%% Copyright (c) 2022 Hajime Nakagami 3 | 4 | -module(efirebirdsql_tz_map). 5 | 6 | -export([timezone_id_by_name/0, timezone_name_by_id/0]). 7 | 8 | -include("efirebirdsql.hrl"). 9 | 10 | 11 | timezone_name_by_id() -> 12 | #{ 13 | 65535 => <<"GMT">>, 14 | 65534 => <<"ACT">>, 15 | 65533 => <<"AET">>, 16 | 65532 => <<"AGT">>, 17 | 65531 => <<"ART">>, 18 | 65530 => <<"AST">>, 19 | 65529 => <<"Africa/Abidjan">>, 20 | 65528 => <<"Africa/Accra">>, 21 | 65527 => <<"Africa/Addis_Ababa">>, 22 | 65526 => <<"Africa/Algiers">>, 23 | 65525 => <<"Africa/Asmara">>, 24 | 65524 => <<"Africa/Asmera">>, 25 | 65523 => <<"Africa/Bamako">>, 26 | 65522 => <<"Africa/Bangui">>, 27 | 65521 => <<"Africa/Banjul">>, 28 | 65520 => <<"Africa/Bissau">>, 29 | 65519 => <<"Africa/Blantyre">>, 30 | 65518 => <<"Africa/Brazzaville">>, 31 | 65517 => <<"Africa/Bujumbura">>, 32 | 65516 => <<"Africa/Cairo">>, 33 | 65515 => <<"Africa/Casablanca">>, 34 | 65514 => <<"Africa/Ceuta">>, 35 | 65513 => <<"Africa/Conakry">>, 36 | 65512 => <<"Africa/Dakar">>, 37 | 65511 => <<"Africa/Dar_es_Salaam">>, 38 | 65510 => <<"Africa/Djibouti">>, 39 | 65509 => <<"Africa/Douala">>, 40 | 65508 => <<"Africa/El_Aaiun">>, 41 | 65507 => <<"Africa/Freetown">>, 42 | 65506 => <<"Africa/Gaborone">>, 43 | 65505 => <<"Africa/Harare">>, 44 | 65504 => <<"Africa/Johannesburg">>, 45 | 65503 => <<"Africa/Juba">>, 46 | 65502 => <<"Africa/Kampala">>, 47 | 65501 => <<"Africa/Khartoum">>, 48 | 65500 => <<"Africa/Kigali">>, 49 | 65499 => <<"Africa/Kinshasa">>, 50 | 65498 => <<"Africa/Lagos">>, 51 | 65497 => <<"Africa/Libreville">>, 52 | 65496 => <<"Africa/Lome">>, 53 | 65495 => <<"Africa/Luanda">>, 54 | 65494 => <<"Africa/Lubumbashi">>, 55 | 65493 => <<"Africa/Lusaka">>, 56 | 65492 => <<"Africa/Malabo">>, 57 | 65491 => <<"Africa/Maputo">>, 58 | 65490 => <<"Africa/Maseru">>, 59 | 65489 => <<"Africa/Mbabane">>, 60 | 65488 => <<"Africa/Mogadishu">>, 61 | 65487 => <<"Africa/Monrovia">>, 62 | 65486 => <<"Africa/Nairobi">>, 63 | 65485 => <<"Africa/Ndjamena">>, 64 | 65484 => <<"Africa/Niamey">>, 65 | 65483 => <<"Africa/Nouakchott">>, 66 | 65482 => <<"Africa/Ouagadougou">>, 67 | 65481 => <<"Africa/Porto-Novo">>, 68 | 65480 => <<"Africa/Sao_Tome">>, 69 | 65479 => <<"Africa/Timbuktu">>, 70 | 65478 => <<"Africa/Tripoli">>, 71 | 65477 => <<"Africa/Tunis">>, 72 | 65476 => <<"Africa/Windhoek">>, 73 | 65475 => <<"America/Adak">>, 74 | 65474 => <<"America/Anchorage">>, 75 | 65473 => <<"America/Anguilla">>, 76 | 65472 => <<"America/Antigua">>, 77 | 65471 => <<"America/Araguaina">>, 78 | 65470 => <<"America/Argentina/Buenos_Aires">>, 79 | 65469 => <<"America/Argentina/Catamarca">>, 80 | 65468 => <<"America/Argentina/ComodRivadavia">>, 81 | 65467 => <<"America/Argentina/Cordoba">>, 82 | 65466 => <<"America/Argentina/Jujuy">>, 83 | 65465 => <<"America/Argentina/La_Rioja">>, 84 | 65464 => <<"America/Argentina/Mendoza">>, 85 | 65463 => <<"America/Argentina/Rio_Gallegos">>, 86 | 65462 => <<"America/Argentina/Salta">>, 87 | 65461 => <<"America/Argentina/San_Juan">>, 88 | 65460 => <<"America/Argentina/San_Luis">>, 89 | 65459 => <<"America/Argentina/Tucuman">>, 90 | 65458 => <<"America/Argentina/Ushuaia">>, 91 | 65457 => <<"America/Aruba">>, 92 | 65456 => <<"America/Asuncion">>, 93 | 65455 => <<"America/Atikokan">>, 94 | 65454 => <<"America/Atka">>, 95 | 65453 => <<"America/Bahia">>, 96 | 65452 => <<"America/Bahia_Banderas">>, 97 | 65451 => <<"America/Barbados">>, 98 | 65450 => <<"America/Belem">>, 99 | 65449 => <<"America/Belize">>, 100 | 65448 => <<"America/Blanc-Sablon">>, 101 | 65447 => <<"America/Boa_Vista">>, 102 | 65446 => <<"America/Bogota">>, 103 | 65445 => <<"America/Boise">>, 104 | 65444 => <<"America/Buenos_Aires">>, 105 | 65443 => <<"America/Cambridge_Bay">>, 106 | 65442 => <<"America/Campo_Grande">>, 107 | 65441 => <<"America/Cancun">>, 108 | 65440 => <<"America/Caracas">>, 109 | 65439 => <<"America/Catamarca">>, 110 | 65438 => <<"America/Cayenne">>, 111 | 65437 => <<"America/Cayman">>, 112 | 65436 => <<"America/Chicago">>, 113 | 65435 => <<"America/Chihuahua">>, 114 | 65434 => <<"America/Coral_Harbour">>, 115 | 65433 => <<"America/Cordoba">>, 116 | 65432 => <<"America/Costa_Rica">>, 117 | 65431 => <<"America/Creston">>, 118 | 65430 => <<"America/Cuiaba">>, 119 | 65429 => <<"America/Curacao">>, 120 | 65428 => <<"America/Danmarkshavn">>, 121 | 65427 => <<"America/Dawson">>, 122 | 65426 => <<"America/Dawson_Creek">>, 123 | 65425 => <<"America/Denver">>, 124 | 65424 => <<"America/Detroit">>, 125 | 65423 => <<"America/Dominica">>, 126 | 65422 => <<"America/Edmonton">>, 127 | 65421 => <<"America/Eirunepe">>, 128 | 65420 => <<"America/El_Salvador">>, 129 | 65419 => <<"America/Ensenada">>, 130 | 65418 => <<"America/Fort_Nelson">>, 131 | 65417 => <<"America/Fort_Wayne">>, 132 | 65416 => <<"America/Fortaleza">>, 133 | 65415 => <<"America/Glace_Bay">>, 134 | 65414 => <<"America/Godthab">>, 135 | 65413 => <<"America/Goose_Bay">>, 136 | 65412 => <<"America/Grand_Turk">>, 137 | 65411 => <<"America/Grenada">>, 138 | 65410 => <<"America/Guadeloupe">>, 139 | 65409 => <<"America/Guatemala">>, 140 | 65408 => <<"America/Guayaquil">>, 141 | 65407 => <<"America/Guyana">>, 142 | 65406 => <<"America/Halifax">>, 143 | 65405 => <<"America/Havana">>, 144 | 65404 => <<"America/Hermosillo">>, 145 | 65403 => <<"America/Indiana/Indianapolis">>, 146 | 65402 => <<"America/Indiana/Knox">>, 147 | 65401 => <<"America/Indiana/Marengo">>, 148 | 65400 => <<"America/Indiana/Petersburg">>, 149 | 65399 => <<"America/Indiana/Tell_City">>, 150 | 65398 => <<"America/Indiana/Vevay">>, 151 | 65397 => <<"America/Indiana/Vincennes">>, 152 | 65396 => <<"America/Indiana/Winamac">>, 153 | 65395 => <<"America/Indianapolis">>, 154 | 65394 => <<"America/Inuvik">>, 155 | 65393 => <<"America/Iqaluit">>, 156 | 65392 => <<"America/Jamaica">>, 157 | 65391 => <<"America/Jujuy">>, 158 | 65390 => <<"America/Juneau">>, 159 | 65389 => <<"America/Kentucky/Louisville">>, 160 | 65388 => <<"America/Kentucky/Monticello">>, 161 | 65387 => <<"America/Knox_IN">>, 162 | 65386 => <<"America/Kralendijk">>, 163 | 65385 => <<"America/La_Paz">>, 164 | 65384 => <<"America/Lima">>, 165 | 65383 => <<"America/Los_Angeles">>, 166 | 65382 => <<"America/Louisville">>, 167 | 65381 => <<"America/Lower_Princes">>, 168 | 65380 => <<"America/Maceio">>, 169 | 65379 => <<"America/Managua">>, 170 | 65378 => <<"America/Manaus">>, 171 | 65377 => <<"America/Marigot">>, 172 | 65376 => <<"America/Martinique">>, 173 | 65375 => <<"America/Matamoros">>, 174 | 65374 => <<"America/Mazatlan">>, 175 | 65373 => <<"America/Mendoza">>, 176 | 65372 => <<"America/Menominee">>, 177 | 65371 => <<"America/Merida">>, 178 | 65370 => <<"America/Metlakatla">>, 179 | 65369 => <<"America/Mexico_City">>, 180 | 65368 => <<"America/Miquelon">>, 181 | 65367 => <<"America/Moncton">>, 182 | 65366 => <<"America/Monterrey">>, 183 | 65365 => <<"America/Montevideo">>, 184 | 65364 => <<"America/Montreal">>, 185 | 65363 => <<"America/Montserrat">>, 186 | 65362 => <<"America/Nassau">>, 187 | 65361 => <<"America/New_York">>, 188 | 65360 => <<"America/Nipigon">>, 189 | 65359 => <<"America/Nome">>, 190 | 65358 => <<"America/Noronha">>, 191 | 65357 => <<"America/North_Dakota/Beulah">>, 192 | 65356 => <<"America/North_Dakota/Center">>, 193 | 65355 => <<"America/North_Dakota/New_Salem">>, 194 | 65354 => <<"America/Ojinaga">>, 195 | 65353 => <<"America/Panama">>, 196 | 65352 => <<"America/Pangnirtung">>, 197 | 65351 => <<"America/Paramaribo">>, 198 | 65350 => <<"America/Phoenix">>, 199 | 65349 => <<"America/Port-au-Prince">>, 200 | 65348 => <<"America/Port_of_Spain">>, 201 | 65347 => <<"America/Porto_Acre">>, 202 | 65346 => <<"America/Porto_Velho">>, 203 | 65345 => <<"America/Puerto_Rico">>, 204 | 65344 => <<"America/Punta_Arenas">>, 205 | 65343 => <<"America/Rainy_River">>, 206 | 65342 => <<"America/Rankin_Inlet">>, 207 | 65341 => <<"America/Recife">>, 208 | 65340 => <<"America/Regina">>, 209 | 65339 => <<"America/Resolute">>, 210 | 65338 => <<"America/Rio_Branco">>, 211 | 65337 => <<"America/Rosario">>, 212 | 65336 => <<"America/Santa_Isabel">>, 213 | 65335 => <<"America/Santarem">>, 214 | 65334 => <<"America/Santiago">>, 215 | 65333 => <<"America/Santo_Domingo">>, 216 | 65332 => <<"America/Sao_Paulo">>, 217 | 65331 => <<"America/Scoresbysund">>, 218 | 65330 => <<"America/Shiprock">>, 219 | 65329 => <<"America/Sitka">>, 220 | 65328 => <<"America/St_Barthelemy">>, 221 | 65327 => <<"America/St_Johns">>, 222 | 65326 => <<"America/St_Kitts">>, 223 | 65325 => <<"America/St_Lucia">>, 224 | 65324 => <<"America/St_Thomas">>, 225 | 65323 => <<"America/St_Vincent">>, 226 | 65322 => <<"America/Swift_Current">>, 227 | 65321 => <<"America/Tegucigalpa">>, 228 | 65320 => <<"America/Thule">>, 229 | 65319 => <<"America/Thunder_Bay">>, 230 | 65318 => <<"America/Tijuana">>, 231 | 65317 => <<"America/Toronto">>, 232 | 65316 => <<"America/Tortola">>, 233 | 65315 => <<"America/Vancouver">>, 234 | 65314 => <<"America/Virgin">>, 235 | 65313 => <<"America/Whitehorse">>, 236 | 65312 => <<"America/Winnipeg">>, 237 | 65311 => <<"America/Yakutat">>, 238 | 65310 => <<"America/Yellowknife">>, 239 | 65309 => <<"Antarctica/Casey">>, 240 | 65308 => <<"Antarctica/Davis">>, 241 | 65307 => <<"Antarctica/DumontDUrville">>, 242 | 65306 => <<"Antarctica/Macquarie">>, 243 | 65305 => <<"Antarctica/Mawson">>, 244 | 65304 => <<"Antarctica/McMurdo">>, 245 | 65303 => <<"Antarctica/Palmer">>, 246 | 65302 => <<"Antarctica/Rothera">>, 247 | 65301 => <<"Antarctica/South_Pole">>, 248 | 65300 => <<"Antarctica/Syowa">>, 249 | 65299 => <<"Antarctica/Troll">>, 250 | 65298 => <<"Antarctica/Vostok">>, 251 | 65297 => <<"Arctic/Longyearbyen">>, 252 | 65296 => <<"Asia/Aden">>, 253 | 65295 => <<"Asia/Almaty">>, 254 | 65294 => <<"Asia/Amman">>, 255 | 65293 => <<"Asia/Anadyr">>, 256 | 65292 => <<"Asia/Aqtau">>, 257 | 65291 => <<"Asia/Aqtobe">>, 258 | 65290 => <<"Asia/Ashgabat">>, 259 | 65289 => <<"Asia/Ashkhabad">>, 260 | 65288 => <<"Asia/Atyrau">>, 261 | 65287 => <<"Asia/Baghdad">>, 262 | 65286 => <<"Asia/Bahrain">>, 263 | 65285 => <<"Asia/Baku">>, 264 | 65284 => <<"Asia/Bangkok">>, 265 | 65283 => <<"Asia/Barnaul">>, 266 | 65282 => <<"Asia/Beirut">>, 267 | 65281 => <<"Asia/Bishkek">>, 268 | 65280 => <<"Asia/Brunei">>, 269 | 65279 => <<"Asia/Calcutta">>, 270 | 65278 => <<"Asia/Chita">>, 271 | 65277 => <<"Asia/Choibalsan">>, 272 | 65276 => <<"Asia/Chongqing">>, 273 | 65275 => <<"Asia/Chungking">>, 274 | 65274 => <<"Asia/Colombo">>, 275 | 65273 => <<"Asia/Dacca">>, 276 | 65272 => <<"Asia/Damascus">>, 277 | 65271 => <<"Asia/Dhaka">>, 278 | 65270 => <<"Asia/Dili">>, 279 | 65269 => <<"Asia/Dubai">>, 280 | 65268 => <<"Asia/Dushanbe">>, 281 | 65267 => <<"Asia/Famagusta">>, 282 | 65266 => <<"Asia/Gaza">>, 283 | 65265 => <<"Asia/Harbin">>, 284 | 65264 => <<"Asia/Hebron">>, 285 | 65263 => <<"Asia/Ho_Chi_Minh">>, 286 | 65262 => <<"Asia/Hong_Kong">>, 287 | 65261 => <<"Asia/Hovd">>, 288 | 65260 => <<"Asia/Irkutsk">>, 289 | 65259 => <<"Asia/Istanbul">>, 290 | 65258 => <<"Asia/Jakarta">>, 291 | 65257 => <<"Asia/Jayapura">>, 292 | 65256 => <<"Asia/Jerusalem">>, 293 | 65255 => <<"Asia/Kabul">>, 294 | 65254 => <<"Asia/Kamchatka">>, 295 | 65253 => <<"Asia/Karachi">>, 296 | 65252 => <<"Asia/Kashgar">>, 297 | 65251 => <<"Asia/Kathmandu">>, 298 | 65250 => <<"Asia/Katmandu">>, 299 | 65249 => <<"Asia/Khandyga">>, 300 | 65248 => <<"Asia/Kolkata">>, 301 | 65247 => <<"Asia/Krasnoyarsk">>, 302 | 65246 => <<"Asia/Kuala_Lumpur">>, 303 | 65245 => <<"Asia/Kuching">>, 304 | 65244 => <<"Asia/Kuwait">>, 305 | 65243 => <<"Asia/Macao">>, 306 | 65242 => <<"Asia/Macau">>, 307 | 65241 => <<"Asia/Magadan">>, 308 | 65240 => <<"Asia/Makassar">>, 309 | 65239 => <<"Asia/Manila">>, 310 | 65238 => <<"Asia/Muscat">>, 311 | 65237 => <<"Asia/Nicosia">>, 312 | 65236 => <<"Asia/Novokuznetsk">>, 313 | 65235 => <<"Asia/Novosibirsk">>, 314 | 65234 => <<"Asia/Omsk">>, 315 | 65233 => <<"Asia/Oral">>, 316 | 65232 => <<"Asia/Phnom_Penh">>, 317 | 65231 => <<"Asia/Pontianak">>, 318 | 65230 => <<"Asia/Pyongyang">>, 319 | 65229 => <<"Asia/Qatar">>, 320 | 65228 => <<"Asia/Qyzylorda">>, 321 | 65227 => <<"Asia/Rangoon">>, 322 | 65226 => <<"Asia/Riyadh">>, 323 | 65225 => <<"Asia/Saigon">>, 324 | 65224 => <<"Asia/Sakhalin">>, 325 | 65223 => <<"Asia/Samarkand">>, 326 | 65222 => <<"Asia/Seoul">>, 327 | 65221 => <<"Asia/Shanghai">>, 328 | 65220 => <<"Asia/Singapore">>, 329 | 65219 => <<"Asia/Srednekolymsk">>, 330 | 65218 => <<"Asia/Taipei">>, 331 | 65217 => <<"Asia/Tashkent">>, 332 | 65216 => <<"Asia/Tbilisi">>, 333 | 65215 => <<"Asia/Tehran">>, 334 | 65214 => <<"Asia/Tel_Aviv">>, 335 | 65213 => <<"Asia/Thimbu">>, 336 | 65212 => <<"Asia/Thimphu">>, 337 | 65211 => <<"Asia/Tokyo">>, 338 | 65210 => <<"Asia/Tomsk">>, 339 | 65209 => <<"Asia/Ujung_Pandang">>, 340 | 65208 => <<"Asia/Ulaanbaatar">>, 341 | 65207 => <<"Asia/Ulan_Bator">>, 342 | 65206 => <<"Asia/Urumqi">>, 343 | 65205 => <<"Asia/Ust-Nera">>, 344 | 65204 => <<"Asia/Vientiane">>, 345 | 65203 => <<"Asia/Vladivostok">>, 346 | 65202 => <<"Asia/Yakutsk">>, 347 | 65201 => <<"Asia/Yangon">>, 348 | 65200 => <<"Asia/Yekaterinburg">>, 349 | 65199 => <<"Asia/Yerevan">>, 350 | 65198 => <<"Atlantic/Azores">>, 351 | 65197 => <<"Atlantic/Bermuda">>, 352 | 65196 => <<"Atlantic/Canary">>, 353 | 65195 => <<"Atlantic/Cape_Verde">>, 354 | 65194 => <<"Atlantic/Faeroe">>, 355 | 65193 => <<"Atlantic/Faroe">>, 356 | 65192 => <<"Atlantic/Jan_Mayen">>, 357 | 65191 => <<"Atlantic/Madeira">>, 358 | 65190 => <<"Atlantic/Reykjavik">>, 359 | 65189 => <<"Atlantic/South_Georgia">>, 360 | 65188 => <<"Atlantic/St_Helena">>, 361 | 65187 => <<"Atlantic/Stanley">>, 362 | 65186 => <<"Australia/ACT">>, 363 | 65185 => <<"Australia/Adelaide">>, 364 | 65184 => <<"Australia/Brisbane">>, 365 | 65183 => <<"Australia/Broken_Hill">>, 366 | 65182 => <<"Australia/Canberra">>, 367 | 65181 => <<"Australia/Currie">>, 368 | 65180 => <<"Australia/Darwin">>, 369 | 65179 => <<"Australia/Eucla">>, 370 | 65178 => <<"Australia/Hobart">>, 371 | 65177 => <<"Australia/LHI">>, 372 | 65176 => <<"Australia/Lindeman">>, 373 | 65175 => <<"Australia/Lord_Howe">>, 374 | 65174 => <<"Australia/Melbourne">>, 375 | 65173 => <<"Australia/NSW">>, 376 | 65172 => <<"Australia/North">>, 377 | 65171 => <<"Australia/Perth">>, 378 | 65170 => <<"Australia/Queensland">>, 379 | 65169 => <<"Australia/South">>, 380 | 65168 => <<"Australia/Sydney">>, 381 | 65167 => <<"Australia/Tasmania">>, 382 | 65166 => <<"Australia/Victoria">>, 383 | 65165 => <<"Australia/West">>, 384 | 65164 => <<"Australia/Yancowinna">>, 385 | 65163 => <<"BET">>, 386 | 65162 => <<"BST">>, 387 | 65161 => <<"Brazil/Acre">>, 388 | 65160 => <<"Brazil/DeNoronha">>, 389 | 65159 => <<"Brazil/East">>, 390 | 65158 => <<"Brazil/West">>, 391 | 65157 => <<"CAT">>, 392 | 65156 => <<"CET">>, 393 | 65155 => <<"CNT">>, 394 | 65154 => <<"CST">>, 395 | 65153 => <<"CST6CDT">>, 396 | 65152 => <<"CTT">>, 397 | 65151 => <<"Canada/Atlantic">>, 398 | 65150 => <<"Canada/Central">>, 399 | 65149 => <<"Canada/East-Saskatchewan">>, 400 | 65148 => <<"Canada/Eastern">>, 401 | 65147 => <<"Canada/Mountain">>, 402 | 65146 => <<"Canada/Newfoundland">>, 403 | 65145 => <<"Canada/Pacific">>, 404 | 65144 => <<"Canada/Saskatchewan">>, 405 | 65143 => <<"Canada/Yukon">>, 406 | 65142 => <<"Chile/Continental">>, 407 | 65141 => <<"Chile/EasterIsland">>, 408 | 65140 => <<"Cuba">>, 409 | 65139 => <<"EAT">>, 410 | 65138 => <<"ECT">>, 411 | 65137 => <<"EET">>, 412 | 65136 => <<"EST">>, 413 | 65135 => <<"EST5EDT">>, 414 | 65134 => <<"Egypt">>, 415 | 65133 => <<"Eire">>, 416 | 65132 => <<"Etc/GMT">>, 417 | 65131 => <<"Etc/GMT+0">>, 418 | 65130 => <<"Etc/GMT+1">>, 419 | 65129 => <<"Etc/GMT+10">>, 420 | 65128 => <<"Etc/GMT+11">>, 421 | 65127 => <<"Etc/GMT+12">>, 422 | 65126 => <<"Etc/GMT+2">>, 423 | 65125 => <<"Etc/GMT+3">>, 424 | 65124 => <<"Etc/GMT+4">>, 425 | 65123 => <<"Etc/GMT+5">>, 426 | 65122 => <<"Etc/GMT+6">>, 427 | 65121 => <<"Etc/GMT+7">>, 428 | 65120 => <<"Etc/GMT+8">>, 429 | 65119 => <<"Etc/GMT+9">>, 430 | 65118 => <<"Etc/GMT-0">>, 431 | 65117 => <<"Etc/GMT-1">>, 432 | 65116 => <<"Etc/GMT-10">>, 433 | 65115 => <<"Etc/GMT-11">>, 434 | 65114 => <<"Etc/GMT-12">>, 435 | 65113 => <<"Etc/GMT-13">>, 436 | 65112 => <<"Etc/GMT-14">>, 437 | 65111 => <<"Etc/GMT-2">>, 438 | 65110 => <<"Etc/GMT-3">>, 439 | 65109 => <<"Etc/GMT-4">>, 440 | 65108 => <<"Etc/GMT-5">>, 441 | 65107 => <<"Etc/GMT-6">>, 442 | 65106 => <<"Etc/GMT-7">>, 443 | 65105 => <<"Etc/GMT-8">>, 444 | 65104 => <<"Etc/GMT-9">>, 445 | 65103 => <<"Etc/GMT0">>, 446 | 65102 => <<"Etc/Greenwich">>, 447 | 65101 => <<"Etc/UCT">>, 448 | 65100 => <<"Etc/UTC">>, 449 | 65099 => <<"Etc/Universal">>, 450 | 65098 => <<"Etc/Zulu">>, 451 | 65097 => <<"Europe/Amsterdam">>, 452 | 65096 => <<"Europe/Andorra">>, 453 | 65095 => <<"Europe/Astrakhan">>, 454 | 65094 => <<"Europe/Athens">>, 455 | 65093 => <<"Europe/Belfast">>, 456 | 65092 => <<"Europe/Belgrade">>, 457 | 65091 => <<"Europe/Berlin">>, 458 | 65090 => <<"Europe/Bratislava">>, 459 | 65089 => <<"Europe/Brussels">>, 460 | 65088 => <<"Europe/Bucharest">>, 461 | 65087 => <<"Europe/Budapest">>, 462 | 65086 => <<"Europe/Busingen">>, 463 | 65085 => <<"Europe/Chisinau">>, 464 | 65084 => <<"Europe/Copenhagen">>, 465 | 65083 => <<"Europe/Dublin">>, 466 | 65082 => <<"Europe/Gibraltar">>, 467 | 65081 => <<"Europe/Guernsey">>, 468 | 65080 => <<"Europe/Helsinki">>, 469 | 65079 => <<"Europe/Isle_of_Man">>, 470 | 65078 => <<"Europe/Istanbul">>, 471 | 65077 => <<"Europe/Jersey">>, 472 | 65076 => <<"Europe/Kaliningrad">>, 473 | 65075 => <<"Europe/Kiev">>, 474 | 65074 => <<"Europe/Kirov">>, 475 | 65073 => <<"Europe/Lisbon">>, 476 | 65072 => <<"Europe/Ljubljana">>, 477 | 65071 => <<"Europe/London">>, 478 | 65070 => <<"Europe/Luxembourg">>, 479 | 65069 => <<"Europe/Madrid">>, 480 | 65068 => <<"Europe/Malta">>, 481 | 65067 => <<"Europe/Mariehamn">>, 482 | 65066 => <<"Europe/Minsk">>, 483 | 65065 => <<"Europe/Monaco">>, 484 | 65064 => <<"Europe/Moscow">>, 485 | 65063 => <<"Europe/Nicosia">>, 486 | 65062 => <<"Europe/Oslo">>, 487 | 65061 => <<"Europe/Paris">>, 488 | 65060 => <<"Europe/Podgorica">>, 489 | 65059 => <<"Europe/Prague">>, 490 | 65058 => <<"Europe/Riga">>, 491 | 65057 => <<"Europe/Rome">>, 492 | 65056 => <<"Europe/Samara">>, 493 | 65055 => <<"Europe/San_Marino">>, 494 | 65054 => <<"Europe/Sarajevo">>, 495 | 65053 => <<"Europe/Saratov">>, 496 | 65052 => <<"Europe/Simferopol">>, 497 | 65051 => <<"Europe/Skopje">>, 498 | 65050 => <<"Europe/Sofia">>, 499 | 65049 => <<"Europe/Stockholm">>, 500 | 65048 => <<"Europe/Tallinn">>, 501 | 65047 => <<"Europe/Tirane">>, 502 | 65046 => <<"Europe/Tiraspol">>, 503 | 65045 => <<"Europe/Ulyanovsk">>, 504 | 65044 => <<"Europe/Uzhgorod">>, 505 | 65043 => <<"Europe/Vaduz">>, 506 | 65042 => <<"Europe/Vatican">>, 507 | 65041 => <<"Europe/Vienna">>, 508 | 65040 => <<"Europe/Vilnius">>, 509 | 65039 => <<"Europe/Volgograd">>, 510 | 65038 => <<"Europe/Warsaw">>, 511 | 65037 => <<"Europe/Zagreb">>, 512 | 65036 => <<"Europe/Zaporozhye">>, 513 | 65035 => <<"Europe/Zurich">>, 514 | 65034 => <<"Factory">>, 515 | 65033 => <<"GB">>, 516 | 65032 => <<"GB-Eire">>, 517 | 65031 => <<"GMT+0">>, 518 | 65030 => <<"GMT-0">>, 519 | 65029 => <<"GMT0">>, 520 | 65028 => <<"Greenwich">>, 521 | 65027 => <<"HST">>, 522 | 65026 => <<"Hongkong">>, 523 | 65025 => <<"IET">>, 524 | 65024 => <<"IST">>, 525 | 65023 => <<"Iceland">>, 526 | 65022 => <<"Indian/Antananarivo">>, 527 | 65021 => <<"Indian/Chagos">>, 528 | 65020 => <<"Indian/Christmas">>, 529 | 65019 => <<"Indian/Cocos">>, 530 | 65018 => <<"Indian/Comoro">>, 531 | 65017 => <<"Indian/Kerguelen">>, 532 | 65016 => <<"Indian/Mahe">>, 533 | 65015 => <<"Indian/Maldives">>, 534 | 65014 => <<"Indian/Mauritius">>, 535 | 65013 => <<"Indian/Mayotte">>, 536 | 65012 => <<"Indian/Reunion">>, 537 | 65011 => <<"Iran">>, 538 | 65010 => <<"Israel">>, 539 | 65009 => <<"JST">>, 540 | 65008 => <<"Jamaica">>, 541 | 65007 => <<"Japan">>, 542 | 65006 => <<"Kwajalein">>, 543 | 65005 => <<"Libya">>, 544 | 65004 => <<"MET">>, 545 | 65003 => <<"MIT">>, 546 | 65002 => <<"MST">>, 547 | 65001 => <<"MST7MDT">>, 548 | 65000 => <<"Mexico/BajaNorte">>, 549 | 64999 => <<"Mexico/BajaSur">>, 550 | 64998 => <<"Mexico/General">>, 551 | 64997 => <<"NET">>, 552 | 64996 => <<"NST">>, 553 | 64995 => <<"NZ">>, 554 | 64994 => <<"NZ-CHAT">>, 555 | 64993 => <<"Navajo">>, 556 | 64992 => <<"PLT">>, 557 | 64991 => <<"PNT">>, 558 | 64990 => <<"PRC">>, 559 | 64989 => <<"PRT">>, 560 | 64988 => <<"PST">>, 561 | 64987 => <<"PST8PDT">>, 562 | 64986 => <<"Pacific/Apia">>, 563 | 64985 => <<"Pacific/Auckland">>, 564 | 64984 => <<"Pacific/Bougainville">>, 565 | 64983 => <<"Pacific/Chatham">>, 566 | 64982 => <<"Pacific/Chuuk">>, 567 | 64981 => <<"Pacific/Easter">>, 568 | 64980 => <<"Pacific/Efate">>, 569 | 64979 => <<"Pacific/Enderbury">>, 570 | 64978 => <<"Pacific/Fakaofo">>, 571 | 64977 => <<"Pacific/Fiji">>, 572 | 64976 => <<"Pacific/Funafuti">>, 573 | 64975 => <<"Pacific/Galapagos">>, 574 | 64974 => <<"Pacific/Gambier">>, 575 | 64973 => <<"Pacific/Guadalcanal">>, 576 | 64972 => <<"Pacific/Guam">>, 577 | 64971 => <<"Pacific/Honolulu">>, 578 | 64970 => <<"Pacific/Johnston">>, 579 | 64969 => <<"Pacific/Kiritimati">>, 580 | 64968 => <<"Pacific/Kosrae">>, 581 | 64967 => <<"Pacific/Kwajalein">>, 582 | 64966 => <<"Pacific/Majuro">>, 583 | 64965 => <<"Pacific/Marquesas">>, 584 | 64964 => <<"Pacific/Midway">>, 585 | 64963 => <<"Pacific/Nauru">>, 586 | 64962 => <<"Pacific/Niue">>, 587 | 64961 => <<"Pacific/Norfolk">>, 588 | 64960 => <<"Pacific/Noumea">>, 589 | 64959 => <<"Pacific/Pago_Pago">>, 590 | 64958 => <<"Pacific/Palau">>, 591 | 64957 => <<"Pacific/Pitcairn">>, 592 | 64956 => <<"Pacific/Pohnpei">>, 593 | 64955 => <<"Pacific/Ponape">>, 594 | 64954 => <<"Pacific/Port_Moresby">>, 595 | 64953 => <<"Pacific/Rarotonga">>, 596 | 64952 => <<"Pacific/Saipan">>, 597 | 64951 => <<"Pacific/Samoa">>, 598 | 64950 => <<"Pacific/Tahiti">>, 599 | 64949 => <<"Pacific/Tarawa">>, 600 | 64948 => <<"Pacific/Tongatapu">>, 601 | 64947 => <<"Pacific/Truk">>, 602 | 64946 => <<"Pacific/Wake">>, 603 | 64945 => <<"Pacific/Wallis">>, 604 | 64944 => <<"Pacific/Yap">>, 605 | 64943 => <<"Poland">>, 606 | 64942 => <<"Portugal">>, 607 | 64941 => <<"ROC">>, 608 | 64940 => <<"ROK">>, 609 | 64939 => <<"SST">>, 610 | 64938 => <<"Singapore">>, 611 | 64937 => <<"SystemV/AST4">>, 612 | 64936 => <<"SystemV/AST4ADT">>, 613 | 64935 => <<"SystemV/CST6">>, 614 | 64934 => <<"SystemV/CST6CDT">>, 615 | 64933 => <<"SystemV/EST5">>, 616 | 64932 => <<"SystemV/EST5EDT">>, 617 | 64931 => <<"SystemV/HST10">>, 618 | 64930 => <<"SystemV/MST7">>, 619 | 64929 => <<"SystemV/MST7MDT">>, 620 | 64928 => <<"SystemV/PST8">>, 621 | 64927 => <<"SystemV/PST8PDT">>, 622 | 64926 => <<"SystemV/YST9">>, 623 | 64925 => <<"SystemV/YST9YDT">>, 624 | 64924 => <<"Turkey">>, 625 | 64923 => <<"UCT">>, 626 | 64922 => <<"US/Alaska">>, 627 | 64921 => <<"US/Aleutian">>, 628 | 64920 => <<"US/Arizona">>, 629 | 64919 => <<"US/Central">>, 630 | 64918 => <<"US/East-Indiana">>, 631 | 64917 => <<"US/Eastern">>, 632 | 64916 => <<"US/Hawaii">>, 633 | 64915 => <<"US/Indiana-Starke">>, 634 | 64914 => <<"US/Michigan">>, 635 | 64913 => <<"US/Mountain">>, 636 | 64912 => <<"US/Pacific">>, 637 | 64911 => <<"US/Pacific-New">>, 638 | 64910 => <<"US/Samoa">>, 639 | 64909 => <<"UTC">>, 640 | 64908 => <<"Universal">>, 641 | 64907 => <<"VST">>, 642 | 64906 => <<"W-SU">>, 643 | 64905 => <<"WET">>, 644 | 64904 => <<"Zulu">>, 645 | 64903 => <<"America/Nuuk">>, 646 | 64902 => <<"Asia/Qostanay">> 647 | }. 648 | 649 | timezone_id_by_name() -> 650 | #{ 651 | <<"GMT">> => 65535, 652 | <<"ACT">> => 65534, 653 | <<"AET">> => 65533, 654 | <<"AGT">> => 65532, 655 | <<"ART">> => 65531, 656 | <<"AST">> => 65530, 657 | <<"Africa/Abidjan">> => 65529, 658 | <<"Africa/Accra">> => 65528, 659 | <<"Africa/Addis_Ababa">> => 65527, 660 | <<"Africa/Algiers">> => 65526, 661 | <<"Africa/Asmara">> => 65525, 662 | <<"Africa/Asmera">> => 65524, 663 | <<"Africa/Bamako">> => 65523, 664 | <<"Africa/Bangui">> => 65522, 665 | <<"Africa/Banjul">> => 65521, 666 | <<"Africa/Bissau">> => 65520, 667 | <<"Africa/Blantyre">> => 65519, 668 | <<"Africa/Brazzaville">> => 65518, 669 | <<"Africa/Bujumbura">> => 65517, 670 | <<"Africa/Cairo">> => 65516, 671 | <<"Africa/Casablanca">> => 65515, 672 | <<"Africa/Ceuta">> => 65514, 673 | <<"Africa/Conakry">> => 65513, 674 | <<"Africa/Dakar">> => 65512, 675 | <<"Africa/Dar_es_Salaam">> => 65511, 676 | <<"Africa/Djibouti">> => 65510, 677 | <<"Africa/Douala">> => 65509, 678 | <<"Africa/El_Aaiun">> => 65508, 679 | <<"Africa/Freetown">> => 65507, 680 | <<"Africa/Gaborone">> => 65506, 681 | <<"Africa/Harare">> => 65505, 682 | <<"Africa/Johannesburg">> => 65504, 683 | <<"Africa/Juba">> => 65503, 684 | <<"Africa/Kampala">> => 65502, 685 | <<"Africa/Khartoum">> => 65501, 686 | <<"Africa/Kigali">> => 65500, 687 | <<"Africa/Kinshasa">> => 65499, 688 | <<"Africa/Lagos">> => 65498, 689 | <<"Africa/Libreville">> => 65497, 690 | <<"Africa/Lome">> => 65496, 691 | <<"Africa/Luanda">> => 65495, 692 | <<"Africa/Lubumbashi">> => 65494, 693 | <<"Africa/Lusaka">> => 65493, 694 | <<"Africa/Malabo">> => 65492, 695 | <<"Africa/Maputo">> => 65491, 696 | <<"Africa/Maseru">> => 65490, 697 | <<"Africa/Mbabane">> => 65489, 698 | <<"Africa/Mogadishu">> => 65488, 699 | <<"Africa/Monrovia">> => 65487, 700 | <<"Africa/Nairobi">> => 65486, 701 | <<"Africa/Ndjamena">> => 65485, 702 | <<"Africa/Niamey">> => 65484, 703 | <<"Africa/Nouakchott">> => 65483, 704 | <<"Africa/Ouagadougou">> => 65482, 705 | <<"Africa/Porto-Novo">> => 65481, 706 | <<"Africa/Sao_Tome">> => 65480, 707 | <<"Africa/Timbuktu">> => 65479, 708 | <<"Africa/Tripoli">> => 65478, 709 | <<"Africa/Tunis">> => 65477, 710 | <<"Africa/Windhoek">> => 65476, 711 | <<"America/Adak">> => 65475, 712 | <<"America/Anchorage">> => 65474, 713 | <<"America/Anguilla">> => 65473, 714 | <<"America/Antigua">> => 65472, 715 | <<"America/Araguaina">> => 65471, 716 | <<"America/Argentina/Buenos_Aires">> => 65470, 717 | <<"America/Argentina/Catamarca">> => 65469, 718 | <<"America/Argentina/ComodRivadavia">> => 65468, 719 | <<"America/Argentina/Cordoba">> => 65467, 720 | <<"America/Argentina/Jujuy">> => 65466, 721 | <<"America/Argentina/La_Rioja">> => 65465, 722 | <<"America/Argentina/Mendoza">> => 65464, 723 | <<"America/Argentina/Rio_Gallegos">> => 65463, 724 | <<"America/Argentina/Salta">> => 65462, 725 | <<"America/Argentina/San_Juan">> => 65461, 726 | <<"America/Argentina/San_Luis">> => 65460, 727 | <<"America/Argentina/Tucuman">> => 65459, 728 | <<"America/Argentina/Ushuaia">> => 65458, 729 | <<"America/Aruba">> => 65457, 730 | <<"America/Asuncion">> => 65456, 731 | <<"America/Atikokan">> => 65455, 732 | <<"America/Atka">> => 65454, 733 | <<"America/Bahia">> => 65453, 734 | <<"America/Bahia_Banderas">> => 65452, 735 | <<"America/Barbados">> => 65451, 736 | <<"America/Belem">> => 65450, 737 | <<"America/Belize">> => 65449, 738 | <<"America/Blanc-Sablon">> => 65448, 739 | <<"America/Boa_Vista">> => 65447, 740 | <<"America/Bogota">> => 65446, 741 | <<"America/Boise">> => 65445, 742 | <<"America/Buenos_Aires">> => 65444, 743 | <<"America/Cambridge_Bay">> => 65443, 744 | <<"America/Campo_Grande">> => 65442, 745 | <<"America/Cancun">> => 65441, 746 | <<"America/Caracas">> => 65440, 747 | <<"America/Catamarca">> => 65439, 748 | <<"America/Cayenne">> => 65438, 749 | <<"America/Cayman">> => 65437, 750 | <<"America/Chicago">> => 65436, 751 | <<"America/Chihuahua">> => 65435, 752 | <<"America/Coral_Harbour">> => 65434, 753 | <<"America/Cordoba">> => 65433, 754 | <<"America/Costa_Rica">> => 65432, 755 | <<"America/Creston">> => 65431, 756 | <<"America/Cuiaba">> => 65430, 757 | <<"America/Curacao">> => 65429, 758 | <<"America/Danmarkshavn">> => 65428, 759 | <<"America/Dawson">> => 65427, 760 | <<"America/Dawson_Creek">> => 65426, 761 | <<"America/Denver">> => 65425, 762 | <<"America/Detroit">> => 65424, 763 | <<"America/Dominica">> => 65423, 764 | <<"America/Edmonton">> => 65422, 765 | <<"America/Eirunepe">> => 65421, 766 | <<"America/El_Salvador">> => 65420, 767 | <<"America/Ensenada">> => 65419, 768 | <<"America/Fort_Nelson">> => 65418, 769 | <<"America/Fort_Wayne">> => 65417, 770 | <<"America/Fortaleza">> => 65416, 771 | <<"America/Glace_Bay">> => 65415, 772 | <<"America/Godthab">> => 65414, 773 | <<"America/Goose_Bay">> => 65413, 774 | <<"America/Grand_Turk">> => 65412, 775 | <<"America/Grenada">> => 65411, 776 | <<"America/Guadeloupe">> => 65410, 777 | <<"America/Guatemala">> => 65409, 778 | <<"America/Guayaquil">> => 65408, 779 | <<"America/Guyana">> => 65407, 780 | <<"America/Halifax">> => 65406, 781 | <<"America/Havana">> => 65405, 782 | <<"America/Hermosillo">> => 65404, 783 | <<"America/Indiana/Indianapolis">> => 65403, 784 | <<"America/Indiana/Knox">> => 65402, 785 | <<"America/Indiana/Marengo">> => 65401, 786 | <<"America/Indiana/Petersburg">> => 65400, 787 | <<"America/Indiana/Tell_City">> => 65399, 788 | <<"America/Indiana/Vevay">> => 65398, 789 | <<"America/Indiana/Vincennes">> => 65397, 790 | <<"America/Indiana/Winamac">> => 65396, 791 | <<"America/Indianapolis">> => 65395, 792 | <<"America/Inuvik">> => 65394, 793 | <<"America/Iqaluit">> => 65393, 794 | <<"America/Jamaica">> => 65392, 795 | <<"America/Jujuy">> => 65391, 796 | <<"America/Juneau">> => 65390, 797 | <<"America/Kentucky/Louisville">> => 65389, 798 | <<"America/Kentucky/Monticello">> => 65388, 799 | <<"America/Knox_IN">> => 65387, 800 | <<"America/Kralendijk">> => 65386, 801 | <<"America/La_Paz">> => 65385, 802 | <<"America/Lima">> => 65384, 803 | <<"America/Los_Angeles">> => 65383, 804 | <<"America/Louisville">> => 65382, 805 | <<"America/Lower_Princes">> => 65381, 806 | <<"America/Maceio">> => 65380, 807 | <<"America/Managua">> => 65379, 808 | <<"America/Manaus">> => 65378, 809 | <<"America/Marigot">> => 65377, 810 | <<"America/Martinique">> => 65376, 811 | <<"America/Matamoros">> => 65375, 812 | <<"America/Mazatlan">> => 65374, 813 | <<"America/Mendoza">> => 65373, 814 | <<"America/Menominee">> => 65372, 815 | <<"America/Merida">> => 65371, 816 | <<"America/Metlakatla">> => 65370, 817 | <<"America/Mexico_City">> => 65369, 818 | <<"America/Miquelon">> => 65368, 819 | <<"America/Moncton">> => 65367, 820 | <<"America/Monterrey">> => 65366, 821 | <<"America/Montevideo">> => 65365, 822 | <<"America/Montreal">> => 65364, 823 | <<"America/Montserrat">> => 65363, 824 | <<"America/Nassau">> => 65362, 825 | <<"America/New_York">> => 65361, 826 | <<"America/Nipigon">> => 65360, 827 | <<"America/Nome">> => 65359, 828 | <<"America/Noronha">> => 65358, 829 | <<"America/North_Dakota/Beulah">> => 65357, 830 | <<"America/North_Dakota/Center">> => 65356, 831 | <<"America/North_Dakota/New_Salem">> => 65355, 832 | <<"America/Ojinaga">> => 65354, 833 | <<"America/Panama">> => 65353, 834 | <<"America/Pangnirtung">> => 65352, 835 | <<"America/Paramaribo">> => 65351, 836 | <<"America/Phoenix">> => 65350, 837 | <<"America/Port-au-Prince">> => 65349, 838 | <<"America/Port_of_Spain">> => 65348, 839 | <<"America/Porto_Acre">> => 65347, 840 | <<"America/Porto_Velho">> => 65346, 841 | <<"America/Puerto_Rico">> => 65345, 842 | <<"America/Punta_Arenas">> => 65344, 843 | <<"America/Rainy_River">> => 65343, 844 | <<"America/Rankin_Inlet">> => 65342, 845 | <<"America/Recife">> => 65341, 846 | <<"America/Regina">> => 65340, 847 | <<"America/Resolute">> => 65339, 848 | <<"America/Rio_Branco">> => 65338, 849 | <<"America/Rosario">> => 65337, 850 | <<"America/Santa_Isabel">> => 65336, 851 | <<"America/Santarem">> => 65335, 852 | <<"America/Santiago">> => 65334, 853 | <<"America/Santo_Domingo">> => 65333, 854 | <<"America/Sao_Paulo">> => 65332, 855 | <<"America/Scoresbysund">> => 65331, 856 | <<"America/Shiprock">> => 65330, 857 | <<"America/Sitka">> => 65329, 858 | <<"America/St_Barthelemy">> => 65328, 859 | <<"America/St_Johns">> => 65327, 860 | <<"America/St_Kitts">> => 65326, 861 | <<"America/St_Lucia">> => 65325, 862 | <<"America/St_Thomas">> => 65324, 863 | <<"America/St_Vincent">> => 65323, 864 | <<"America/Swift_Current">> => 65322, 865 | <<"America/Tegucigalpa">> => 65321, 866 | <<"America/Thule">> => 65320, 867 | <<"America/Thunder_Bay">> => 65319, 868 | <<"America/Tijuana">> => 65318, 869 | <<"America/Toronto">> => 65317, 870 | <<"America/Tortola">> => 65316, 871 | <<"America/Vancouver">> => 65315, 872 | <<"America/Virgin">> => 65314, 873 | <<"America/Whitehorse">> => 65313, 874 | <<"America/Winnipeg">> => 65312, 875 | <<"America/Yakutat">> => 65311, 876 | <<"America/Yellowknife">> => 65310, 877 | <<"Antarctica/Casey">> => 65309, 878 | <<"Antarctica/Davis">> => 65308, 879 | <<"Antarctica/DumontDUrville">> => 65307, 880 | <<"Antarctica/Macquarie">> => 65306, 881 | <<"Antarctica/Mawson">> => 65305, 882 | <<"Antarctica/McMurdo">> => 65304, 883 | <<"Antarctica/Palmer">> => 65303, 884 | <<"Antarctica/Rothera">> => 65302, 885 | <<"Antarctica/South_Pole">> => 65301, 886 | <<"Antarctica/Syowa">> => 65300, 887 | <<"Antarctica/Troll">> => 65299, 888 | <<"Antarctica/Vostok">> => 65298, 889 | <<"Arctic/Longyearbyen">> => 65297, 890 | <<"Asia/Aden">> => 65296, 891 | <<"Asia/Almaty">> => 65295, 892 | <<"Asia/Amman">> => 65294, 893 | <<"Asia/Anadyr">> => 65293, 894 | <<"Asia/Aqtau">> => 65292, 895 | <<"Asia/Aqtobe">> => 65291, 896 | <<"Asia/Ashgabat">> => 65290, 897 | <<"Asia/Ashkhabad">> => 65289, 898 | <<"Asia/Atyrau">> => 65288, 899 | <<"Asia/Baghdad">> => 65287, 900 | <<"Asia/Bahrain">> => 65286, 901 | <<"Asia/Baku">> => 65285, 902 | <<"Asia/Bangkok">> => 65284, 903 | <<"Asia/Barnaul">> => 65283, 904 | <<"Asia/Beirut">> => 65282, 905 | <<"Asia/Bishkek">> => 65281, 906 | <<"Asia/Brunei">> => 65280, 907 | <<"Asia/Calcutta">> => 65279, 908 | <<"Asia/Chita">> => 65278, 909 | <<"Asia/Choibalsan">> => 65277, 910 | <<"Asia/Chongqing">> => 65276, 911 | <<"Asia/Chungking">> => 65275, 912 | <<"Asia/Colombo">> => 65274, 913 | <<"Asia/Dacca">> => 65273, 914 | <<"Asia/Damascus">> => 65272, 915 | <<"Asia/Dhaka">> => 65271, 916 | <<"Asia/Dili">> => 65270, 917 | <<"Asia/Dubai">> => 65269, 918 | <<"Asia/Dushanbe">> => 65268, 919 | <<"Asia/Famagusta">> => 65267, 920 | <<"Asia/Gaza">> => 65266, 921 | <<"Asia/Harbin">> => 65265, 922 | <<"Asia/Hebron">> => 65264, 923 | <<"Asia/Ho_Chi_Minh">> => 65263, 924 | <<"Asia/Hong_Kong">> => 65262, 925 | <<"Asia/Hovd">> => 65261, 926 | <<"Asia/Irkutsk">> => 65260, 927 | <<"Asia/Istanbul">> => 65259, 928 | <<"Asia/Jakarta">> => 65258, 929 | <<"Asia/Jayapura">> => 65257, 930 | <<"Asia/Jerusalem">> => 65256, 931 | <<"Asia/Kabul">> => 65255, 932 | <<"Asia/Kamchatka">> => 65254, 933 | <<"Asia/Karachi">> => 65253, 934 | <<"Asia/Kashgar">> => 65252, 935 | <<"Asia/Kathmandu">> => 65251, 936 | <<"Asia/Katmandu">> => 65250, 937 | <<"Asia/Khandyga">> => 65249, 938 | <<"Asia/Kolkata">> => 65248, 939 | <<"Asia/Krasnoyarsk">> => 65247, 940 | <<"Asia/Kuala_Lumpur">> => 65246, 941 | <<"Asia/Kuching">> => 65245, 942 | <<"Asia/Kuwait">> => 65244, 943 | <<"Asia/Macao">> => 65243, 944 | <<"Asia/Macau">> => 65242, 945 | <<"Asia/Magadan">> => 65241, 946 | <<"Asia/Makassar">> => 65240, 947 | <<"Asia/Manila">> => 65239, 948 | <<"Asia/Muscat">> => 65238, 949 | <<"Asia/Nicosia">> => 65237, 950 | <<"Asia/Novokuznetsk">> => 65236, 951 | <<"Asia/Novosibirsk">> => 65235, 952 | <<"Asia/Omsk">> => 65234, 953 | <<"Asia/Oral">> => 65233, 954 | <<"Asia/Phnom_Penh">> => 65232, 955 | <<"Asia/Pontianak">> => 65231, 956 | <<"Asia/Pyongyang">> => 65230, 957 | <<"Asia/Qatar">> => 65229, 958 | <<"Asia/Qyzylorda">> => 65228, 959 | <<"Asia/Rangoon">> => 65227, 960 | <<"Asia/Riyadh">> => 65226, 961 | <<"Asia/Saigon">> => 65225, 962 | <<"Asia/Sakhalin">> => 65224, 963 | <<"Asia/Samarkand">> => 65223, 964 | <<"Asia/Seoul">> => 65222, 965 | <<"Asia/Shanghai">> => 65221, 966 | <<"Asia/Singapore">> => 65220, 967 | <<"Asia/Srednekolymsk">> => 65219, 968 | <<"Asia/Taipei">> => 65218, 969 | <<"Asia/Tashkent">> => 65217, 970 | <<"Asia/Tbilisi">> => 65216, 971 | <<"Asia/Tehran">> => 65215, 972 | <<"Asia/Tel_Aviv">> => 65214, 973 | <<"Asia/Thimbu">> => 65213, 974 | <<"Asia/Thimphu">> => 65212, 975 | <<"Asia/Tokyo">> => 65211, 976 | <<"Asia/Tomsk">> => 65210, 977 | <<"Asia/Ujung_Pandang">> => 65209, 978 | <<"Asia/Ulaanbaatar">> => 65208, 979 | <<"Asia/Ulan_Bator">> => 65207, 980 | <<"Asia/Urumqi">> => 65206, 981 | <<"Asia/Ust-Nera">> => 65205, 982 | <<"Asia/Vientiane">> => 65204, 983 | <<"Asia/Vladivostok">> => 65203, 984 | <<"Asia/Yakutsk">> => 65202, 985 | <<"Asia/Yangon">> => 65201, 986 | <<"Asia/Yekaterinburg">> => 65200, 987 | <<"Asia/Yerevan">> => 65199, 988 | <<"Atlantic/Azores">> => 65198, 989 | <<"Atlantic/Bermuda">> => 65197, 990 | <<"Atlantic/Canary">> => 65196, 991 | <<"Atlantic/Cape_Verde">> => 65195, 992 | <<"Atlantic/Faeroe">> => 65194, 993 | <<"Atlantic/Faroe">> => 65193, 994 | <<"Atlantic/Jan_Mayen">> => 65192, 995 | <<"Atlantic/Madeira">> => 65191, 996 | <<"Atlantic/Reykjavik">> => 65190, 997 | <<"Atlantic/South_Georgia">> => 65189, 998 | <<"Atlantic/St_Helena">> => 65188, 999 | <<"Atlantic/Stanley">> => 65187, 1000 | <<"Australia/ACT">> => 65186, 1001 | <<"Australia/Adelaide">> => 65185, 1002 | <<"Australia/Brisbane">> => 65184, 1003 | <<"Australia/Broken_Hill">> => 65183, 1004 | <<"Australia/Canberra">> => 65182, 1005 | <<"Australia/Currie">> => 65181, 1006 | <<"Australia/Darwin">> => 65180, 1007 | <<"Australia/Eucla">> => 65179, 1008 | <<"Australia/Hobart">> => 65178, 1009 | <<"Australia/LHI">> => 65177, 1010 | <<"Australia/Lindeman">> => 65176, 1011 | <<"Australia/Lord_Howe">> => 65175, 1012 | <<"Australia/Melbourne">> => 65174, 1013 | <<"Australia/NSW">> => 65173, 1014 | <<"Australia/North">> => 65172, 1015 | <<"Australia/Perth">> => 65171, 1016 | <<"Australia/Queensland">> => 65170, 1017 | <<"Australia/South">> => 65169, 1018 | <<"Australia/Sydney">> => 65168, 1019 | <<"Australia/Tasmania">> => 65167, 1020 | <<"Australia/Victoria">> => 65166, 1021 | <<"Australia/West">> => 65165, 1022 | <<"Australia/Yancowinna">> => 65164, 1023 | <<"BET">> => 65163, 1024 | <<"BST">> => 65162, 1025 | <<"Brazil/Acre">> => 65161, 1026 | <<"Brazil/DeNoronha">> => 65160, 1027 | <<"Brazil/East">> => 65159, 1028 | <<"Brazil/West">> => 65158, 1029 | <<"CAT">> => 65157, 1030 | <<"CET">> => 65156, 1031 | <<"CNT">> => 65155, 1032 | <<"CST">> => 65154, 1033 | <<"CST6CDT">> => 65153, 1034 | <<"CTT">> => 65152, 1035 | <<"Canada/Atlantic">> => 65151, 1036 | <<"Canada/Central">> => 65150, 1037 | <<"Canada/East-Saskatchewan">> => 65149, 1038 | <<"Canada/Eastern">> => 65148, 1039 | <<"Canada/Mountain">> => 65147, 1040 | <<"Canada/Newfoundland">> => 65146, 1041 | <<"Canada/Pacific">> => 65145, 1042 | <<"Canada/Saskatchewan">> => 65144, 1043 | <<"Canada/Yukon">> => 65143, 1044 | <<"Chile/Continental">> => 65142, 1045 | <<"Chile/EasterIsland">> => 65141, 1046 | <<"Cuba">> => 65140, 1047 | <<"EAT">> => 65139, 1048 | <<"ECT">> => 65138, 1049 | <<"EET">> => 65137, 1050 | <<"EST">> => 65136, 1051 | <<"EST5EDT">> => 65135, 1052 | <<"Egypt">> => 65134, 1053 | <<"Eire">> => 65133, 1054 | <<"Etc/GMT">> => 65132, 1055 | <<"Etc/GMT+0">> => 65131, 1056 | <<"Etc/GMT+1">> => 65130, 1057 | <<"Etc/GMT+10">> => 65129, 1058 | <<"Etc/GMT+11">> => 65128, 1059 | <<"Etc/GMT+12">> => 65127, 1060 | <<"Etc/GMT+2">> => 65126, 1061 | <<"Etc/GMT+3">> => 65125, 1062 | <<"Etc/GMT+4">> => 65124, 1063 | <<"Etc/GMT+5">> => 65123, 1064 | <<"Etc/GMT+6">> => 65122, 1065 | <<"Etc/GMT+7">> => 65121, 1066 | <<"Etc/GMT+8">> => 65120, 1067 | <<"Etc/GMT+9">> => 65119, 1068 | <<"Etc/GMT-0">> => 65118, 1069 | <<"Etc/GMT-1">> => 65117, 1070 | <<"Etc/GMT-10">> => 65116, 1071 | <<"Etc/GMT-11">> => 65115, 1072 | <<"Etc/GMT-12">> => 65114, 1073 | <<"Etc/GMT-13">> => 65113, 1074 | <<"Etc/GMT-14">> => 65112, 1075 | <<"Etc/GMT-2">> => 65111, 1076 | <<"Etc/GMT-3">> => 65110, 1077 | <<"Etc/GMT-4">> => 65109, 1078 | <<"Etc/GMT-5">> => 65108, 1079 | <<"Etc/GMT-6">> => 65107, 1080 | <<"Etc/GMT-7">> => 65106, 1081 | <<"Etc/GMT-8">> => 65105, 1082 | <<"Etc/GMT-9">> => 65104, 1083 | <<"Etc/GMT0">> => 65103, 1084 | <<"Etc/Greenwich">> => 65102, 1085 | <<"Etc/UCT">> => 65101, 1086 | <<"Etc/UTC">> => 65100, 1087 | <<"Etc/Universal">> => 65099, 1088 | <<"Etc/Zulu">> => 65098, 1089 | <<"Europe/Amsterdam">> => 65097, 1090 | <<"Europe/Andorra">> => 65096, 1091 | <<"Europe/Astrakhan">> => 65095, 1092 | <<"Europe/Athens">> => 65094, 1093 | <<"Europe/Belfast">> => 65093, 1094 | <<"Europe/Belgrade">> => 65092, 1095 | <<"Europe/Berlin">> => 65091, 1096 | <<"Europe/Bratislava">> => 65090, 1097 | <<"Europe/Brussels">> => 65089, 1098 | <<"Europe/Bucharest">> => 65088, 1099 | <<"Europe/Budapest">> => 65087, 1100 | <<"Europe/Busingen">> => 65086, 1101 | <<"Europe/Chisinau">> => 65085, 1102 | <<"Europe/Copenhagen">> => 65084, 1103 | <<"Europe/Dublin">> => 65083, 1104 | <<"Europe/Gibraltar">> => 65082, 1105 | <<"Europe/Guernsey">> => 65081, 1106 | <<"Europe/Helsinki">> => 65080, 1107 | <<"Europe/Isle_of_Man">> => 65079, 1108 | <<"Europe/Istanbul">> => 65078, 1109 | <<"Europe/Jersey">> => 65077, 1110 | <<"Europe/Kaliningrad">> => 65076, 1111 | <<"Europe/Kiev">> => 65075, 1112 | <<"Europe/Kirov">> => 65074, 1113 | <<"Europe/Lisbon">> => 65073, 1114 | <<"Europe/Ljubljana">> => 65072, 1115 | <<"Europe/London">> => 65071, 1116 | <<"Europe/Luxembourg">> => 65070, 1117 | <<"Europe/Madrid">> => 65069, 1118 | <<"Europe/Malta">> => 65068, 1119 | <<"Europe/Mariehamn">> => 65067, 1120 | <<"Europe/Minsk">> => 65066, 1121 | <<"Europe/Monaco">> => 65065, 1122 | <<"Europe/Moscow">> => 65064, 1123 | <<"Europe/Nicosia">> => 65063, 1124 | <<"Europe/Oslo">> => 65062, 1125 | <<"Europe/Paris">> => 65061, 1126 | <<"Europe/Podgorica">> => 65060, 1127 | <<"Europe/Prague">> => 65059, 1128 | <<"Europe/Riga">> => 65058, 1129 | <<"Europe/Rome">> => 65057, 1130 | <<"Europe/Samara">> => 65056, 1131 | <<"Europe/San_Marino">> => 65055, 1132 | <<"Europe/Sarajevo">> => 65054, 1133 | <<"Europe/Saratov">> => 65053, 1134 | <<"Europe/Simferopol">> => 65052, 1135 | <<"Europe/Skopje">> => 65051, 1136 | <<"Europe/Sofia">> => 65050, 1137 | <<"Europe/Stockholm">> => 65049, 1138 | <<"Europe/Tallinn">> => 65048, 1139 | <<"Europe/Tirane">> => 65047, 1140 | <<"Europe/Tiraspol">> => 65046, 1141 | <<"Europe/Ulyanovsk">> => 65045, 1142 | <<"Europe/Uzhgorod">> => 65044, 1143 | <<"Europe/Vaduz">> => 65043, 1144 | <<"Europe/Vatican">> => 65042, 1145 | <<"Europe/Vienna">> => 65041, 1146 | <<"Europe/Vilnius">> => 65040, 1147 | <<"Europe/Volgograd">> => 65039, 1148 | <<"Europe/Warsaw">> => 65038, 1149 | <<"Europe/Zagreb">> => 65037, 1150 | <<"Europe/Zaporozhye">> => 65036, 1151 | <<"Europe/Zurich">> => 65035, 1152 | <<"Factory">> => 65034, 1153 | <<"GB">> => 65033, 1154 | <<"GB-Eire">> => 65032, 1155 | <<"GMT+0">> => 65031, 1156 | <<"GMT-0">> => 65030, 1157 | <<"GMT0">> => 65029, 1158 | <<"Greenwich">> => 65028, 1159 | <<"HST">> => 65027, 1160 | <<"Hongkong">> => 65026, 1161 | <<"IET">> => 65025, 1162 | <<"IST">> => 65024, 1163 | <<"Iceland">> => 65023, 1164 | <<"Indian/Antananarivo">> => 65022, 1165 | <<"Indian/Chagos">> => 65021, 1166 | <<"Indian/Christmas">> => 65020, 1167 | <<"Indian/Cocos">> => 65019, 1168 | <<"Indian/Comoro">> => 65018, 1169 | <<"Indian/Kerguelen">> => 65017, 1170 | <<"Indian/Mahe">> => 65016, 1171 | <<"Indian/Maldives">> => 65015, 1172 | <<"Indian/Mauritius">> => 65014, 1173 | <<"Indian/Mayotte">> => 65013, 1174 | <<"Indian/Reunion">> => 65012, 1175 | <<"Iran">> => 65011, 1176 | <<"Israel">> => 65010, 1177 | <<"JST">> => 65009, 1178 | <<"Jamaica">> => 65008, 1179 | <<"Japan">> => 65007, 1180 | <<"Kwajalein">> => 65006, 1181 | <<"Libya">> => 65005, 1182 | <<"MET">> => 65004, 1183 | <<"MIT">> => 65003, 1184 | <<"MST">> => 65002, 1185 | <<"MST7MDT">> => 65001, 1186 | <<"Mexico/BajaNorte">> => 65000, 1187 | <<"Mexico/BajaSur">> => 64999, 1188 | <<"Mexico/General">> => 64998, 1189 | <<"NET">> => 64997, 1190 | <<"NST">> => 64996, 1191 | <<"NZ">> => 64995, 1192 | <<"NZ-CHAT">> => 64994, 1193 | <<"Navajo">> => 64993, 1194 | <<"PLT">> => 64992, 1195 | <<"PNT">> => 64991, 1196 | <<"PRC">> => 64990, 1197 | <<"PRT">> => 64989, 1198 | <<"PST">> => 64988, 1199 | <<"PST8PDT">> => 64987, 1200 | <<"Pacific/Apia">> => 64986, 1201 | <<"Pacific/Auckland">> => 64985, 1202 | <<"Pacific/Bougainville">> => 64984, 1203 | <<"Pacific/Chatham">> => 64983, 1204 | <<"Pacific/Chuuk">> => 64982, 1205 | <<"Pacific/Easter">> => 64981, 1206 | <<"Pacific/Efate">> => 64980, 1207 | <<"Pacific/Enderbury">> => 64979, 1208 | <<"Pacific/Fakaofo">> => 64978, 1209 | <<"Pacific/Fiji">> => 64977, 1210 | <<"Pacific/Funafuti">> => 64976, 1211 | <<"Pacific/Galapagos">> => 64975, 1212 | <<"Pacific/Gambier">> => 64974, 1213 | <<"Pacific/Guadalcanal">> => 64973, 1214 | <<"Pacific/Guam">> => 64972, 1215 | <<"Pacific/Honolulu">> => 64971, 1216 | <<"Pacific/Johnston">> => 64970, 1217 | <<"Pacific/Kiritimati">> => 64969, 1218 | <<"Pacific/Kosrae">> => 64968, 1219 | <<"Pacific/Kwajalein">> => 64967, 1220 | <<"Pacific/Majuro">> => 64966, 1221 | <<"Pacific/Marquesas">> => 64965, 1222 | <<"Pacific/Midway">> => 64964, 1223 | <<"Pacific/Nauru">> => 64963, 1224 | <<"Pacific/Niue">> => 64962, 1225 | <<"Pacific/Norfolk">> => 64961, 1226 | <<"Pacific/Noumea">> => 64960, 1227 | <<"Pacific/Pago_Pago">> => 64959, 1228 | <<"Pacific/Palau">> => 64958, 1229 | <<"Pacific/Pitcairn">> => 64957, 1230 | <<"Pacific/Pohnpei">> => 64956, 1231 | <<"Pacific/Ponape">> => 64955, 1232 | <<"Pacific/Port_Moresby">> => 64954, 1233 | <<"Pacific/Rarotonga">> => 64953, 1234 | <<"Pacific/Saipan">> => 64952, 1235 | <<"Pacific/Samoa">> => 64951, 1236 | <<"Pacific/Tahiti">> => 64950, 1237 | <<"Pacific/Tarawa">> => 64949, 1238 | <<"Pacific/Tongatapu">> => 64948, 1239 | <<"Pacific/Truk">> => 64947, 1240 | <<"Pacific/Wake">> => 64946, 1241 | <<"Pacific/Wallis">> => 64945, 1242 | <<"Pacific/Yap">> => 64944, 1243 | <<"Poland">> => 64943, 1244 | <<"Portugal">> => 64942, 1245 | <<"ROC">> => 64941, 1246 | <<"ROK">> => 64940, 1247 | <<"SST">> => 64939, 1248 | <<"Singapore">> => 64938, 1249 | <<"SystemV/AST4">> => 64937, 1250 | <<"SystemV/AST4ADT">> => 64936, 1251 | <<"SystemV/CST6">> => 64935, 1252 | <<"SystemV/CST6CDT">> => 64934, 1253 | <<"SystemV/EST5">> => 64933, 1254 | <<"SystemV/EST5EDT">> => 64932, 1255 | <<"SystemV/HST10">> => 64931, 1256 | <<"SystemV/MST7">> => 64930, 1257 | <<"SystemV/MST7MDT">> => 64929, 1258 | <<"SystemV/PST8">> => 64928, 1259 | <<"SystemV/PST8PDT">> => 64927, 1260 | <<"SystemV/YST9">> => 64926, 1261 | <<"SystemV/YST9YDT">> => 64925, 1262 | <<"Turkey">> => 64924, 1263 | <<"UCT">> => 64923, 1264 | <<"US/Alaska">> => 64922, 1265 | <<"US/Aleutian">> => 64921, 1266 | <<"US/Arizona">> => 64920, 1267 | <<"US/Central">> => 64919, 1268 | <<"US/East-Indiana">> => 64918, 1269 | <<"US/Eastern">> => 64917, 1270 | <<"US/Hawaii">> => 64916, 1271 | <<"US/Indiana-Starke">> => 64915, 1272 | <<"US/Michigan">> => 64914, 1273 | <<"US/Mountain">> => 64913, 1274 | <<"US/Pacific">> => 64912, 1275 | <<"US/Pacific-New">> => 64911, 1276 | <<"US/Samoa">> => 64910, 1277 | <<"UTC">> => 64909, 1278 | <<"Universal">> => 64908, 1279 | <<"VST">> => 64907, 1280 | <<"W-SU">> => 64906, 1281 | <<"WET">> => 64905, 1282 | <<"Zulu">> => 64904, 1283 | <<"America/Nuuk">> => 64903, 1284 | <<"Asia/Qostanay">> => 64902 1285 | }. 1286 | --------------------------------------------------------------------------------