aboutsummaryrefslogblamecommitdiffstats
path: root/lib/odbc/test/odbc_connect_SUITE.erl
blob: 7d732f20f77189e07ac0c772e7a1a37fb9aaf75d (plain) (tree)
1
2
3
4


                   
                                                        





















                                                                         
                                           














                                                                      
 
                                         
 
         






                                                                 
                                                                 

                              


                      

                                               

                                     
           

                                    
           
 









                                                                       



















                                                                                                        






                                                                      
                           




























                                                                       




                                                                                           
                                    
                                     








                                                                           
                                                                                        

                                       

                                               


                                                  
                                                                      























                                                                          
                              





                                                                           


                                                                                        
                                       
 
                                               
 

                           
                                                  

                                                                          


                                                                           
                  
                                                 
                                                             

                                          
                  
                                                       
                  
                                                 
                                                             

                                              
                  

                                                       

                                               
 
                              






                                                                               

                                                                       
                                          
                              





                                                                               

                                                                
                                                                         
                           













                                                                           

                                                                    













                                                                          
                                                                                            






                                                                   

                                          





                                                                           
                                                                                            



                                                           

                                                  
                                              
 























                                                                           
                                                                                            






























                                                                           
                                                                                            






























                                                                           
                                                                                            













                                                                           

                                                                                  

                                                                  











                                                                           

                                               


                                                  
                                                                                       






























                                                                             
                              



                                                         
                                                                                        



















                                                                             



                                                                   




                                  
                              






                                                                           
                                                                                        

                                       
                                               



                                                  
                                                                                       


















                                                                           
                              




                                                         
                                                                                        





                                                                            
                              












                                                                           
                                                                       



                                                         
                                                                                        
                                       
                                               



                                                  
                                                                                        


































                                                                             
                              



                                                        
                                                                                       















                                                                             



                                                                   




                                  
                              






















                                                                           
                                                                                        
                                       
                                               



                                                  
                                                                                       






















                                                                           
                                                                                        















                                                                            
                                                                                            

















                                                                           
                                                          




































                                                                           
                                                         







                                                                           
                                                                                      



















                                                                           














                                                                                     







                                                                           
                                                                                            




                                                              

                                                                 




                                                              

                                                                 


                                                           
                                             
 



                                 





























                                                                                                                                                          
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2002-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights 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_node, 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) ->
    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(_TestCase, 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(_TestCase, 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_node(doc) ->
    "Test what happens if the port-program can not be found";
no_c_node(suite) -> [];
no_c_node(_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")),
    ok = file:rename(FileName1, FileName2),
    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.
%%------------------------------------------------------------------------

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),
    Port = lists:last(erlang:ports()),      
    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).

%%-------------------------------------------------------------------------
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),
    Port = lists:last(erlang:ports()),      
    {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?


%%-------------------------------------------------------------------------
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.
%%-------------------------------------------------------------------------
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).