1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2002-2013. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: 
   20: %%
   21: 
   22: -module(odbc_connect_SUITE).
   23: 
   24: %% Note: This directive should only be used in test suites.
   25: -compile(export_all).
   26: 
   27: -include_lib("common_test/include/ct.hrl").
   28: -include("test_server_line.hrl").
   29: -include("odbc_test.hrl").
   30: 
   31: -define(MAX_SEQ_TIMEOUTS, 10).
   32: 
   33: %%--------------------------------------------------------------------
   34: %% all(Arg) -> [Doc] | [Case] | {skip, Comment}
   35: %% Arg - doc | suite
   36: %% Doc - string()
   37: %% Case - atom() 
   38: %%	Name of a test case function. 
   39: %% Comment - string()
   40: %% Description: Returns documentation/test cases in this test suite
   41: %%		or a skip tuple if the platform is not supported.  
   42: %%--------------------------------------------------------------------
   43: 
   44: suite() -> [{ct_hooks,[ts_install_cth]}].
   45: 
   46: all() -> 
   47:     case odbc_test_lib:odbc_check() of
   48: 	ok ->
   49: 	    [not_exist_db, commit, rollback, not_explicit_commit,
   50: 	     no_c_node, port_dies, control_process_dies,
   51: 	     {group, client_dies}, connect_timeout, timeout,
   52: 	     many_timeouts, timeout_reset, disconnect_on_timeout,
   53: 	     connection_closed, disable_scrollable_cursors,
   54: 	     return_rows_as_lists, api_missuse, extended_errors];
   55: 	Other -> {skip, Other}
   56:     end.
   57: 
   58: groups() -> 
   59:     [{client_dies, [],
   60:       [client_dies_normal, client_dies_timeout,
   61:        client_dies_error]}].
   62: 
   63: init_per_group(_GroupName, Config) ->
   64:     Config.
   65: 
   66: end_per_group(_GroupName, Config) ->
   67:     Config.
   68: 
   69: 
   70: %%--------------------------------------------------------------------
   71: %% Function: init_per_suite(Config) -> Config
   72: %% Config - [tuple()]
   73: %%   A list of key/value pairs, holding the test case configuration.
   74: %% Description: Initiation before the whole suite
   75: %%
   76: %% Note: This function is free to add any key/value pairs to the Config
   77: %% variable, but should NOT alter/remove any existing entries.
   78: %%--------------------------------------------------------------------
   79: init_per_suite(Config) when is_list(Config) ->
   80:     file:write_file(filename:join([proplists:get_value(priv_dir,Config),
   81: 				   "..","..","..","ignore_core_files"]),""),
   82:     case odbc_test_lib:skip() of
   83: 	true ->
   84: 	    {skip, "ODBC not supported"};
   85: 	false ->
   86: 	    case (catch odbc:start()) of
   87: 		ok ->
   88: 		    case catch odbc:connect(?RDBMS:connection_string(),
   89: 					    [{auto_commit, off}] ++ odbc_test_lib:platform_options()) of
   90: 			{ok, Ref} ->
   91: 			    odbc:disconnect(Ref),
   92: 			    [{tableName, odbc_test_lib:unique_table_name()} | Config];
   93: 			_  ->
   94: 			    {skip, "ODBC is not properly setup"}
   95: 		    end;
   96: 		_ ->
   97: 		    {skip,"ODBC not startable"}
   98: 	    end
   99:     end.
  100: 
  101: %%--------------------------------------------------------------------
  102: %% Function: end_per_suite(Config) -> _
  103: %% Config - [tuple()]
  104: %%   A list of key/value pairs, holding the test case configuration.
  105: %% Description: Cleanup after the whole suite
  106: %%--------------------------------------------------------------------
  107: end_per_suite(_Config) ->
  108:     application:stop(odbc).
  109: 
  110: %%--------------------------------------------------------------------
  111: %% Function: init_per_testcase(Case, Config) -> Config
  112: %% Case - atom()
  113: %%   Name of the test case that is about to be run.
  114: %% Config - [tuple()]
  115: %%   A list of key/value pairs, holding the test case configuration.
  116: %%
  117: %% Description: Initiation before each test case
  118: %%
  119: %% Note: This function is free to add any key/value pairs to the Config
  120: %% variable, but should NOT alter/remove any existing entries.
  121: %%--------------------------------------------------------------------
  122: init_per_testcase(_TestCase, Config) ->
  123:     test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]),
  124:     Dog = test_server:timetrap(?default_timeout),
  125:     Temp = lists:keydelete(connection_ref, 1, Config),
  126:     NewConfig = lists:keydelete(watchdog, 1, Temp),
  127:     [{watchdog, Dog} | NewConfig].
  128: 
  129: %%--------------------------------------------------------------------
  130: %% Function: end_per_testcase(Case, Config) -> _
  131: %% Case - atom()
  132: %%   Name of the test case that is about to be run.
  133: %% Config - [tuple()]
  134: %%   A list of key/value pairs, holding the test case configuration.
  135: %% Description: Cleanup after each test case
  136: %%--------------------------------------------------------------------
  137: end_per_testcase(_TestCase, Config) ->
  138:     Table = ?config(tableName, Config),
  139:     {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  140:     Result = odbc:sql_query(Ref, "DROP TABLE " ++ Table),
  141:     io:format("Drop table: ~p ~p~n", [Table, Result]),
  142:     odbc:disconnect(Ref),
  143:     Dog = ?config(watchdog, Config),
  144:     test_server:timetrap_cancel(Dog).
  145: 
  146: %%-------------------------------------------------------------------------
  147: %% Test cases starts here.
  148: %%-------------------------------------------------------------------------
  149: commit(doc)->
  150:     ["Test the use of explicit commit"];
  151: commit(suite) -> [];
  152: commit(Config)  ->
  153:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(), 
  154: 			      [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  155: 
  156:     Table = ?config(tableName, Config),
  157:     TransStr = transaction_support_str(?RDBMS),
  158: 
  159:     {updated, _} = 
  160: 	odbc:sql_query(Ref, 
  161: 		       "CREATE TABLE " ++ Table ++
  162: 		       " (ID integer, DATA varchar(10))" ++ TransStr),
  163: 
  164:     {updated, 1} = 
  165: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1,'bar')"),
  166: 
  167:     {updated, 1} = 
  168: 	odbc:sql_query(Ref, "UPDATE " ++ Table ++
  169: 		       " SET DATA = 'foo' WHERE ID = 1"),
  170: 
  171:     ok = odbc:commit(Ref, commit),
  172:     UpdateResult = ?RDBMS:update_result(),
  173:     UpdateResult = 
  174: 	odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
  175: 
  176:     {updated, 1} = 
  177: 	odbc:sql_query(Ref, "UPDATE " ++ Table ++
  178: 		       " SET DATA = 'bar' WHERE ID = 1"),
  179:     ok = odbc:commit(Ref, commit, ?TIMEOUT),
  180:     InsertResult = ?RDBMS:insert_result(),
  181:     InsertResult = 
  182: 	odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
  183: 
  184:     {'EXIT', {function_clause, _}} = 
  185: 	(catch odbc:commit(Ref, commit, -1)),
  186: 
  187:     ok = odbc:disconnect(Ref).
  188: %%-------------------------------------------------------------------------
  189: 
  190: rollback(doc)->
  191:     ["Test the use of explicit rollback"];
  192: rollback(suite) -> [];
  193: rollback(Config)  ->
  194:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  195: 			      [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  196: 
  197:     Table = ?config(tableName, Config),
  198: 
  199:     TransStr = transaction_support_str(?RDBMS),
  200: 
  201:     {updated, _} =
  202: 	odbc:sql_query(Ref,
  203: 		       "CREATE TABLE " ++ Table ++
  204: 			   " (ID integer, DATA varchar(10))" ++ TransStr),
  205:     {updated, 1} =
  206: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
  207:     ok = odbc:commit(Ref, commit),
  208: 
  209:     {updated, 1} =
  210: 	odbc:sql_query(Ref, "UPDATE " ++ Table ++
  211: 			   " SET DATA = 'foo' WHERE ID = 1"),
  212:     ok = odbc:commit(Ref, rollback),
  213:     InsertResult = ?RDBMS:insert_result(),
  214:     InsertResult =
  215: 	odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
  216:     {updated, 1} =
  217: 	odbc:sql_query(Ref, "UPDATE " ++ Table ++
  218: 			   " SET DATA = 'foo' WHERE ID = 1"),
  219:     ok = odbc:commit(Ref, rollback, ?TIMEOUT),
  220:     InsertResult = ?RDBMS:insert_result(),
  221:     InsertResult =
  222: 	odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
  223: 
  224:     {'EXIT', {function_clause, _}} =
  225: 	(catch odbc:commit(Ref, rollback, -1)),
  226: 
  227:     ok = odbc:disconnect(Ref).
  228: 
  229: %%-------------------------------------------------------------------------
  230: not_explicit_commit(doc) ->
  231:     ["Test what happens if you try using commit on a auto_commit connection."];
  232: not_explicit_commit(suite) -> [];
  233: not_explicit_commit(_Config) ->
  234:     {ok, Ref} = 
  235: 	odbc:connect(?RDBMS:connection_string(), [{auto_commit, on}] ++
  236: 		    odbc_test_lib:platform_options()),
  237:     {error, _} = odbc:commit(Ref, commit),
  238:     ok = odbc:disconnect(Ref).
  239: 
  240: %%-------------------------------------------------------------------------
  241: not_exist_db(doc) ->
  242:     ["Tests valid data format but invalid data in the connection parameters."];
  243: not_exist_db(suite) -> [];
  244: not_exist_db(_Config)  ->
  245:     {error, _} = odbc:connect("DSN=foo;UID=bar;PWD=foobar",
  246: 			      odbc_test_lib:platform_options()),
  247:     %% So that the odbc control server can be stoped "in the correct way"
  248:     test_server:sleep(100).
  249: 
  250: %%-------------------------------------------------------------------------
  251: no_c_node(doc) ->
  252:     "Test what happens if the port-program can not be found";
  253: no_c_node(suite) -> [];
  254: no_c_node(_Config) ->
  255:     process_flag(trap_exit, true),
  256:     Dir = filename:nativename(filename:join(code:priv_dir(odbc), 
  257: 					    "bin")),
  258:     FileName1 = filename:nativename(os:find_executable("odbcserver", 
  259: 						       Dir)),
  260:     FileName2 = filename:nativename(filename:join(Dir, "odbcsrv")),
  261:     ok = file:rename(FileName1, FileName2),
  262:     Result = 
  263: 	case catch odbc:connect(?RDBMS:connection_string(),
  264: 				odbc_test_lib:platform_options()) of
  265: 	    {error, port_program_executable_not_found} ->
  266: 		ok;
  267: 	    Else ->
  268: 		Else
  269: 	end,
  270: 
  271:     ok = file:rename(FileName2, FileName1), 
  272:     ok = Result.
  273: %%------------------------------------------------------------------------
  274: 
  275: port_dies(doc) ->
  276:     "Tests what happens if the port program dies";
  277: port_dies(suite) -> [];
  278: port_dies(_Config) ->
  279:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  280:     {status, _} = process_info(Ref, status),   
  281:     process_flag(trap_exit, true),
  282:     NamedPorts =  [{P,  erlang:port_info(P, name)} || P <- erlang:ports()],
  283:     case [P || {P, {name, Name}} <- NamedPorts,  is_odbcserver(Name)]  of
  284: 	[Port] ->
  285: 	    exit(Port, kill),
  286: 	    %% Wait for exit_status from port 5000 ms (will not get a exit
  287: 	    %% status in this case), then wait a little longer to make sure
  288: 	    %% the port and the controlprocess has had time to terminate.
  289: 	    test_server:sleep(10000),
  290: 	    undefined = process_info(Ref, status);
  291: 	[] ->
  292: 	    ct:fail([erlang:port_info(P, name) || P <- erlang:ports()])  
  293:     end.
  294: 
  295: 
  296: %%-------------------------------------------------------------------------
  297: control_process_dies(doc) ->
  298:     "Tests what happens if the Erlang control process dies";
  299: control_process_dies(suite) -> [];
  300: control_process_dies(_Config) ->
  301:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  302:     process_flag(trap_exit, true),
  303:     NamedPorts =  [{P,  erlang:port_info(P, name)} || P <- erlang:ports()],
  304:     case [P || {P, {name, Name}} <- NamedPorts,  is_odbcserver(Name)] of
  305: 	[Port] ->
  306: 	    {connected, Ref} = erlang:port_info(Port, connected),  
  307: 	    exit(Ref, kill),
  308: 	    test_server:sleep(500),
  309: 	    undefined = erlang:port_info(Port, connected);
  310: 	%% Check for c-program still running, how?
  311: 	[] ->
  312: 	    ct:fail([erlang:port_info(P, name) || P <- erlang:ports()])    
  313:     end.
  314: 
  315: %%-------------------------------------------------------------------------
  316: client_dies_normal(doc) ->
  317:     ["Client dies with reason normal."];
  318: client_dies_normal(suite) -> [];
  319: client_dies_normal(Config) when is_list(Config) ->
  320:     Pid = spawn(?MODULE, client_normal, [self()]),
  321: 
  322:     MonitorReference =
  323: 	receive 
  324: 	    {dbRef, Ref}  ->
  325: 		MRef = erlang:monitor(process, Ref),
  326: 		Pid ! continue,
  327: 		MRef
  328: 	end,
  329: 
  330:     receive 
  331: 	{'DOWN', MonitorReference, _Type, _Object, _Info} ->
  332: 	    ok
  333:     after 5000 ->
  334: 	    test_server:fail(control_process_not_stopped)
  335:     end.
  336: 
  337: client_normal(Pid) ->
  338:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  339:     Pid ! {dbRef, Ref},
  340:     receive 
  341: 	continue ->
  342: 	    ok
  343:     end,
  344:     exit(self(), normal).
  345: 
  346: 
  347: %%-------------------------------------------------------------------------
  348: client_dies_timeout(doc) ->
  349:     ["Client dies with reason timeout."];
  350: client_dies_timeout(suite) -> [];
  351: client_dies_timeout(Config) when is_list(Config) ->
  352:     Pid = spawn(?MODULE, client_timeout, [self()]),
  353: 
  354:     MonitorReference =
  355: 	receive 
  356: 	    {dbRef, Ref}  ->
  357: 		MRef = erlang:monitor(process, Ref),
  358: 		Pid ! continue,
  359: 		MRef
  360: 	end,
  361: 
  362:     receive 
  363: 	{'DOWN', MonitorReference, _Type, _Object, _Info} ->
  364: 	    ok
  365:     after 5000 ->
  366: 	    test_server:fail(control_process_not_stopped)
  367:     end.
  368: 
  369: client_timeout(Pid) ->
  370:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  371:     Pid ! {dbRef, Ref},
  372:     receive 
  373: 	continue ->
  374: 	    ok
  375:     end,
  376:     exit(self(), timeout).
  377: 
  378: 
  379: %%-------------------------------------------------------------------------
  380: client_dies_error(doc) ->
  381:     ["Client dies with reason error."];
  382: client_dies_error(suite) -> [];
  383: client_dies_error(Config) when is_list(Config) ->
  384:     Pid = spawn(?MODULE, client_error, [self()]),
  385: 
  386:     MonitorReference =
  387: 	receive 
  388: 	    {dbRef, Ref}  ->
  389: 		MRef = erlang:monitor(process, Ref),
  390: 		Pid ! continue,
  391: 		MRef
  392: 	end,
  393: 
  394:     receive 
  395: 	{'DOWN', MonitorReference, _Type, _Object, _Info} ->
  396: 	    ok
  397:     after 5000 ->
  398: 	    test_server:fail(control_process_not_stopped)
  399:     end.
  400: 
  401: client_error(Pid) ->
  402:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  403:     Pid ! {dbRef, Ref},
  404:     receive 
  405: 	continue ->
  406: 	    ok
  407:     end,
  408:     exit(self(), error).
  409: 
  410: 
  411: %%-------------------------------------------------------------------------
  412: connect_timeout(doc) ->
  413:     ["Test the timeout for the connect function."];
  414: connect_timeout(suite) -> [];
  415: connect_timeout(Config) when is_list(Config) ->
  416:     {'EXIT',timeout} = (catch odbc:connect(?RDBMS:connection_string(),
  417: 					   [{timeout, 0}] ++
  418: 					       odbc_test_lib:platform_options())),
  419:     %% Need to return ok here "{'EXIT',timeout} return value" will
  420:     %% be interpreted as that the testcase has timed out.
  421:     ok.
  422: %%-------------------------------------------------------------------------
  423: timeout(doc) ->
  424:     ["Test that timeouts don't cause unwanted behavior sush as receiving"
  425:      " an anwser to a previously tiemed out query."];
  426: timeout(suite) -> [];
  427: timeout(Config)  when is_list(Config) ->
  428: 
  429:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  430: 			      [{auto_commit, off}]),
  431:     Table = ?config(tableName, Config),
  432: 
  433:     TransStr = transaction_support_str(?RDBMS),
  434: 
  435:     {updated, _} = 
  436: 	odbc:sql_query(Ref, 
  437: 		       "CREATE TABLE " ++ Table ++
  438: 		       " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr),
  439: 
  440:     {updated, 1} = 
  441: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
  442: 
  443:     ok = odbc:commit(Ref, commit),
  444: 
  445:     {updated, 1} = 
  446: 	odbc:sql_query(Ref, "UPDATE " ++ Table ++
  447: 		       " SET DATA = 'foo' WHERE ID = 1"),
  448: 
  449:     {updated, 1} = 
  450: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'baz')"),
  451: 
  452:     Pid = spawn_link(?MODULE, update_table_timeout, [Table, 5000, self()]),
  453: 
  454:     receive 
  455: 	timout_occurred ->
  456: 	    ok = odbc:commit(Ref, commit),
  457: 	    Pid ! continue
  458:     end,
  459: 
  460:     receive 
  461: 	altered ->
  462: 	    ok
  463:     end,
  464: 
  465:     {selected, Fields, [{"foobar"}]} = 
  466: 	odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 1"),
  467:     ["DATA"] = odbc_test_lib:to_upper(Fields),
  468: 
  469:     ok = odbc:commit(Ref, commit),
  470:     ok = odbc:disconnect(Ref).
  471: 
  472: update_table_timeout(Table, TimeOut, Pid) ->
  473: 
  474:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  475: 			      [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  476:     UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
  477: 
  478:     case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
  479: 	{'EXIT', timeout} ->
  480: 	    Pid ! timout_occurred;
  481: 	{updated, 1} ->
  482: 	    test_server:fail(database_locker_failed)
  483:     end,
  484: 
  485:     receive 
  486: 	continue ->
  487: 	    ok
  488:     end,
  489: 
  490:     %% Make sure we receive the correct result and not the answer
  491:     %% to the previous query.
  492:     {selected, Fields, [{"baz"}]} = 
  493: 	odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"),
  494:     ["DATA"] = odbc_test_lib:to_upper(Fields),
  495: 
  496:     %% Do not check {updated, 1} as some drivers will return 0
  497:     %% even though the update is done, which is checked by the test
  498:     %% case when the altered message is recived.
  499:     {updated, _} = odbc:sql_query(Ref, UpdateQuery, TimeOut),
  500: 
  501:     ok = odbc:commit(Ref, commit),
  502: 
  503:     Pid ! altered,
  504: 
  505:     ok = odbc:disconnect(Ref).
  506: %%-------------------------------------------------------------------------
  507: many_timeouts(doc) ->
  508:     ["Tests that many consecutive timeouts lead to that the connection "
  509:      "is shutdown."];
  510: many_timeouts(suite) -> [];
  511: many_timeouts(Config) when is_list(Config) ->
  512:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  513: 			      [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  514: 
  515:     Table = ?config(tableName, Config),
  516:     TransStr = transaction_support_str(?RDBMS),
  517: 
  518:     {updated, _} = 
  519: 	odbc:sql_query(Ref, 
  520: 		       "CREATE TABLE " ++ Table ++
  521: 		       " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr),
  522: 
  523:     {updated, 1} = 
  524: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
  525: 
  526:     ok = odbc:commit(Ref, commit),
  527: 
  528:     {updated, 1} = 
  529: 	odbc:sql_query(Ref, "UPDATE " ++ Table ++
  530: 		       " SET DATA = 'foo' WHERE ID = 1"),
  531: 
  532:     _Pid = spawn_link(?MODULE, update_table_many_timeouts, 
  533: 		     [Table, 5000, self()]),
  534: 
  535:     receive 
  536: 	many_timeouts_occurred ->
  537: 	    ok
  538:     end,
  539: 
  540:     ok = odbc:commit(Ref, commit),
  541:     ok = odbc:disconnect(Ref).
  542: 
  543: 
  544: update_table_many_timeouts(Table, TimeOut, Pid) ->
  545: 
  546:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  547: 			      [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  548:     UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
  549: 
  550:     ok = loop_many_timouts(Ref, UpdateQuery, TimeOut),
  551: 
  552:     Pid ! many_timeouts_occurred, 
  553: 
  554:     ok = odbc:disconnect(Ref).
  555: 
  556: 
  557: loop_many_timouts(Ref, UpdateQuery, TimeOut) ->
  558:     case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
  559: 	{'EXIT',timeout} ->
  560: 	    loop_many_timouts(Ref, UpdateQuery, TimeOut);
  561: 	{updated, 1} ->
  562: 	    test_server:fail(database_locker_failed);
  563: 	{error, connection_closed} ->
  564: 	    ok
  565:     end.
  566: %%-------------------------------------------------------------------------
  567: timeout_reset(doc) ->
  568:     ["Check that the number of consecutive timouts is reset to 0 when "
  569:      "a successful call to the database is made."];
  570: timeout_reset(suite) -> [];
  571: timeout_reset(Config) when is_list(Config) ->
  572:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  573: 			      [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  574:     Table = ?config(tableName, Config),
  575:     TransStr = transaction_support_str(?RDBMS),
  576: 
  577:     {updated, _} = 
  578: 	odbc:sql_query(Ref, 
  579: 		       "CREATE TABLE " ++ Table ++
  580: 		       " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++  TransStr),
  581: 
  582:     {updated, 1} = 
  583: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
  584: 
  585:     ok = odbc:commit(Ref, commit),
  586: 
  587:     {updated, 1} = 
  588: 	odbc:sql_query(Ref, "UPDATE " ++ Table ++
  589: 		       " SET DATA = 'foo' WHERE ID = 1"),
  590: 
  591:     {updated, 1} = 
  592: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'baz')"),
  593: 
  594: 
  595:     Pid = spawn_link(?MODULE, update_table_timeout_reset, 
  596: 		     [Table, 5000, self()]),
  597: 
  598:     receive 
  599: 	many_timeouts_occurred ->
  600: 	    ok
  601:     end,
  602: 
  603:     ok = odbc:commit(Ref, commit),
  604:     Pid ! continue,
  605: 
  606:     receive 
  607: 	altered ->
  608: 	    ok
  609:     end,
  610: 
  611:     {selected, Fields, [{"foobar"}]} = 
  612: 	odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 1"),
  613:     ["DATA"] = odbc_test_lib:to_upper(Fields),
  614: 
  615:     ok = odbc:commit(Ref, commit),
  616:     ok = odbc:disconnect(Ref).
  617: 
  618: update_table_timeout_reset(Table, TimeOut, Pid) ->
  619: 
  620:     {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
  621: 			     [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  622:     UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
  623: 
  624:     ok = loop_timout_reset(Ref, UpdateQuery, TimeOut, 
  625: 			   ?MAX_SEQ_TIMEOUTS-1),
  626: 
  627:     Pid ! many_timeouts_occurred,
  628: 
  629:     receive 
  630: 	continue ->
  631: 	    ok
  632:     end,
  633: 
  634:     {selected, Fields, [{"baz"}]} =
  635: 	odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"),
  636:     ["DATA"] = odbc_test_lib:to_upper(Fields),
  637: 
  638:     %% Do not check {updated, 1} as some drivers will return 0
  639:     %% even though the update is done, which is checked by the test
  640:     %% case when the altered message is recived.
  641:     {updated, _} = odbc:sql_query(Ref, UpdateQuery, TimeOut),
  642: 
  643:     ok = odbc:commit(Ref, commit),
  644: 
  645:     Pid ! altered,
  646: 
  647:     ok = odbc:disconnect(Ref).
  648: 
  649: loop_timout_reset(_, _, _, 0) ->
  650:     ok;
  651: 
  652: loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts) ->
  653:     case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
  654: 	{'EXIT',timeout} ->
  655: 	    loop_timout_reset(Ref, UpdateQuery, 
  656: 			      TimeOut, NumTimeouts - 1);
  657: 	{updated, 1} ->
  658: 	    test_server:fail(database_locker_failed);
  659: 	{error, connection_closed} ->
  660: 	    test_server:fail(connection_closed_premature)
  661:     end.
  662: 
  663: %%-------------------------------------------------------------------------
  664: 
  665: disconnect_on_timeout(doc) ->
  666:     ["Check that disconnect after a time out works properly"]; 
  667: disconnect_on_timeout(suite) -> [];
  668: disconnect_on_timeout(Config) when is_list(Config) ->
  669: 
  670:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  671: 			      [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  672:     Table = ?config(tableName, Config),
  673:     TransStr = transaction_support_str(?RDBMS),
  674: 
  675:     {updated, _} = 
  676: 	odbc:sql_query(Ref, 
  677: 		       "CREATE TABLE " ++ Table ++
  678: 		       " (ID integer, DATA varchar(10), PRIMARY KEY(ID))" ++ TransStr),
  679: 
  680:     {updated, 1} = 
  681: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
  682: 
  683:     ok = odbc:commit(Ref, commit),
  684: 
  685:     {updated, 1} = 
  686: 	odbc:sql_query(Ref, "UPDATE " ++ Table ++
  687: 		       " SET DATA = 'foo' WHERE ID = 1"),
  688: 
  689: 
  690:     _Pid = spawn_link(?MODULE, update_table_disconnect_on_timeout,
  691: 		     [Table, 5000, self()]),
  692:     receive 
  693: 	ok ->
  694: 	    ok = odbc:commit(Ref, commit);
  695: 	nok ->
  696: 	    test_server:fail(database_locker_failed)
  697:     end.
  698: 
  699: update_table_disconnect_on_timeout(Table, TimeOut, Pid) ->
  700: 
  701:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  702: 			      [{auto_commit, off}] ++ odbc_test_lib:platform_options()),
  703:     UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
  704: 
  705:     case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
  706: 	{'EXIT', timeout} ->
  707: 	    ok = odbc:disconnect(Ref),
  708: 	    Pid ! ok;
  709: 	{updated, 1} ->
  710: 	    Pid ! nok
  711:     end.
  712: 
  713: %%-------------------------------------------------------------------------
  714: connection_closed(doc) ->
  715:     ["Checks that you get an appropriate error message if you try to"
  716:      " use a connection that has been closed"];
  717: connection_closed(suite) -> [];
  718: connection_closed(Config) when is_list(Config) ->
  719:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  720: 
  721:     Table = ?config(tableName, Config), 
  722:     {updated, _} = 
  723: 	odbc:sql_query(Ref, 
  724: 		       "CREATE TABLE " ++ Table ++
  725: 		       " (ID integer, DATA char(10), PRIMARY KEY(ID))"),
  726: 
  727:     ok = odbc:disconnect(Ref),
  728: 
  729:     {error, connection_closed} = 
  730: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
  731:     {error, connection_closed} =  
  732: 	odbc:select_count(Ref, "SELECT * FROM " ++ Table),
  733:     {error, connection_closed} = odbc:first(Ref),
  734:     {error, connection_closed} = odbc:last(Ref),
  735:     {error, connection_closed} = odbc:next(Ref),
  736:     {error, connection_closed} = odbc:prev(Ref),
  737:     {error, connection_closed} = odbc:select(Ref, next, 3),
  738:     {error, connection_closed} = odbc:commit(Ref, commit).
  739: 
  740: %%-------------------------------------------------------------------------
  741: disable_scrollable_cursors(doc) ->
  742:     ["Test disabling of scrollable cursors."];
  743: disable_scrollable_cursors(suite) -> [];
  744: disable_scrollable_cursors(Config) when is_list(Config) ->
  745:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  746: 			      [{scrollable_cursors, off}]),
  747: 
  748:     Table = ?config(tableName, Config),
  749: 
  750:     {updated, _} = 
  751: 	odbc:sql_query(Ref, 
  752: 		       "CREATE TABLE " ++ Table ++
  753: 		       " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
  754: 
  755:     {updated, _} = 
  756: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
  757: 
  758:     {ok, _} = odbc:select_count(Ref, "SELECT ID FROM " ++ Table),
  759: 
  760:     NextResult = ?RDBMS:selected_ID(1, next),
  761: 
  762:     test_server:format("Expected: ~p~n", [NextResult]),
  763: 
  764:     Result = odbc:next(Ref),
  765:     test_server:format("Got: ~p~n", [Result]),
  766:     NextResult = Result,
  767: 
  768:     {error, scrollable_cursors_disabled} = odbc:first(Ref),
  769:     {error, scrollable_cursors_disabled} = odbc:last(Ref),
  770:     {error, scrollable_cursors_disabled} = odbc:prev(Ref),
  771:     {error, scrollable_cursors_disabled} = 
  772: 	odbc:select(Ref, {relative, 2}, 5),
  773:     {error, scrollable_cursors_disabled} =
  774: 	odbc:select(Ref, {absolute, 2}, 5),
  775: 
  776:     {selected, _ColNames,[]} = odbc:select(Ref, next, 1).
  777: 
  778: %%-------------------------------------------------------------------------
  779: return_rows_as_lists(doc)->
  780:     ["Test the option that a row may be returned as a list instead " 
  781:      "of a tuple. Too be somewhat backward compatible."];
  782: return_rows_as_lists(suite) -> [];
  783: return_rows_as_lists(Config) when is_list(Config) ->
  784:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(),
  785: 			      [{tuple_row, off}] ++ odbc_test_lib:platform_options()),
  786: 
  787:     Table = ?config(tableName, Config),
  788: 
  789:     {updated, _} = 
  790: 	odbc:sql_query(Ref, 
  791: 		       "CREATE TABLE " ++ Table ++
  792: 		       " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
  793: 
  794:     {updated, _} = 
  795: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
  796: 
  797:     {updated, _} = 
  798: 	odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'foo')"),
  799: 
  800:     ListRows = ?RDBMS:selected_list_rows(),
  801:     ListRows = 
  802: 	odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
  803: 
  804:     {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
  805: 
  806:     case proplists:get_value(scrollable_cursors, odbc_test_lib:platform_options()) of
  807: 	off ->
  808: 	    Next = ?RDBMS:next_list_rows(),
  809: 	    Next = odbc:next(Ref);
  810: 	_ ->
  811: 	    First = ?RDBMS:first_list_rows(),
  812: 	    Last =  ?RDBMS:last_list_rows(),
  813: 	    Prev = ?RDBMS:prev_list_rows(),
  814: 	    Next = ?RDBMS:next_list_rows(),
  815: 
  816: 	    Last = odbc:last(Ref),
  817: 	    Prev = odbc:prev(Ref),
  818: 	    First = odbc:first(Ref),
  819: 	    Next = odbc:next(Ref)
  820:     end.
  821: 
  822: %%-------------------------------------------------------------------------
  823: 
  824: api_missuse(doc)->
  825:     ["Test that behaviour of the control process if the api is abused"];
  826: api_missuse(suite) -> [];
  827: api_missuse(Config) when is_list(Config)->
  828: 
  829:     {ok, Ref} =  odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  830:     %% Serious programming fault, connetion will be shut down 
  831:     gen_server:call(Ref, {self(), foobar, 10}, infinity),
  832:     test_server:sleep(10),
  833:     undefined = process_info(Ref, status),
  834: 
  835:     {ok, Ref2} =  odbc:connect(?RDBMS:connection_string(),
  836: 			       odbc_test_lib:platform_options()),
  837:     %% Serious programming fault, connetion will be shut down 
  838:     gen_server:cast(Ref2, {self(), foobar, 10}),
  839:     test_server:sleep(10),
  840:     undefined = process_info(Ref2, status),
  841: 
  842:     {ok, Ref3} =  odbc:connect(?RDBMS:connection_string(),
  843: 			       odbc_test_lib:platform_options()),
  844:     %% Could be an innocent misstake the connection lives. 
  845:     Ref3 ! foobar, 
  846:     test_server:sleep(10),
  847:     {status, _} = process_info(Ref3, status).
  848: 
  849: transaction_support_str(mysql) ->
  850:     "ENGINE = InnoDB";
  851: transaction_support_str(_) ->
  852:     "".
  853: 
  854: 
  855: %%-------------------------------------------------------------------------
  856: extended_errors(doc)->
  857:     ["Test the extended errors connection option: When off; the old behaviour of just an error "
  858:      "string is returned on error. When on, the error string is replaced by a 3 element tuple "
  859:      "that also exposes underlying ODBC provider error codes."];
  860: extended_errors(suite)  -> [];
  861: extended_errors(Config) when is_list(Config)->
  862:     Table = ?config(tableName, Config),
  863:     {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()),
  864:     {updated, _} = odbc:sql_query(Ref, "create table " ++ Table ++" ( id integer, data varchar(10))"),
  865: 
  866:     % Error case WITHOUT extended errors on...
  867:     case odbc:sql_query(Ref, "create table " ++ Table ++" ( id integer, data varchar(10))") of
  868:         {error, ErrorString} when is_list(ErrorString) -> ok
  869:     end,
  870: 
  871:     % Now the test case with extended errors on - This should return a tuple, not a list/string now.
  872:     % The first element is a string that is the ODBC error string; the 2nd element is a native integer error
  873:     % code passed from the underlying provider driver. The last is the familiar old error string.
  874:     % We can't check the actual error code; as each different underlying provider will return
  875:     % a different value - So we just check the return types at least.
  876:     {ok, RefExtended} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options() ++ [{extended_errors, on}]),
  877:     case odbc:sql_query(RefExtended, "create table " ++ Table ++" ( id integer, data varchar(10))") of
  878:         {error, {ODBCCodeString, NativeCodeNum, ShortErrorString}} when is_list(ODBCCodeString), is_number(NativeCodeNum), is_list(ShortErrorString) -> ok
  879:     end,
  880: 
  881:     ok = odbc:disconnect(Ref),
  882:     ok = odbc:disconnect(RefExtended).
  883: 
  884: 
  885: is_odbcserver(Name) ->
  886:     case re:run(Name, "odbcserver") of
  887: 	{match, _} ->
  888: 	    true;
  889: 	_ ->
  890: 	    false
  891:     end.
  892: