%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2002-2014. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% -module(odbc_connect_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). -include_lib("common_test/include/ct.hrl"). -include("test_server_line.hrl"). -include("odbc_test.hrl"). -define(MAX_SEQ_TIMEOUTS, 10). %%-------------------------------------------------------------------- %% all(Arg) -> [Doc] | [Case] | {skip, Comment} %% Arg - doc | suite %% Doc - string() %% Case - atom() %% Name of a test case function. %% Comment - string() %% Description: Returns documentation/test cases in this test suite %% or a skip tuple if the platform is not supported. %%-------------------------------------------------------------------- suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> case odbc_test_lib:odbc_check() of ok -> [not_exist_db, commit, rollback, not_explicit_commit, no_c_executable, port_dies, control_process_dies, {group, client_dies}, connect_timeout, timeout, many_timeouts, timeout_reset, disconnect_on_timeout, connection_closed, disable_scrollable_cursors, return_rows_as_lists, api_missuse, extended_errors]; Other -> {skip, Other} end. groups() -> [{client_dies, [], [client_dies_normal, client_dies_timeout, client_dies_error]}]. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %%-------------------------------------------------------------------- %% Function: init_per_suite(Config) -> Config %% Config - [tuple()] %% A list of key/value pairs, holding the test case configuration. %% Description: Initiation before the whole suite %% %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_suite(Config) when is_list(Config) -> file:write_file(filename:join([proplists:get_value(priv_dir,Config), "..","..","..","ignore_core_files"]),""), case odbc_test_lib:skip() of true -> {skip, "ODBC not supported"}; false -> case (catch odbc:start()) of ok -> case catch odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()) of {ok, Ref} -> odbc:disconnect(Ref), [{tableName, odbc_test_lib:unique_table_name()} | Config]; _ -> {skip, "ODBC is not properly setup"} end; _ -> {skip,"ODBC not startable"} end end. %%-------------------------------------------------------------------- %% Function: end_per_suite(Config) -> _ %% Config - [tuple()] %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after the whole suite %%-------------------------------------------------------------------- end_per_suite(_Config) -> application:stop(odbc). %%-------------------------------------------------------------------- %% Function: init_per_testcase(Case, Config) -> Config %% Case - atom() %% Name of the test case that is about to be run. %% Config - [tuple()] %% A list of key/value pairs, holding the test case configuration. %% %% Description: Initiation before each test case %% %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_testcase(connect_port_timeout, Config) -> odbc:stop(), application:load(odbc), application:set_env(odbc, port_timeout, 0), odbc:start(), init_per_testcase_common(Config); init_per_testcase(_TestCase, Config) -> init_per_testcase_common(Config). init_per_testcase_common(Config) -> test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), Dog = test_server:timetrap(?default_timeout), Temp = lists:keydelete(connection_ref, 1, Config), NewConfig = lists:keydelete(watchdog, 1, Temp), [{watchdog, Dog} | NewConfig]. %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ %% Case - atom() %% Name of the test case that is about to be run. %% Config - [tuple()] %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after each test case %%-------------------------------------------------------------------- end_per_testcase(connect_port_timeout, Config) -> application:unset_env(odbc, port_timeout), odbc:stop(), odbc:start(), end_per_testcase_common(Config); end_per_testcase(_TestCase, Config) -> end_per_testcase_common(Config). end_per_testcase_common(Config) -> Table = ?config(tableName, Config), {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Result = odbc:sql_query(Ref, "DROP TABLE " ++ Table), io:format("Drop table: ~p ~p~n", [Table, Result]), odbc:disconnect(Ref), Dog = ?config(watchdog, Config), test_server:timetrap_cancel(Dog). %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- commit(doc)-> ["Test the use of explicit commit"]; commit(suite) -> []; commit(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA varchar(10))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1,'bar')"), {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ " SET DATA = 'foo' WHERE ID = 1"), ok = odbc:commit(Ref, commit), UpdateResult = ?RDBMS:update_result(), UpdateResult = odbc:sql_query(Ref, "SELECT * FROM " ++ Table), {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ " SET DATA = 'bar' WHERE ID = 1"), ok = odbc:commit(Ref, commit, ?TIMEOUT), InsertResult = ?RDBMS:insert_result(), InsertResult = odbc:sql_query(Ref, "SELECT * FROM " ++ Table), {'EXIT', {function_clause, _}} = (catch odbc:commit(Ref, commit, -1)), ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- rollback(doc)-> ["Test the use of explicit rollback"]; rollback(suite) -> []; rollback(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA varchar(10))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), ok = odbc:commit(Ref, commit), {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ " SET DATA = 'foo' WHERE ID = 1"), ok = odbc:commit(Ref, rollback), InsertResult = ?RDBMS:insert_result(), InsertResult = odbc:sql_query(Ref, "SELECT * FROM " ++ Table), {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ " SET DATA = 'foo' WHERE ID = 1"), ok = odbc:commit(Ref, rollback, ?TIMEOUT), InsertResult = ?RDBMS:insert_result(), InsertResult = odbc:sql_query(Ref, "SELECT * FROM " ++ Table), {'EXIT', {function_clause, _}} = (catch odbc:commit(Ref, rollback, -1)), ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- not_explicit_commit(doc) -> ["Test what happens if you try using commit on a auto_commit connection."]; not_explicit_commit(suite) -> []; not_explicit_commit(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, on}] ++ odbc_test_lib:platform_options()), {error, _} = odbc:commit(Ref, commit), ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- not_exist_db(doc) -> ["Tests valid data format but invalid data in the connection parameters."]; not_exist_db(suite) -> []; not_exist_db(_Config) -> {error, _} = odbc:connect("DSN=foo;UID=bar;PWD=foobar", odbc_test_lib:platform_options()), %% So that the odbc control server can be stoped "in the correct way" test_server:sleep(100). %%------------------------------------------------------------------------- no_c_executable(doc) -> "Test what happens if the port-program can not be found"; no_c_executable(suite) -> []; no_c_executable(_Config) -> process_flag(trap_exit, true), Dir = filename:nativename(filename:join(code:priv_dir(odbc), "bin")), FileName1 = filename:nativename(os:find_executable("odbcserver", Dir)), FileName2 = filename:nativename(filename:join(Dir, "odbcsrv")), case file:rename(FileName1, FileName2) of ok -> Result = case catch odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()) of {error, port_program_executable_not_found} -> ok; Else -> Else end, ok = file:rename(FileName2, FileName1), ok = Result; _ -> {skip, "File permission issues"} end. %%------------------------------------------------------------------------ port_dies(doc) -> "Tests what happens if the port program dies"; port_dies(suite) -> []; port_dies(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), {status, _} = process_info(Ref, status), process_flag(trap_exit, true), NamedPorts = [{P, erlang:port_info(P, name)} || P <- erlang:ports()], case [P || {P, {name, Name}} <- NamedPorts, is_odbcserver(Name)] of [Port] -> exit(Port, kill), %% Wait for exit_status from port 5000 ms (will not get a exit %% status in this case), then wait a little longer to make sure %% the port and the controlprocess has had time to terminate. test_server:sleep(10000), undefined = process_info(Ref, status); [] -> ct:fail([erlang:port_info(P, name) || P <- erlang:ports()]) end. %%------------------------------------------------------------------------- control_process_dies(doc) -> "Tests what happens if the Erlang control process dies"; control_process_dies(suite) -> []; control_process_dies(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), process_flag(trap_exit, true), NamedPorts = [{P, erlang:port_info(P, name)} || P <- erlang:ports()], case [P || {P, {name, Name}} <- NamedPorts, is_odbcserver(Name)] of [Port] -> {connected, Ref} = erlang:port_info(Port, connected), exit(Ref, kill), test_server:sleep(500), undefined = erlang:port_info(Port, connected); %% Check for c-program still running, how? [] -> ct:fail([erlang:port_info(P, name) || P <- erlang:ports()]) end. %%------------------------------------------------------------------------- client_dies_normal(doc) -> ["Client dies with reason normal."]; client_dies_normal(suite) -> []; client_dies_normal(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_normal, [self()]), MonitorReference = receive {dbRef, Ref} -> MRef = erlang:monitor(process, Ref), Pid ! continue, MRef end, receive {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> test_server:fail(control_process_not_stopped) end. client_normal(Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Pid ! {dbRef, Ref}, receive continue -> ok end, exit(self(), normal). %%------------------------------------------------------------------------- client_dies_timeout(doc) -> ["Client dies with reason timeout."]; client_dies_timeout(suite) -> []; client_dies_timeout(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_timeout, [self()]), MonitorReference = receive {dbRef, Ref} -> MRef = erlang:monitor(process, Ref), Pid ! continue, MRef end, receive {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> test_server:fail(control_process_not_stopped) end. client_timeout(Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Pid ! {dbRef, Ref}, receive continue -> ok end, exit(self(), timeout). %%------------------------------------------------------------------------- client_dies_error(doc) -> ["Client dies with reason error."]; client_dies_error(suite) -> []; client_dies_error(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_error, [self()]), MonitorReference = receive {dbRef, Ref} -> MRef = erlang:monitor(process, Ref), Pid ! continue, MRef end, receive {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> test_server:fail(control_process_not_stopped) end. client_error(Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Pid ! {dbRef, Ref}, receive continue -> ok end, exit(self(), error). %%------------------------------------------------------------------------- connect_timeout(doc) -> ["Test the timeout for the connect function."]; connect_timeout(suite) -> []; connect_timeout(Config) when is_list(Config) -> {'EXIT',timeout} = (catch odbc:connect(?RDBMS:connection_string(), [{timeout, 0}] ++ odbc_test_lib:platform_options())), %% Need to return ok here "{'EXIT',timeout} return value" will %% be interpreted as that the testcase has timed out. ok. %%------------------------------------------------------------------------- connect_port_timeout(doc) -> ["Test the timeout for the port program to connect back to the odbc " "application within the connect function."]; connect_port_timeout(suite) -> []; connect_port_timeout(Config) when is_list(Config) -> %% Application environment var 'port_timeout' has been set to 0 by %% init_per_testcase/2. {error,timeout} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()). %%------------------------------------------------------------------------- timeout(doc) -> ["Test that timeouts don't cause unwanted behavior sush as receiving" " an anwser to a previously tiemed out query."]; timeout(suite) -> []; timeout(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}]), Table = ?config(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), ok = odbc:commit(Ref, commit), {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ " SET DATA = 'foo' WHERE ID = 1"), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'baz')"), Pid = spawn_link(?MODULE, update_table_timeout, [Table, 5000, self()]), receive timout_occurred -> ok = odbc:commit(Ref, commit), Pid ! continue end, receive altered -> ok end, {selected, Fields, [{"foobar"}]} = odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 1"), ["DATA"] = odbc_test_lib:to_upper(Fields), ok = odbc:commit(Ref, commit), ok = odbc:disconnect(Ref). update_table_timeout(Table, TimeOut, Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1", case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of {'EXIT', timeout} -> Pid ! timout_occurred; {updated, 1} -> test_server:fail(database_locker_failed) end, receive continue -> ok end, %% Make sure we receive the correct result and not the answer %% to the previous query. {selected, Fields, [{"baz"}]} = odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"), ["DATA"] = odbc_test_lib:to_upper(Fields), %% Do not check {updated, 1} as some drivers will return 0 %% even though the update is done, which is checked by the test %% case when the altered message is recived. {updated, _} = odbc:sql_query(Ref, UpdateQuery, TimeOut), ok = odbc:commit(Ref, commit), Pid ! altered, ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- many_timeouts(doc) -> ["Tests that many consecutive timeouts lead to that the connection " "is shutdown."]; many_timeouts(suite) -> []; many_timeouts(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), ok = odbc:commit(Ref, commit), {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ " SET DATA = 'foo' WHERE ID = 1"), _Pid = spawn_link(?MODULE, update_table_many_timeouts, [Table, 5000, self()]), receive many_timeouts_occurred -> ok end, ok = odbc:commit(Ref, commit), ok = odbc:disconnect(Ref). update_table_many_timeouts(Table, TimeOut, Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1", ok = loop_many_timouts(Ref, UpdateQuery, TimeOut), Pid ! many_timeouts_occurred, ok = odbc:disconnect(Ref). loop_many_timouts(Ref, UpdateQuery, TimeOut) -> case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of {'EXIT',timeout} -> loop_many_timouts(Ref, UpdateQuery, TimeOut); {updated, 1} -> test_server:fail(database_locker_failed); {error, connection_closed} -> ok end. %%------------------------------------------------------------------------- timeout_reset(doc) -> ["Check that the number of consecutive timouts is reset to 0 when " "a successful call to the database is made."]; timeout_reset(suite) -> []; timeout_reset(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), ok = odbc:commit(Ref, commit), {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ " SET DATA = 'foo' WHERE ID = 1"), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'baz')"), Pid = spawn_link(?MODULE, update_table_timeout_reset, [Table, 5000, self()]), receive many_timeouts_occurred -> ok end, ok = odbc:commit(Ref, commit), Pid ! continue, receive altered -> ok end, {selected, Fields, [{"foobar"}]} = odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 1"), ["DATA"] = odbc_test_lib:to_upper(Fields), ok = odbc:commit(Ref, commit), ok = odbc:disconnect(Ref). update_table_timeout_reset(Table, TimeOut, Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1", ok = loop_timout_reset(Ref, UpdateQuery, TimeOut, ?MAX_SEQ_TIMEOUTS-1), Pid ! many_timeouts_occurred, receive continue -> ok end, {selected, Fields, [{"baz"}]} = odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"), ["DATA"] = odbc_test_lib:to_upper(Fields), %% Do not check {updated, 1} as some drivers will return 0 %% even though the update is done, which is checked by the test %% case when the altered message is recived. {updated, _} = odbc:sql_query(Ref, UpdateQuery, TimeOut), ok = odbc:commit(Ref, commit), Pid ! altered, ok = odbc:disconnect(Ref). loop_timout_reset(_, _, _, 0) -> ok; loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts) -> case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of {'EXIT',timeout} -> loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts - 1); {updated, 1} -> test_server:fail(database_locker_failed); {error, connection_closed} -> test_server:fail(connection_closed_premature) end. %%------------------------------------------------------------------------- disconnect_on_timeout(doc) -> ["Check that disconnect after a time out works properly"]; disconnect_on_timeout(suite) -> []; disconnect_on_timeout(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr), {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), ok = odbc:commit(Ref, commit), {updated, 1} = odbc:sql_query(Ref, "UPDATE " ++ Table ++ " SET DATA = 'foo' WHERE ID = 1"), _Pid = spawn_link(?MODULE, update_table_disconnect_on_timeout, [Table, 5000, self()]), receive ok -> ok = odbc:commit(Ref, commit); nok -> test_server:fail(database_locker_failed) end. update_table_disconnect_on_timeout(Table, TimeOut, Pid) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1", case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of {'EXIT', timeout} -> ok = odbc:disconnect(Ref), Pid ! ok; {updated, 1} -> Pid ! nok end. %%------------------------------------------------------------------------- connection_closed(doc) -> ["Checks that you get an appropriate error message if you try to" " use a connection that has been closed"]; connection_closed(suite) -> []; connection_closed(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Table = ?config(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA char(10), PRIMARY KEY(ID))"), ok = odbc:disconnect(Ref), {error, connection_closed} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), {error, connection_closed} = odbc:select_count(Ref, "SELECT * FROM " ++ Table), {error, connection_closed} = odbc:first(Ref), {error, connection_closed} = odbc:last(Ref), {error, connection_closed} = odbc:next(Ref), {error, connection_closed} = odbc:prev(Ref), {error, connection_closed} = odbc:select(Ref, next, 3), {error, connection_closed} = odbc:commit(Ref, commit). %%------------------------------------------------------------------------- disable_scrollable_cursors(doc) -> ["Test disabling of scrollable cursors."]; disable_scrollable_cursors(suite) -> []; disable_scrollable_cursors(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{scrollable_cursors, off}]), Table = ?config(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"), {updated, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), {ok, _} = odbc:select_count(Ref, "SELECT ID FROM " ++ Table), NextResult = ?RDBMS:selected_ID(1, next), test_server:format("Expected: ~p~n", [NextResult]), Result = odbc:next(Ref), test_server:format("Got: ~p~n", [Result]), NextResult = Result, {error, scrollable_cursors_disabled} = odbc:first(Ref), {error, scrollable_cursors_disabled} = odbc:last(Ref), {error, scrollable_cursors_disabled} = odbc:prev(Ref), {error, scrollable_cursors_disabled} = odbc:select(Ref, {relative, 2}, 5), {error, scrollable_cursors_disabled} = odbc:select(Ref, {absolute, 2}, 5), {selected, _ColNames,[]} = odbc:select(Ref, next, 1). %%------------------------------------------------------------------------- return_rows_as_lists(doc)-> ["Test the option that a row may be returned as a list instead " "of a tuple. Too be somewhat backward compatible."]; return_rows_as_lists(suite) -> []; return_rows_as_lists(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{tuple_row, off}] ++ odbc_test_lib:platform_options()), Table = ?config(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"), {updated, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"), {updated, _} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'foo')"), ListRows = ?RDBMS:selected_list_rows(), ListRows = odbc:sql_query(Ref, "SELECT * FROM " ++ Table), {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table), case proplists:get_value(scrollable_cursors, odbc_test_lib:platform_options()) of off -> Next = ?RDBMS:next_list_rows(), Next = odbc:next(Ref); _ -> First = ?RDBMS:first_list_rows(), Last = ?RDBMS:last_list_rows(), Prev = ?RDBMS:prev_list_rows(), Next = ?RDBMS:next_list_rows(), Last = odbc:last(Ref), Prev = odbc:prev(Ref), First = odbc:first(Ref), Next = odbc:next(Ref) end. %%------------------------------------------------------------------------- api_missuse(doc)-> ["Test that behaviour of the control process if the api is abused"]; api_missuse(suite) -> []; api_missuse(Config) when is_list(Config)-> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), %% Serious programming fault, connetion will be shut down gen_server:call(Ref, {self(), foobar, 10}, infinity), test_server:sleep(10), undefined = process_info(Ref, status), {ok, Ref2} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), %% Serious programming fault, connetion will be shut down gen_server:cast(Ref2, {self(), foobar, 10}), test_server:sleep(10), undefined = process_info(Ref2, status), {ok, Ref3} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), %% Could be an innocent misstake the connection lives. Ref3 ! foobar, test_server:sleep(10), {status, _} = process_info(Ref3, status). transaction_support_str(mysql) -> "ENGINE = InnoDB"; transaction_support_str(_) -> "". %%------------------------------------------------------------------------- extended_errors(doc)-> ["Test the extended errors connection option: When off; the old behaviour of just an error " "string is returned on error. When on, the error string is replaced by a 3 element tuple " "that also exposes underlying ODBC provider error codes."]; extended_errors(suite) -> []; extended_errors(Config) when is_list(Config)-> Table = ?config(tableName, Config), {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), {updated, _} = odbc:sql_query(Ref, "create table " ++ Table ++" ( id integer, data varchar(10))"), % Error case WITHOUT extended errors on... case odbc:sql_query(Ref, "create table " ++ Table ++" ( id integer, data varchar(10))") of {error, ErrorString} when is_list(ErrorString) -> ok end, % Now the test case with extended errors on - This should return a tuple, not a list/string now. % The first element is a string that is the ODBC error string; the 2nd element is a native integer error % code passed from the underlying provider driver. The last is the familiar old error string. % We can't check the actual error code; as each different underlying provider will return % a different value - So we just check the return types at least. {ok, RefExtended} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options() ++ [{extended_errors, on}]), case odbc:sql_query(RefExtended, "create table " ++ Table ++" ( id integer, data varchar(10))") of {error, {ODBCCodeString, NativeCodeNum, ShortErrorString}} when is_list(ODBCCodeString), is_number(NativeCodeNum), is_list(ShortErrorString) -> ok end, ok = odbc:disconnect(Ref), ok = odbc:disconnect(RefExtended). is_odbcserver(Name) -> case re:run(Name, "odbcserver") of {match, _} -> true; _ -> false end.