diff --git a/.travis.yml b/.travis.yml index 0c3e76c0..8aa8d24b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,10 @@ before_script: - make script: "make test" + otp_release: - - 17.0-rc1 + - 18.0 + - 17.5 - R16B03-1 - R16B - R15B03 diff --git a/Makefile b/Makefile index cd06e0a9..e5d78ed9 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,19 @@ APP_NAME=emysql MODULES=$(shell ls -1 src/*.erl | awk -F[/.] '{ print $$2 }' | sed '$$q;s/$$/,/g') MAKETIME=$(shell date) +## Check if we are on erlang version that has namespaced types +ERL_NT=$(shell escript ./support/ntype_check.escript) + +## Check if we are on erlang version that has erlang:timestamp/0 +ERL_TS=$(shell escript ./support/timestamp_check.escript) + +ifeq ($(ERL_NT),true) +ERLC_NT_FLAG=-Dnamespaced_types +endif +ifeq ($(ERL_TS),true) +ERLC_TS_FLAG=-Dtimestamp_support +endif + all: crypto_compat app (cd src;$(MAKE)) @@ -80,7 +93,7 @@ CT_RUN = ct_run \ CT_SUITES=environment basics conn_mgr build-tests: - erlc -v -o test/ $(wildcard test/*.erl) -pa ebin/ + erlc -v $(ERLC_NT_FLAG) $(ERLC_TS_FLAG) -o test/ $(wildcard test/*.erl) -pa ebin/ test: all build-tests @mkdir -p logs diff --git a/include/emysql.hrl b/include/emysql.hrl index 1320289f..4fa979ba 100644 --- a/include/emysql.hrl +++ b/include/emysql.hrl @@ -3,16 +3,16 @@ %% Jacob Vorreuter , %% Henning Diedrich , %% Eonblast Corporation -%% +%% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"),to deal in the Software without restric- -%% tion, including without limitation the rights to use, copy, +%% tion, including without limitation the rights to use, copy, %% modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the -%% Software is furnished to do so, subject to the following +%% Software is furnished to do so, subject to the following %% conditions: -%% +%% %% The above copyright notice and this permission notice shall be %% included in all copies or substantial portions of the Software. %% @@ -25,89 +25,88 @@ %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR %% OTHER DEALINGS IN THE SOFTWARE. - --record(pool, {pool_id :: atom(), - size :: number(), - user :: string(), - password :: string(), - host :: string(), - port :: number(), - database :: string(), +-record(pool, {pool_id :: atom(), + size :: number(), + user :: string(), + password :: string(), + host :: string(), + port :: number(), + database :: string(), encoding :: utf8 | latin1 | {utf8, utf8_unicode_ci} | {utf8, utf8_general_ci}, - available=queue:new() :: queue(), - locked=gb_trees:empty() :: gb_tree(), - waiting=queue:new() :: queue(), - start_cmds=[] :: string(), - conn_test_period=0 :: number(), + available=queue:new() :: emysql:t_queue(), + locked=gb_trees:empty() :: emysql:t_gb_tree(), + waiting=queue:new() :: emysql:t_queue(), + start_cmds=[] :: string(), + conn_test_period=0 :: number(), connect_timeout=infinity :: number() | infinity, warnings=false :: boolean()}). --record(emysql_connection, {id :: string(), - pool_id :: atom(), +-record(emysql_connection, {id :: string(), + pool_id :: atom(), encoding :: atom(), % maybe could be latin1 | utf8 ? - socket :: inet:socket(), - version :: number(), - thread_id :: number(), - caps :: number(), - language :: number, - prepared=gb_trees:empty(), - locked_at :: number(), - alive=true :: boolean(), - test_period=0 :: number(), - last_test_time=0 :: number(), + socket :: inet:socket(), + version :: number(), + thread_id :: number(), + caps :: number(), + language :: number, + prepared=gb_trees:empty(), + locked_at :: number(), + alive=true :: boolean(), + test_period=0 :: number(), + last_test_time=0 :: number(), monitor_ref :: reference(), warnings=false :: boolean()}). --record(greeting, {protocol_version :: number(), - server_version :: binary(), - thread_id :: number(), - salt1 :: binary(), - salt2 :: binary(), - caps :: number(), - caps_high :: number(), - language :: number(), - status :: number(), - seq_num :: number(), +-record(greeting, {protocol_version :: number(), + server_version :: binary(), + thread_id :: number(), + salt1 :: binary(), + salt2 :: binary(), + caps :: number(), + caps_high :: number(), + language :: number(), + status :: number(), + seq_num :: number(), plugin :: binary()}). --record(field, {seq_num :: number(), - catalog :: binary(), - db :: binary(), - table :: binary(), - org_table :: binary(), - name :: binary(), - org_name :: binary(), - type :: number(), - default :: number(), - charset_nr :: number(), - length :: number(), - flags :: number(), - decimals :: number(), +-record(field, {seq_num :: number(), + catalog :: binary(), + db :: binary(), + table :: binary(), + org_table :: binary(), + name :: binary(), + org_name :: binary(), + type :: number(), + default :: number(), + charset_nr :: number(), + length :: number(), + flags :: number(), + decimals :: number(), decoder :: fun()}). --record(packet, {size :: number(), - seq_num :: number(), +-record(packet, {size :: number(), + seq_num :: number(), data :: binary()}). --record(ok_packet, {seq_num :: number(), - affected_rows :: number(), - insert_id :: number(), - status :: number(), - warning_count :: number(), +-record(ok_packet, {seq_num :: number(), + affected_rows :: number(), + insert_id :: number(), + status :: number(), + warning_count :: number(), msg :: string() | {error, string(), unicode:latin1_chardata() | unicode:chardata() | unicode:external_chardata()} | {incomplete, string(), binary()}}). % It's unfortunate that error_packet's status is binary when the status of other % packets is a number. --record(error_packet, {seq_num :: number(), - code :: number(), - status :: binary(), +-record(error_packet, {seq_num :: number(), + code :: number(), + status :: binary(), msg :: [byte()]}). --record(eof_packet, {seq_num :: number(), - status :: number(), +-record(eof_packet, {seq_num :: number(), + status :: number(), warning_count :: number()}). % extended to mySQL 4.1+ format --record(result_packet, {seq_num :: number(), +-record(result_packet, {seq_num :: number(), field_list :: list(), rows, extra}). @@ -179,4 +178,3 @@ % we discovered that the new statement returns a different % number of result set columns. -define(SERVER_STATUS_METADATA_CHANGED, 1024). - diff --git a/rebar.config b/rebar.config index e61983a8..2b0a7736 100644 --- a/rebar.config +++ b/rebar.config @@ -1,9 +1,11 @@ % -*- Erlang -*- % vim: ts=4 sw=4 et ft=erlang {erl_opts, [ + {platform_define, "^[0-9]+", namespaced_types}, nowarn_deprecated_type ]}. {pre_hooks,[ + {"linux|bsd|darwin|solaris", compile, "escript ./support/crypto_compat.escript"}, {"win32", compile, "escript.exe support/crypto_compat.escript"} ]}. diff --git a/rebar.config.script b/rebar.config.script new file mode 100644 index 00000000..0917d911 --- /dev/null +++ b/rebar.config.script @@ -0,0 +1,14 @@ +{exports, ExportList} = lists:keyfind(exports,1,erlang:module_info()), +Check = lists:member({timestamp,0},ExportList), +case Check of + true -> + case lists:keyfind(erl_opts, 1, CONFIG) of + false -> + CONFIG ++ [{erl_opts,[{d,timestamp_support}]}]; + {erl_opts, Opts} -> + NewOpts = {erl_opts, Opts ++ [{d,timestamp_support}]}, + lists:keyreplace(erl_opts, 1, CONFIG, NewOpts) + end; + false -> + CONFIG +end. diff --git a/src/emysql.erl b/src/emysql.erl index a373de9a..73dbfae3 100644 --- a/src/emysql.erl +++ b/src/emysql.erl @@ -105,9 +105,9 @@ add_pool/9, add_pool/8, remove_pool/1, increment_pool_size/2, decrement_pool_size/2 ]). - + %% Interaction API -%% Used to interact with the database. +%% Used to interact with the database. -export([ prepare/2, execute/2, execute/3, execute/4, execute/5, @@ -136,6 +136,23 @@ % for record and constant defines -include("emysql.hrl"). +-export_type([ + t_gb_tree/0, + t_queue/0, + t_dict/0 +]). + +-ifdef(namespaced_types). +-type t_gb_tree() :: gb_trees:tree(). +-type t_queue() :: queue:queue(). +-type t_dict() :: dict:dict(). +-else. +-type t_gb_tree() :: gb_tree(). +-type t_queue() :: queue(). +-type t_dict() :: dict(). +-endif. + + %% @spec start() -> ok %% @doc Start the Emysql application. %% @@ -246,8 +263,8 @@ config_ok(#pool{pool_id=PoolId,size=Size,user=User,password=Password,host=Host,p config_ok(_BadOptions) -> erlang:error(badarg). -encoding_ok(Enc) when is_atom(Enc) -> ok; -encoding_ok({Enc, Coll}) when is_atom(Enc), is_atom(Coll) -> ok; +encoding_ok(Enc) when is_atom(Enc) -> ok; +encoding_ok({Enc, Coll}) when is_atom(Enc), is_atom(Coll) -> ok; encoding_ok(_) -> erlang:error(badarg). %% Creates a pool record, opens n=Size connections and calls @@ -267,7 +284,7 @@ add_pool(PoolId, Options) when is_list(Options) -> Warnings = proplists:get_value(warnings, Options, false), add_pool(#pool{pool_id=PoolId,size=Size, user=User, password=Password, host=Host, port=Port, database=Database, - encoding=Encoding, start_cmds=StartCmds, + encoding=Encoding, start_cmds=StartCmds, connect_timeout=ConnectTimeout, warnings=Warnings}). add_pool(#pool{pool_id=PoolId,size=Size,user=User,password=Password,host=Host,port=Port, @@ -275,7 +292,7 @@ add_pool(#pool{pool_id=PoolId,size=Size,user=User,password=Password,host=Host,po connect_timeout=ConnectTimeout,warnings=Warnings}=PoolSettings)-> config_ok(PoolSettings), case emysql_conn_mgr:has_pool(PoolId) of - true -> + true -> {error,pool_already_exists}; false -> Pool = #pool{ @@ -328,8 +345,8 @@ add_pool(PoolId, Size, User, Password, Host, Port, Database, Encoding) -> add_pool(PoolId, Size, User, Password, Host, Port, Database, Encoding, StartCmds) -> add_pool(PoolId, Size, User, Password, Host, Port, Database, Encoding, StartCmds, infinity). -add_pool(PoolId, Size, User, Password, Host, Port, Database, - Encoding, StartCmds, ConnectTimeout)-> +add_pool(PoolId, Size, User, Password, Host, Port, Database, + Encoding, StartCmds, ConnectTimeout)-> add_pool(PoolId,[{size,Size},{user,User},{password,Password}, {host,Host},{port,Port},{database,Database}, {encoding,Encoding},{start_cmds,StartCmds}, @@ -669,7 +686,7 @@ result_type(#eof_packet{}) -> eof. -spec as_dict(Result) -> Dict when Result :: #result_packet{}, - Dict :: dict(). + Dict :: t_dict(). as_dict(Res) -> emysql_conv:as_dict(Res). diff --git a/src/emysql_conn.erl b/src/emysql_conn.erl index 01a5c74b..9448b87e 100644 --- a/src/emysql_conn.erl +++ b/src/emysql_conn.erl @@ -75,7 +75,7 @@ set_database(Connection, Database) -> set_encoding(_, undefined) -> ok; set_encoding(Connection, {Encoding, Collation}) -> - Packet = <>, emysql_tcp:send_and_recv_packet(Connection#emysql_connection.socket, Packet, 0); set_encoding(Connection, Encoding) -> @@ -260,7 +260,7 @@ run_startcmds_or_die(#emysql_connection{socket=Socket}, StartCmds) -> end, StartCmds ). - + set_encoding_or_die(#emysql_connection { socket = Socket } = Connection, Encoding) -> case set_encoding(Connection, Encoding) of ok -> ok; @@ -269,7 +269,7 @@ set_encoding_or_die(#emysql_connection { socket = Socket } = Connection, Encodin gen_tcp:close(Socket), exit({failed_to_set_encoding, Err2#error_packet.msg}) end. - + reset_connection(Pools, Conn, StayLocked) -> %% if a process dies or times out while doing work %% the socket must be closed and the connection reset @@ -334,7 +334,7 @@ need_test_connection(Conn) -> (Conn#emysql_connection.last_test_time + Conn#emysql_connection.test_period < now_seconds()). now_seconds() -> - {M, S, _} = erlang:now(), + {M, S, _} = emysql_util:timestamp(), M * 1000000 + S. %%-------------------------------------------------------------------- @@ -365,7 +365,7 @@ set_params_packet(NumStart, Values) -> BinValues = [encode(Val, binary) || Val <- Values], BinNums = [encode(Num, binary) || Num <- lists:seq(NumStart, NumStart + length(Values) - 1)], BinPairs = lists:zip(BinNums, BinValues), - Parts = [<<"@", NumBin/binary, "=", ValBin/binary>> || {NumBin, ValBin} <- BinPairs], + Parts = [<<"@", NumBin/binary, "=", ValBin/binary>> || {NumBin, ValBin} <- BinPairs], Sets = list_to_binary(join(Parts, <<",">>)), <>. @@ -449,7 +449,7 @@ encode({_Time1, _Time2, _Time3}=Val, binary) -> list_to_binary(encode(Val, list)); encode(Val, _) -> {error, {unrecognized_value, Val}}. - + %% @private two_digits(Nums) when is_list(Nums) -> [two_digits(Num) || Num <- Nums]; diff --git a/src/emysql_conn_mgr.erl b/src/emysql_conn_mgr.erl index cd31eeb9..e283a6e4 100644 --- a/src/emysql_conn_mgr.erl +++ b/src/emysql_conn_mgr.erl @@ -40,7 +40,7 @@ -include("emysql.hrl"). --record(state, {pools, lockers = dict:new() :: dict()}). +-record(state, {pools, lockers = dict:new() :: emysql:t_dict()}). %%==================================================================== %% API @@ -419,7 +419,7 @@ lock_next_connection(Available ,Locked, Who) -> end. connection_locked_at(Conn, MonitorRef) -> - Conn#emysql_connection{locked_at=lists:nth(2, tuple_to_list(now())), + Conn#emysql_connection{locked_at=lists:nth(2, tuple_to_list(emysql_util:timestamp())), monitor_ref = MonitorRef}. serve_waiting_pids(Pool) -> diff --git a/src/emysql_util.erl b/src/emysql_util.erl index 71848cd9..8b0debd1 100644 --- a/src/emysql_util.erl +++ b/src/emysql_util.erl @@ -44,6 +44,11 @@ as_record/4 ]). +%% Erlang version-specific functions +-export([ + timestamp/0 +]). + affected_rows(P) -> emysql:affected_rows(P). field_names(R) -> emysql:field_names(R). insert_id(P) -> emysql:insert_id(P). @@ -55,3 +60,8 @@ as_proplist(Res) -> emysql:as_proplist(Res). as_record(Res, RecName, Fields) -> emysql:as_record(Res, RecName, Fields). as_record(Res, RecName, Fields, Fun) -> emysql:as_record(Res, RecName, Fields, Fun). +-ifdef(timestamp_support). +timestamp() -> erlang:timestamp(). +-else. +timestamp() -> erlang:now(). +-endif. diff --git a/support/include.mk b/support/include.mk index eb826c5a..b6bab0a0 100644 --- a/support/include.mk +++ b/support/include.mk @@ -15,6 +15,19 @@ ifdef debug ERLC_FLAGS += -Ddebug endif +## Check if we are on erlang version that has namespaced types +ERL_NT := $(shell escript ../support/ntype_check.escript) +## Check if we are on erlang version that has erlang:timestamp/0 +ERL_TS := $(shell escript ../support/timestamp_check.escript) + +ifeq ($(ERL_NT),true) + ERLC_FLAGS += -Dnamespaced_types +endif + +ifeq ($(ERL_TS),true) + ERLC_FLAGS += -Dtimestamp_support +endif + EBIN_DIR := ../ebin DOC_DIR := ../doc EMULATOR := beam @@ -30,10 +43,9 @@ EBIN_FILES = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app) $(ERL_TEMPLATES) $(EBIN_DIR)/%.$(EMULATOR): %.erl $(ERL_HEADERS) $(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $< -./%.$(EMULATOR): %.erl +./%.$(EMULATOR): %.erl $(ERLC) $(ERLC_FLAGS) -o . $< $(DOC_DIR)/%.html: %.erl $(ERL) -noshell -run edoc file $< -run init stop mv *.html $(DOC_DIR) - diff --git a/support/ntype_check.escript b/support/ntype_check.escript new file mode 100644 index 00000000..eda46d96 --- /dev/null +++ b/support/ntype_check.escript @@ -0,0 +1,14 @@ +#!/usr/bin/env escript +%% vim: ts=4 sw=4 et ft=erlang + +%% The purpose of this script is to check if we need namespaced types for +%% successful compilation. + +main([]) -> + Check = case erlang:system_info(otp_release) of + [X|_] when X >= 48 andalso X =< 57 -> + true; + _ -> + false + end, + io:format("~s",[Check]). diff --git a/support/timestamp_check.escript b/support/timestamp_check.escript new file mode 100644 index 00000000..5c24454a --- /dev/null +++ b/support/timestamp_check.escript @@ -0,0 +1,10 @@ +#!/usr/bin/env escript +%% vim: ts=4 sw=4 et ft=erlang + +%% The purpose of this script is to check if we need namespaced types for +%% successful compilation. + +main([]) -> + {exports, ExportList} = lists:keyfind(exports,1,erlang:module_info()), + Check = lists:member({timestamp,0},ExportList), + io:format("~s",[Check]).