1: %%============================================================================== 2: %% Copyright 2016 Erlang Solutions Ltd. 3: %% 4: %% Licensed under the Apache License, Version 2.0 (the "License"); 5: %% you may not use this file except in compliance with the License. 6: %% You may obtain a copy of the License at 7: %% 8: %% http://www.apache.org/licenses/LICENSE-2.0 9: %% 10: %% Unless required by applicable law or agreed to in writing, software 11: %% distributed under the License is distributed on an "AS IS" BASIS, 12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13: %% See the License for the specific language governing permissions and 14: %% limitations under the License. 15: %%============================================================================== 16: 17: -module(rdbms_SUITE). 18: -compile([export_all, nowarn_export_all]). 19: 20: -include_lib("common_test/include/ct.hrl"). 21: -include_lib("eunit/include/eunit.hrl"). 22: 23: %% We need assert from it 24: -include("mam_helper.hrl"). 25: 26: -import(domain_helper, [host_type/0]). 27: 28: -import(distributed_helper, [mim/0, rpc/4]). 29: 30: %%-------------------------------------------------------------------- 31: %% Suite configuration 32: %%-------------------------------------------------------------------- 33: 34: all() -> 35: [{group, rdbms_queries}]. 36: 37: groups() -> 38: [{rdbms_queries, [], rdbms_queries_cases()}]. 39: 40: rdbms_queries_cases() -> 41: [select_one_works_case, 42: select_ascii_string_works_case, 43: read_int32_case, 44: read_int64_case, 45: read_unicode_case, 46: read_unicode250_case, 47: read_ascii_char_case, 48: read_ascii_string_case, 49: read_binary_8k_case, 50: read_binary_65k_case, 51: read_binary_16m_case, 52: read_enum_char_case, 53: read_boolean_case, 54: 55: read_prep_int32_case, 56: read_prep_int64_case, 57: read_prep_unicode_case, 58: read_prep_unicode250_case, 59: read_prep_ascii_char_case, 60: read_prep_ascii_string_case, 61: read_prep_binary_8k_case, 62: read_prep_binary_65k_case, 63: read_prep_binary_16m_case, 64: read_prep_enum_char_case, 65: read_prep_boolean_case, 66: select_current_timestamp_case, 67: 68: select_like_case, 69: select_like_prep_case, 70: 71: insert_batch_with_null_case, 72: test_cast_insert, 73: test_request_insert, 74: test_wrapped_request, 75: test_failed_wrapper, 76: test_request_transaction, 77: test_restart_transaction_with_execute, 78: test_restart_transaction_with_execute_eventually_passes, 79: test_failed_transaction_with_execute_wrapped, 80: test_failed_wrapper_transaction, 81: test_incremental_upsert, 82: arguments_from_two_tables]. 83: 84: suite() -> 85: escalus:suite(). 86: 87: %%-------------------------------------------------------------------- 88: %% Init & teardown 89: %%-------------------------------------------------------------------- 90: init_per_suite(Config) -> 91: case not ct_helper:is_ct_running() 92: orelse mongoose_helper:is_rdbms_enabled(host_type()) of 93: false -> {skip, rdbms_or_ct_not_running}; 94: true -> 95: Pools = rpc(mim(), mongoose_config, get_opt, [outgoing_pools]), 96: GlobalRdbmsPool = stop_global_default_pool(Pools), 97: start_local_host_type_pool(Pools), 98: %% Warning: inject_module does not really work well with --rerun-big-tests flag 99: mongoose_helper:inject_module(?MODULE), 100: escalus:init_per_suite([{global_default_rdbms_pool, GlobalRdbmsPool} | Config]) 101: end. 102: 103: end_per_suite(Config) -> 104: escalus:end_per_suite(Config), 105: GlobalRdbmsPool = ?config(global_default_rdbms_pool, Config), 106: rpc(mim(), mongoose_wpool, start_configured_pools, [[GlobalRdbmsPool]]). 107: 108: init_per_testcase(test_incremental_upsert, Config) -> 109: erase_inbox(Config), 110: escalus:init_per_testcase(test_incremental_upsert, Config); 111: init_per_testcase(CaseName, Config) -> 112: escalus:init_per_testcase(CaseName, Config). 113: 114: end_per_testcase(CaseName, Config) 115: when CaseName =:= test_restart_transaction_with_execute; 116: CaseName =:= test_restart_transaction_with_execute_eventually_passes; 117: CaseName =:= test_failed_transaction_with_execute_wrapped; 118: CaseName =:= test_failed_wrapper; 119: CaseName =:= test_failed_wrapper_transaction -> 120: rpc(mim(), meck, unload, []), 121: escalus:end_per_testcase(CaseName, Config); 122: end_per_testcase(test_incremental_upsert, Config) -> 123: erase_inbox(Config), 124: escalus:end_per_testcase(test_incremental_upsert, Config); 125: end_per_testcase(CaseName, Config) -> 126: escalus:end_per_testcase(CaseName, Config). 127: 128: %%-------------------------------------------------------------------- 129: %% Data for cases 130: %%-------------------------------------------------------------------- 131: 132: int32_values() -> 133: [1, -1, 0, 42, 2147483647, null]. 134: 135: int64_values() -> 136: [9223372036854775807, 137: null]. 138: 139: ascii_string_values() -> 140: [<<>>, <<"1">>, <<"test">>, 141: <<"\\">>, <<"\\\\">>, 142: <<"'">>, <<"''">>, <<"'''">>, 143: <<"\"">>, <<"\"\"">>, 144: <<"\r\n">>, <<"\r">>, <<"\n">>, 145: binary:copy(<<"a">>, 250), null]. 146: 147: unicode_values() -> 148: ascii_string_values() ++ 149: [<<"юникод"/utf8>>, <<"😁"/utf8>>] 150: ++ 151: %% Would fail with binary_data_8k and mssql. 152: %% For some reason mssql returns string "7878...." of length 4000. 153: %% What is 78? 16#78 = 120 = $x. 154: %% i.e. half of 8000 bytes for data. 155: %% Probably 2 bytes encoding is used for this. 156: % [binary:copy(<<$x>>, 4001), 157: %% Helps to debug if we don't consume all data from a buffer. 158: %% Than there would be a gap of missing numbers in the middle. 159: %% 1000 of 1-es, 1000 of 2-s, ..., 1000 of 10-s. 160: %% 161: %% In one version of eodbc, it returns 5,5,5,5... instead of 1,1,1,1... 162: %% 163: %% Also, 164: %% eodbc:sql_query(Conn, "SELECT convert(varbinary(max), binary_data_8k) FROM test_types") = gives correct result. 165: %% but 166: %% eodbc:sql_query(Conn, "SELECT binary_data_8k FROM test_types") = gives not correct result. 167: %% 168: %% eodbc:sql_query(Conn, "SELECT convert(varbinary(1000), binary_data_8k) FROM test_types") = gives correct result. 169: %% gives 010101.... as expected 170: [iolist_to_binary([lists:duplicate(1000, X) || X <- lists:seq(1, 10)]), 171: binary:copy(<<$a>>, 10000), 172: %% There is a bug with 8001 chars limit in upstream odbc 173: %% We use a fork arcusfelis/eodbc, that has the bug fixed 174: %% https://bugs.erlang.org/browse/ERL-421 175: binary:copy(<<10>>, 10000), null]. 176: 177: binary_values() -> 178: [<<0>>, <<"255">>, 179: <<240,159>>, %% Incomplete unicode 180: <<240,159,0>>, %% Invalid unicode 181: iolist_to_binary(lists:seq(0, 255)), 182: %% one kilobyte 183: binary:copy(<<1>>, 1024), 184: %% two kilobytes 185: binary:copy(<<2>>, 2048), 186: binary:copy(<<5>>, 1024*5), 187: %% There is a bug with 8001 chars limit in upstream odbc 188: %% We use a fork arcusfelis/eodbc, that has the bug fixed 189: %% https://bugs.erlang.org/browse/ERL-421 190: binary:copy(<<8>>, 8002), 191: binary:copy(<<0>>, 100000), 192: null 193: ] ++ 194: case is_odbc() orelse is_pgsql() of 195: true -> 196: []; 197: false -> 198: %% FIXME long data causes timeout with mssql 199: %% 200: %% FIXME %% epgsql_sock:handle_info/2 is not optimized 201: %% The query takes 30 seconds on Postgres 202: %% mongoose_rdbms:sql_query(<<"localhost">>, <<"SELECT binary_data_16m FROM test_types">>). 203: [binary:copy(<<16>>, 16777215)] 204: end. 205: 206: binary_8k_values() -> 207: truncate_binaries(8000, unicode_values() ++ binary_values()). 208: 209: binary_65k_values() -> 210: truncate_binaries(65535, unicode_values() ++ binary_values()). 211: 212: binary_16m_values() -> 213: truncate_binaries(16777215, unicode_values() ++ binary_values()). 214: 215: ascii_char_values() -> 216: [<<"a">>, <<"b">>]. 217: 218: enum_char_values() -> 219: [<<"A">>, <<"B">>, <<"C">>]. 220: 221: simple_like_texts() -> 222: [#{text => <<"hello user!">>, 223: not_matching => [<<"hi">>, <<"help">>], 224: matching => [<<"hello">>, <<"user">>, <<"hell">>]}, 225: #{text => <<60,79,67,32,59,48,63,58,48>>, 226: not_matching => [<<62,66,64,48,65,66,53,66>>], 227: matching => [<<60,79,67>>]}]. 228: 229: like_texts() -> 230: simple_like_texts() ++ 231: [#{text => <<"abc%">>, 232: not_matching => [<<"ab%">>, <<"%bc%">>], 233: matching => [<<"abc%">>, <<"abc">>]}, 234: #{text => <<"żółć_"/utf8>>, 235: not_matching => [<<"_ółć_"/utf8>>], 236: matching => [<<"żół"/utf8>>, <<"ółć_"/utf8>>]}]. 237: 238: %%-------------------------------------------------------------------- 239: %% Test cases 240: %%-------------------------------------------------------------------- 241: 242: %% Checks, that we at least can connect to db 243: select_one_works_case(Config) -> 244: ?assert_equal({selected, [{<<"1">>}]}, 245: %% Postgres direct driver returns <<"1">> 246: %% Everyone else return 1 247: selected_to_binary(sql_query(Config, <<"SELECT 1">>))). 248: 249: %% Should be binary. 250: %% Unicode is another case. 251: select_ascii_string_works_case(Config) -> 252: ?assert_equal({selected, [{<<"ascii">>}]}, 253: sql_query(Config, <<"SELECT 'ascii'">>)). 254: 255: %% Writes and reads values of this datatype back 256: read_int32_case(Config) -> 257: Values = int32_values(), 258: [check_int32(Config, Value) || Value <- Values]. 259: 260: read_int64_case(Config) -> 261: Values = int32_values() ++ int64_values(), 262: [check_int64(Config, Value) || Value <- Values]. 263: 264: read_unicode_case(Config) -> 265: [check_unicode(Config, Value) || Value <- unicode_values()]. 266: 267: read_unicode250_case(Config) -> 268: [check_unicode250(Config, Value) || Value <- unicode_values(), byte_size(Value) < 250]. 269: 270: read_ascii_char_case(Config) -> 271: [check_ascii_char(Config, Value) || Value <- ascii_char_values()]. 272: 273: read_ascii_string_case(Config) -> 274: [check_ascii_string(Config, Value) 275: || Value <- ascii_char_values() ++ ascii_string_values()]. 276: 277: read_binary_8k_case(Config) -> 278: [check_binary_8k(Config, Value) || Value <- binary_8k_values()]. 279: 280: read_binary_65k_case(Config) -> 281: [check_binary_65k(Config, Value) || Value <- binary_65k_values()]. 282: 283: read_binary_16m_case(Config) -> 284: [check_binary_16m(Config, Value) || Value <- binary_16m_values()]. 285: 286: read_enum_char_case(Config) -> 287: [check_enum_char(Config, Value) || Value <- enum_char_values()]. 288: 289: read_boolean_case(Config) -> 290: [check_boolean(Config, Value) || Value <- [true, false]]. 291: 292: %%-------------------------------------------------------------------- 293: %% Prepared cases 294: %%-------------------------------------------------------------------- 295: 296: read_prep_int32_case(Config) -> 297: Values = int32_values(), 298: [check_prep_int32(Config, Value) || Value <- Values]. 299: 300: read_prep_int64_case(Config) -> 301: Values = int32_values() ++ int64_values(), 302: [check_prep_int64(Config, Value) || Value <- Values]. 303: 304: read_prep_unicode_case(Config) -> 305: [check_prep_unicode(Config, Value) || Value <- unicode_values()]. 306: 307: read_prep_unicode250_case(Config) -> 308: [check_prep_unicode250(Config, Value) || Value <- unicode_values(), byte_size(Value) < 250]. 309: 310: read_prep_ascii_char_case(Config) -> 311: [check_prep_ascii_char(Config, Value) || Value <- ascii_char_values()]. 312: 313: read_prep_ascii_string_case(Config) -> 314: [check_prep_ascii_string(Config, Value) 315: || Value <- ascii_char_values() ++ ascii_string_values()]. 316: 317: read_prep_binary_8k_case(Config) -> 318: [check_prep_binary_8k(Config, Value) || Value <- binary_8k_values()]. 319: 320: read_prep_binary_65k_case(Config) -> 321: [check_prep_binary_65k(Config, Value) || Value <- binary_65k_values()]. 322: 323: read_prep_binary_16m_case(Config) -> 324: [check_prep_binary_16m(Config, Value) || Value <- binary_16m_values()]. 325: 326: read_prep_enum_char_case(Config) -> 327: [check_prep_enum_char(Config, Value) || Value <- enum_char_values()]. 328: 329: read_prep_boolean_case(Config) -> 330: [check_prep_boolean(Config, Value) || Value <- [0, 1]]. 331: 332: select_current_timestamp_case(Config) -> 333: ok = rpc(mim(), mongoose_rdbms_timestamp, prepare, []), 334: assert_is_integer(rpc(mim(), mongoose_rdbms_timestamp, select, [host_type(), tag()])). 335: 336: assert_is_integer(X) when is_integer(X) -> 337: X. 338: 339: truncate_binaries(Len, List) -> 340: [truncate_binary(Len, Bin) || Bin <- List]. 341: 342: truncate_binary(Len, Bin) when byte_size(Bin) > Len -> 343: binary:part(Bin, {0,Len}); 344: truncate_binary(_Len, Bin) -> 345: Bin. 346: 347: safe_binary(Len, Bin) when byte_size(Bin) > Len -> 348: #{what => truncated_safe_binary, 349: truncated_length => Len, 350: total_length => byte_size(Bin), 351: truncated_binary => binary:part(Bin, {0,Len})}; 352: safe_binary(_Len, Bin) -> 353: Bin. 354: 355: arguments_from_two_tables(Config) -> 356: erase_users(Config), 357: sql_prepare(Config, select_multi_args, users, [password, 'last.seconds'], 358: <<"SELECT users.username from users " 359: " LEFT JOIN last ON (last.username = users.username) " 360: " WHERE users.password = ? AND last.seconds > ?">>), 361: UserInsert = "INSERT INTO users (username, server, password) VALUES ", 362: sql_query(Config, UserInsert ++ "('alice', 'domain', 'secret')"), 363: sql_query(Config, UserInsert ++ "('bob', 'domain', 'billy')"), 364: LastInsert = "INSERT INTO last (username, server, seconds, state) VALUES ", 365: sql_query(Config, LastInsert ++ "('alice', 'domain', 1615368268, 'ok')"), 366: sql_query(Config, LastInsert ++ "('bob', 'domain', 1610000000, 'cool')"), 367: SelectResult = sql_execute(Config, select_multi_args, [<<"secret">>, 1611000000]), 368: ?assert_equal({selected, [{<<"alice">>}]}, SelectResult), 369: erase_users(Config), 370: ok. 371: 372: %% Ensures that ODBC uses a correct type when encoding NULL 373: %% and it does not interfere with non-null values 374: insert_batch_with_null_case(Config) -> 375: erase_table(Config), 376: sql_prepare(Config, insert_batch, test_types, [unicode, unicode], 377: <<"INSERT INTO test_types(unicode) VALUES (?), (?)">>), 378: sql_execute(Config, insert_batch, [null, <<"check1">>]), 379: sql_execute(Config, insert_batch, [<<"check2">>, null]), 380: SelectResult = sql_query(Config, "SELECT unicode FROM test_types"), 381: %% Sorting with null values is DB specific, so sort it with Erlang 382: ?assert_equal({selected, [{null}, {null}, {<<"check1">>}, {<<"check2">>}]}, 383: selected_to_sorted(SelectResult)). 384: 385: test_cast_insert(Config) -> 386: erase_table(Config), 387: sql_prepare(Config, insert_one, test_types, [unicode], 388: <<"INSERT INTO test_types(unicode) VALUES (?)">>), 389: sql_execute_cast(Config, insert_one, [<<"check1">>]), 390: sql_query_cast(Config, <<"INSERT INTO test_types(unicode) VALUES ('check2')">>), 391: mongoose_helper:wait_until( 392: fun() -> 393: SelectResult = sql_query(Config, "SELECT unicode FROM test_types"), 394: ?assertEqual({selected, [{<<"check1">>}, {<<"check2">>}]}, 395: selected_to_sorted(SelectResult)) 396: end, ok, #{name => cast_queries}). 397: 398: test_request_insert(Config) -> 399: erase_table(Config), 400: sql_prepare(Config, insert_one, test_types, [unicode], 401: <<"INSERT INTO test_types(unicode) VALUES (?)">>), 402: sql_execute_request(Config, insert_one, [<<"check1">>]), 403: sql_query_request(Config, <<"INSERT INTO test_types(unicode) VALUES ('check2')">>), 404: mongoose_helper:wait_until( 405: fun() -> 406: SelectResult = sql_query(Config, "SELECT unicode FROM test_types"), 407: ?assertEqual({selected, [{<<"check1">>}, {<<"check2">>}]}, 408: selected_to_sorted(SelectResult)) 409: end, ok, #{name => request_queries}). 410: 411: test_wrapped_request(Config) -> 412: % given 413: erase_table(Config), 414: sql_prepare(Config, insert_one, test_types, [unicode], 415: <<"INSERT INTO test_types(unicode) VALUES (?)">>), 416: rpc(mim(), mongoose_metrics, ensure_metric, [global, [test_metric], histogram]), 417: WrapperFun = fun(SqlExecute) -> 418: {Time, Result} = timer:tc(SqlExecute), 419: mongoose_metrics:update(global, [test_metric], Time), 420: Result 421: end, 422: 423: % when 424: sql_execute_wrapped_request_and_wait_response(Config, insert_one, [<<"check1">>], WrapperFun), 425: 426: % then 427: mongoose_helper:wait_until( 428: fun() -> 429: SelectResult = sql_query(Config, "SELECT unicode FROM test_types"), 430: ?assertEqual({selected, [{<<"check1">>}]}, selected_to_sorted(SelectResult)) 431: end, ok, #{name => request_queries}), 432: 433: {ok, Metric} = rpc(mim(), mongoose_metrics, get_metric_value, [global, [test_metric]]), 434: MetricValue = proplists:get_value(mean, Metric), 435: ?assert(MetricValue > 0). 436: 437: test_failed_wrapper(Config) -> 438: % given 439: erase_table(Config), 440: sql_prepare(Config, insert_one, test_types, [unicode], 441: <<"INSERT INTO test_types(unicode) VALUES (?)">>), 442: ok = rpc(mim(), meck, new, [supervisor, [passthrough, no_link, unstick]]), 443: WrapperFun = fun(_SqlExecute) -> 444: error(wrapper_crashed) 445: end, 446: 447: % when 448: Result = sql_execute_wrapped_request_and_wait_response(Config, insert_one, [<<"check1">>], WrapperFun), 449: 450: % then 451: ?assertEqual({reply,{error,wrapper_crashed}}, Result), 452: ?assertEqual([], rpc(mim(), meck, history, [supervisor])). 453: 454: test_request_transaction(Config) -> 455: erase_table(Config), 456: Queries = [<<"INSERT INTO test_types(unicode) VALUES ('check1')">>, 457: <<"INSERT INTO test_types(unicode) VALUES ('check2')">>], 458: sql_transaction_request(Config, Queries), 459: mongoose_helper:wait_until( 460: fun() -> 461: SelectResult = sql_query(Config, "SELECT unicode FROM test_types"), 462: ?assertEqual({selected, [{<<"check1">>}, {<<"check2">>}]}, 463: selected_to_sorted(SelectResult)) 464: end, ok, #{name => request_queries}). 465: 466: test_restart_transaction_with_execute(Config) -> 467: erase_table(Config), 468: prepare_insert_int8(Config), 469: ok = rpc(mim(), meck, new, [mongoose_rdbms_backend, [passthrough, no_link]]), 470: ok = rpc(mim(), meck, expect, [mongoose_rdbms_backend, execute, 4, 471: {error, simulated_db_error}]), 472: %% Check that mocking works 473: {error, simulated_db_error} = sql_execute(Config, insert_int8, [1]), 474: %% Executed by the MIM node 475: HostType = host_type(), 476: Pid = self(), 477: F = fun() -> Pid ! called, mongoose_rdbms:execute(HostType, insert_int8, [2]) end, 478: {aborted, #{reason := simulated_db_error}} = sql_transaction(Config, F), 479: called_times(11), %% 1 first run + 10 restarts 480: ok. 481: 482: test_restart_transaction_with_execute_eventually_passes(Config) -> 483: erase_table(Config), 484: prepare_insert_int8(Config), 485: ok = rpc(mim(), meck, new, [mongoose_rdbms_backend, [passthrough, no_link]]), 486: ok = rpc(mim(), meck, expect, [mongoose_rdbms_backend, execute, 4, 487: {error, simulated_db_error}]), 488: %% Check that mocking works 489: {error, simulated_db_error} = sql_execute(Config, insert_int8, [1]), 490: %% Executed by the MIM node 491: HostType = host_type(), 492: Pid = self(), 493: F = fun() -> Pid ! called, fail_times(3, Pid, HostType) end, 494: {atomic, ok} = sql_transaction(Config, F), 495: called_times(3), 496: ok. 497: 498: test_failed_transaction_with_execute_wrapped(Config) -> 499: % given 500: HostType = host_type(), 501: Pid = self(), 502: erase_table(Config), 503: prepare_insert_int8(Config), 504: ok = rpc(mim(), meck, new, [mongoose_rdbms_backend, [passthrough, no_link]]), 505: ok = rpc(mim(), meck, expect, [mongoose_rdbms_backend, execute, 4, 506: {error, simulated_db_error}]), 507: WrapperFun = fun(SqlExecute) -> 508: Pid ! msg_before, 509: Result = SqlExecute(), 510: Pid ! msg_after, 511: Result 512: end, 513: 514: % when 515: F = fun() -> mongoose_rdbms:execute_wrapped_request(HostType, insert_int8, [2], WrapperFun) end, 516: {aborted, #{reason := simulated_db_error}} = sql_transaction(Config, F), 517: 518: % then 519: check_not_received(msg_after). 520: 521: test_failed_wrapper_transaction(Config) -> 522: % given 523: erase_table(Config), 524: prepare_insert_int8(Config), 525: ok = rpc(mim(), meck, new, [supervisor, [passthrough, no_link, unstick]]), 526: WrapperFun = fun(_SqlExecute) -> 527: error(wrapper_crashed) 528: end, 529: 530: % when 531: F = fun() -> sql_execute_wrapped_request(Config, insert_one, [<<"check1">>], WrapperFun) end, 532: sql_transaction(Config, F), 533: 534: % then 535: ?assertEqual([], rpc(mim(), meck, history, [supervisor])). 536: 537: prepare_insert_int8(Config) -> 538: Q = <<"INSERT INTO test_types(", (escape_column(<<"int8">>))/binary, ") VALUES (?)">>, 539: sql_prepare(Config, insert_int8, test_types, [int8], Q). 540: 541: fail_times(N, Pid, HostType) -> 542: case update_counter(Pid) + 1 of 543: N -> 544: ok; 545: _ -> 546: mongoose_rdbms:execute(HostType, insert_int8, [2]) 547: end. 548: 549: %% Returns old value 550: update_counter(Pid) -> 551: Key = {test_counter, Pid}, 552: N = case get(Key) of undefined -> 0; X -> X end, 553: put(Key, N + 1), 554: N. 555: 556: called_times(0) -> 557: %% Check that there are no more calls 558: receive called -> error(unexpected) after 0 -> ok end; 559: called_times(N) -> 560: receive called -> ok after 5000 -> error({called_times_timeout, N}) end, 561: called_times(N - 1). 562: 563: test_incremental_upsert(Config) -> 564: case is_odbc() of 565: true -> 566: ok; 567: false -> 568: do_test_incremental_upsert(Config) 569: end. 570: 571: do_test_incremental_upsert(Config) -> 572: KeyFields = [<<"luser">>, <<"lserver">>, <<"remote_bare_jid">>], 573: InsertFields = KeyFields ++ [<<"msg_id">>, <<"content">>, <<"unread_count">>, <<"timestamp">>], 574: 575: Key = [<<"alice">>, <<"localhost">>, <<"bob@localhost">>], 576: Insert = [<<"alice">>, <<"localhost">>, <<"bob@localhost">>, <<"msg_id">>, <<"content">>, 1], 577: sql_prepare_upsert(Config, upsert_incr, inbox, 578: InsertFields, [<<"timestamp">>], KeyFields, <<"timestamp">>), 579: sql_execute_upsert(Config, upsert_incr, Insert ++ [42], [42], Key), 580: sql_execute_upsert(Config, upsert_incr, Insert ++ [43], [43], Key), 581: sql_execute_upsert(Config, upsert_incr, Insert ++ [0], [0], Key), 582: SelectResult = sql_query(Config, <<"SELECT timestamp FROM inbox">>), 583: ?assertEqual({selected, [{<<"43">>}]}, selected_to_binary(SelectResult)). 584: 585: %%-------------------------------------------------------------------- 586: %% Text searching 587: %%-------------------------------------------------------------------- 588: 589: select_like_case(Config) -> 590: %% Non-prepared queries don't support proper LIKE escaping 591: [check_like(Config, TextMap) || TextMap <- simple_like_texts()]. 592: 593: select_like_prep_case(Config) -> 594: [check_like_prep(Config, TextMap) || TextMap <- like_texts()]. 595: 596: %%-------------------------------------------------------------------- 597: %% Helpers 598: %%-------------------------------------------------------------------- 599: tag() -> 600: extra_tag. 601: 602: sql_query(_Config, Query) -> 603: slow_rpc(mongoose_rdbms, sql_query, [host_type(), tag(), Query]). 604: 605: sql_prepare(_Config, Name, Table, Fields, Query) -> 606: escalus_ejabberd:rpc(mongoose_rdbms, prepare, [Name, Table, Fields, Query]). 607: 608: sql_prepare_upsert(_Config, Name, Table, Insert, Update, Unique, Incr) -> 609: escalus_ejabberd:rpc(rdbms_queries, prepare_upsert, [host_type(), Name, Table, Insert, Update, Unique, Incr]). 610: 611: sql_execute(_Config, Name, Parameters) -> 612: slow_rpc(mongoose_rdbms, execute, [host_type(), tag(), Name, Parameters]). 613: 614: sql_execute_cast(_Config, Name, Parameters) -> 615: slow_rpc(mongoose_rdbms, execute_cast, [host_type(), tag(), Name, Parameters]). 616: 617: sql_query_cast(_Config, Query) -> 618: slow_rpc(mongoose_rdbms, sql_query_cast, [host_type(), tag(), Query]). 619: 620: sql_execute_request(_Config, Name, Parameters) -> 621: slow_rpc(mongoose_rdbms, execute_request, [host_type(), tag(), Name, Parameters]). 622: 623: sql_execute_wrapped_request(_Config, Name, Parameters, WrapperFun) -> 624: slow_rpc(mongoose_rdbms, execute_wrapped_request, [host_type(), tag(), Name, Parameters, WrapperFun]). 625: 626: sql_execute_wrapped_request_and_wait_response(_Config, Name, Parameters, WrapperFun) -> 627: slow_rpc(?MODULE, execute_wrapped_request_and_wait_response, [host_type(), tag(), Name, Parameters, WrapperFun]). 628: 629: execute_wrapped_request_and_wait_response(HostType, Tag, Name, Parameters, WrapperFun) -> 630: RequestId = mongoose_rdbms:execute_wrapped_request(HostType, Tag, Name, Parameters, WrapperFun), 631: gen_server:wait_response(RequestId, 100). 632: 633: sql_execute_upsert(_Config, Name, Insert, Update, Unique) -> 634: slow_rpc(rdbms_queries, execute_upsert, [host_type(), tag(), Name, Insert, Update, Unique]). 635: 636: sql_query_request(_Config, Query) -> 637: slow_rpc(mongoose_rdbms, sql_query_request, [host_type(), tag(), Query]). 638: 639: sql_transaction_request(_Config, Query) -> 640: slow_rpc(mongoose_rdbms, sql_transaction_request, [host_type(), tag(), Query]). 641: 642: sql_transaction(_Config, F) -> 643: slow_rpc(mongoose_rdbms, sql_transaction, [host_type(), tag(), F]). 644: 645: escape_null(_Config) -> 646: escalus_ejabberd:rpc(mongoose_rdbms, escape_null, []). 647: 648: escape_string(_Config, Value) -> 649: escalus_ejabberd:rpc(mongoose_rdbms, escape_string, [Value]). 650: 651: escape_binary(_Config, Value) -> 652: slow_rpc(mongoose_rdbms, escape_binary, [host_type(), Value]). 653: 654: escape_boolean(_Config, Value) -> 655: escalus_ejabberd:rpc(mongoose_rdbms, escape_boolean, [Value]). 656: 657: escape_like(_Config, Value) -> 658: escalus_ejabberd:rpc(mongoose_rdbms, escape_like, [Value]). 659: 660: escape_prepared_like(_Config, Value) -> 661: escalus_ejabberd:rpc(mongoose_rdbms, escape_prepared_like, [Value]). 662: 663: unescape_binary(_Config, Value) -> 664: escalus_ejabberd:rpc(mongoose_rdbms, unescape_binary, [host_type(), Value]). 665: 666: use_escaped(_Config, Value) -> 667: escalus_ejabberd:rpc(mongoose_rdbms, use_escaped, [Value]). 668: 669: use_escaped_like(_Config, Value) -> 670: escalus_ejabberd:rpc(mongoose_rdbms, use_escaped_like, [Value]). 671: 672: escape_string_or_null(Config, null) -> 673: escape_null(Config); 674: escape_string_or_null(Config, TextValue) -> 675: escape_string(Config, TextValue). 676: 677: escape_binary_or_null(Config, null) -> 678: escape_null(Config); 679: escape_binary_or_null(Config, Value) -> 680: escape_binary(Config, Value). 681: 682: decode_boolean(_Config, Value) -> 683: escalus_ejabberd:rpc(mongoose_rdbms, to_bool, [Value]). 684: 685: erase_table(Config) -> 686: {updated, _} = sql_query(Config, <<"DELETE FROM test_types">>). 687: 688: erase_users(Config) -> 689: {updated, _} = sql_query(Config, <<"DELETE FROM users">>), 690: {updated, _} = sql_query(Config, <<"DELETE FROM last">>). 691: 692: erase_inbox(Config) -> 693: {updated, _} = sql_query(Config, <<"DELETE FROM inbox">>). 694: 695: check_int32(Config, Value) -> 696: check_generic_integer(Config, Value, <<"int32">>). 697: 698: check_int64(Config, Value) -> 699: check_generic_integer(Config, Value, <<"int64">>). 700: 701: check_generic_integer(Config, Value, Column) -> 702: EraseResult = erase_table(Config), 703: InsertQuery = <<"INSERT INTO test_types (", Column/binary, ") " 704: "VALUES (", (integer_or_null_to_binary(Value))/binary, ")">>, 705: SelectQuery = <<"SELECT ", Column/binary, " FROM test_types">>, 706: InsertResult = sql_query(Config, InsertQuery), 707: SelectResult = sql_query(Config, SelectQuery), 708: %% Compare as binaries 709: ?assert_equal_extra({selected, [{integer_to_binary_or_null(Value)}]}, 710: selected_to_binary(SelectResult), 711: #{column => Column, 712: erase_result => EraseResult, 713: test_value => Value, 714: insert_query => InsertQuery, 715: select_query => SelectQuery, 716: select_result => SelectResult, 717: insert_result => InsertResult}). 718: 719: integer_or_null_to_binary(null) -> <<"NULL">>; 720: integer_or_null_to_binary(X) -> integer_to_binary(X). 721: 722: integer_to_binary_or_null(null) -> null; 723: integer_to_binary_or_null(X) -> integer_to_binary(X). 724: 725: %% Helper function to transform values to an uniform format. 726: %% Single tuple, single element case. 727: %% In ODBC int32 is integer, but int64 is binary. 728: selected_to_binary({selected, [{Value}]}) when is_integer(Value) -> 729: {selected, [{integer_to_binary(Value)}]}; 730: selected_to_binary(Other) -> 731: Other. 732: 733: selected_to_sorted({selected, Rows}) -> 734: {selected, lists:sort(Rows)}; 735: selected_to_sorted(Other) -> 736: Other. 737: 738: value_to_binary(Value) when is_integer(Value) -> 739: integer_to_binary(Value); 740: value_to_binary(Value) -> 741: Value. 742: 743: check_unicode250(Config, Value) -> 744: check_unicode_generic(Config, Value, <<"unicode250">>). 745: 746: check_unicode(Config, Value) -> 747: check_unicode_generic(Config, Value, <<"unicode">>). 748: 749: check_unicode_generic(Config, Value, Column) -> 750: SValue = escape_string_or_null(Config, Value), 751: EraseResult = erase_table(Config), 752: InsertQuery = ["INSERT INTO test_types (", Column, ") " 753: "VALUES (", use_escaped(Config, SValue), ")"], 754: SelectQuery = <<"SELECT ", Column/binary, " FROM test_types">>, 755: InsertResult = sql_query(Config, InsertQuery), 756: SelectResult = sql_query(Config, SelectQuery), 757: %% Compare as binaries 758: ?assert_equal_extra({selected, [{Value}]}, 759: SelectResult, 760: #{column => Column, 761: erase_result => EraseResult, 762: expected_length => byte_size_or_null(Value), 763: selected_length => maybe_selected_length(Config, SelectResult), 764: compare_selected => compare_selected(Config, SelectResult, Value), 765: test_value => Value, 766: insert_query => InsertQuery, 767: insert_query_binary => iolist_to_binary(InsertQuery), 768: select_query => SelectQuery, 769: select_result => SelectResult, 770: insert_result => InsertResult}). 771: 772: check_ascii_char(Config, Value) -> 773: SValue = escape_string_or_null(Config, Value), 774: EraseResult = erase_table(Config), 775: InsertQuery = ["INSERT INTO test_types (ascii_char) " 776: "VALUES (", use_escaped(Config, SValue), ")"], 777: SelectQuery = <<"SELECT ascii_char FROM test_types">>, 778: InsertResult = sql_query(Config, InsertQuery), 779: SelectResult = sql_query(Config, SelectQuery), 780: %% Compare as binaries 781: ?assert_equal_extra({selected, [{Value}]}, 782: SelectResult, 783: #{erase_result => EraseResult, 784: test_value => Value, 785: insert_query => InsertQuery, 786: insert_query_binary => iolist_to_binary(InsertQuery), 787: select_query => SelectQuery, 788: select_result => SelectResult, 789: insert_result => InsertResult}). 790: 791: check_ascii_string(Config, Value) -> 792: SValue = escape_string_or_null(Config, Value), 793: EraseResult = erase_table(Config), 794: InsertQuery = ["INSERT INTO test_types (ascii_string) " 795: "VALUES (", use_escaped(Config, SValue), ")"], 796: SelectQuery = <<"SELECT ascii_string FROM test_types">>, 797: InsertResult = sql_query(Config, InsertQuery), 798: SelectResult = sql_query(Config, SelectQuery), 799: %% Compare as binaries 800: ?assert_equal_extra({selected, [{Value}]}, 801: SelectResult, 802: #{erase_result => EraseResult, 803: test_value => Value, 804: insert_query => InsertQuery, 805: insert_query_binary => iolist_to_binary(InsertQuery), 806: select_query => SelectQuery, 807: select_result => SelectResult, 808: insert_result => InsertResult}). 809: 810: check_binary_8k(Config, Value) -> 811: check_binary(Config, Value, <<"binary_data_8k">>). 812: 813: check_binary_65k(Config, Value) -> 814: check_binary(Config, Value, <<"binary_data_65k">>). 815: 816: check_binary_16m(Config, Value) -> 817: check_binary(Config, Value, <<"binary_data_16m">>). 818: 819: check_binary(Config, Value, Column) -> 820: SValue = escape_binary_or_null(Config, Value), 821: EraseResult = erase_table(Config), 822: InsertQuery = ["INSERT INTO test_types (", Column, ") " 823: "VALUES (", use_escaped(Config, SValue), ")"], 824: SelectQuery = <<"SELECT ", Column/binary, " FROM test_types">>, 825: InsertResult = sql_query(Config, InsertQuery), 826: SelectResult = sql_query(Config, SelectQuery), 827: %% Compare as binaries 828: ?assert_equal_extra({selected, [{Value}]}, 829: selected_unescape(Config, SelectResult), 830: #{erase_result => EraseResult, 831: inserted_length => byte_size_or_null(Value), 832: %% pgsql+odbc can truncate binaries 833: maybe_selected_length => maybe_selected_length(Config, SelectResult), 834: maybe_selected_tail => maybe_selected_tail(Config, SelectResult), 835: compare_selected => compare_selected(Config, selected_unescape(Config, SelectResult), Value), 836: test_value => Value, 837: insert_query_binary => iolist_to_binary(InsertQuery), 838: select_query => SelectQuery, 839: select_result => SelectResult, 840: insert_result => InsertResult}). 841: 842: byte_size_or_null(null) -> 843: null; 844: byte_size_or_null(Value) -> 845: byte_size(Value). 846: 847: check_enum_char(Config, Value) when is_binary(Value) -> 848: SValue = escape_string_or_null(Config, Value), 849: EraseResult = erase_table(Config), 850: InsertQuery = ["INSERT INTO test_types (enum_char) " 851: "VALUES (", use_escaped(Config, SValue), ")"], 852: SelectQuery = <<"SELECT enum_char FROM test_types">>, 853: InsertResult = sql_query(Config, InsertQuery), 854: SelectResult = sql_query(Config, SelectQuery), 855: %% Compare as binaries 856: ?assert_equal_extra({selected, [{Value}]}, 857: SelectResult, 858: #{erase_result => EraseResult, 859: test_value => Value, 860: insert_query => InsertQuery, 861: insert_query_binary => iolist_to_binary(InsertQuery), 862: select_query => SelectQuery, 863: select_result => SelectResult, 864: insert_result => InsertResult}). 865: 866: check_boolean(Config, Value) when is_boolean(Value) -> 867: SValue = escape_boolean(Config, Value), 868: EraseResult = erase_table(Config), 869: InsertQuery = ["INSERT INTO test_types (bool_flag) " 870: "VALUES (", use_escaped(Config, SValue), ")"], 871: SelectQuery = <<"SELECT bool_flag FROM test_types">>, 872: InsertResult = sql_query(Config, InsertQuery), 873: SelectResult = sql_query(Config, SelectQuery), 874: %% Compare as binaries 875: ?assert_equal_extra({selected, [{Value}]}, 876: selected_decode_boolean(Config, SelectResult), 877: #{erase_result => EraseResult, 878: test_value => Value, 879: insert_query => InsertQuery, 880: insert_query_binary => iolist_to_binary(InsertQuery), 881: select_query => SelectQuery, 882: select_result => SelectResult, 883: insert_result => InsertResult}). 884: 885: selected_unescape(_Config, {selected, [{null}]}) -> 886: {selected, [{null}]}; 887: selected_unescape(Config, {selected, [{Value}]}) -> 888: {selected, [{unescape_binary(Config, Value)}]}; 889: selected_unescape(_Config, Other) -> 890: Other. 891: 892: selected_decode_boolean(Config, {selected, [{Value}]}) -> 893: {selected, [{decode_boolean(Config, Value)}]}; 894: selected_decode_boolean(_Config, Other) -> 895: Other. 896: 897: selected_boolean_to_binary_int(Config, {selected, [{Value}]}) -> 898: {selected, [{boolean_to_binary_int(decode_boolean(Config, Value))}]}; 899: selected_boolean_to_binary_int(_Config, Other) -> 900: Other. 901: 902: boolean_to_binary_int(true) -> <<"1">>; 903: boolean_to_binary_int(false) -> <<"0">>. 904: 905: maybe_selected_length(_Config, {selected, [{Value}]}) when is_binary(Value) -> 906: byte_size(Value); 907: maybe_selected_length(_Config, _Other) -> 908: unknown. 909: 910: maybe_selected_tail(Config, Selected) -> 911: maybe_selected_tail(Config, Selected, 100). 912: 913: maybe_selected_tail(_Config, {selected, [{Value}]}, TailLen) 914: when is_binary(Value), byte_size(Value) > TailLen -> 915: binary:part(Value, {byte_size(Value), -TailLen}); 916: maybe_selected_tail(_Config, {selected, [{Value}]}, _TailLen) -> 917: Value; 918: maybe_selected_tail(_Config, _Other, _TailLen) -> 919: unknown. 920: 921: check_prep_int32(Config, Value) -> 922: check_generic_prep_integer(Config, Value, <<"int32">>). 923: 924: check_prep_int64(Config, Value) -> 925: check_generic_prep_integer(Config, Value, <<"int64">>). 926: 927: check_prep_unicode(Config, Value) -> 928: check_generic_prep(Config, Value, <<"unicode">>). 929: 930: check_prep_unicode250(Config, Value) -> 931: check_generic_prep(Config, Value, <<"unicode250">>). 932: 933: %% Char is ascii string of length one 934: check_prep_ascii_char(Config, Value) -> 935: check_generic_prep(Config, Value, <<"unicode">>). 936: 937: %% Varchar 938: check_prep_ascii_string(Config, Value) -> 939: check_generic_prep(Config, Value, <<"ascii_string">>). 940: 941: check_prep_binary_65k(Config, Value) -> 942: %% MSSQL returns binaries in HEX encoding 943: check_generic_prep(Config, Value, <<"binary_data_65k">>, unescape_binary). 944: 945: check_prep_binary_8k(Config, Value) -> 946: %% MSSQL returns binaries in HEX encoding 947: check_generic_prep(Config, Value, <<"binary_data_8k">>, unescape_binary). 948: 949: check_prep_binary_16m(Config, Value) -> 950: %% MSSQL returns binaries in HEX encoding 951: check_generic_prep(Config, Value, <<"binary_data_16m">>, unescape_binary). 952: 953: check_generic_prep_integer(Config, Value, Column) -> 954: check_generic_prep(Config, Value, Column). 955: 956: check_prep_enum_char(Config, Value) -> 957: check_generic_prep(Config, Value, <<"enum_char">>). 958: 959: check_prep_boolean(Config, Value) -> 960: check_generic_prep(Config, Value, <<"bool_flag">>, boolean_to_binary_int). 961: 962: %% Data types 963: %% {ok, Conn} = odbc:connect("DSN=mongoose-mssql;UID=sa;PWD=mongooseim_secret+ESL123", []). 964: %% odbc:describe_table(Conn, "test_types"). 965: %% [{"unicode",{sql_wvarchar,536870911}}, 966: %% {"binary_data_65k",'SQL_VARBINARY'}, 967: %% {"ascii_char",{sql_char,1}}, 968: %% {"ascii_string",{sql_varchar,250}}, 969: %% {"int32",sql_integer}, 970: %% {"int64",'SQL_BIGINT'}, 971: %% {"int8",sql_tinyint}] 972: 973: check_generic_prep(Config, Value, Column) -> 974: check_generic_prep(Config, Value, Column, to_binary). 975: 976: check_generic_prep(Config, Value, Column, TransformResult) -> 977: EraseResult = erase_table(Config), 978: 979: InsertQuery = <<"INSERT INTO test_types (", Column/binary, ") " 980: "VALUES (?)">>, 981: SelectQuery = <<"SELECT ", Column/binary, " FROM test_types">>, 982: Name = list_to_atom("insert_" ++ binary_to_list(Column)), 983: Table = test_types, 984: Fields = [binary_to_atom(Column, utf8)], 985: PrepareResult = sql_prepare(Config, Name, Table, Fields, InsertQuery), 986: Parameters = [Value], 987: InsertResult = sql_execute(Config, Name, Parameters), 988: SelectResult = sql_query(Config, SelectQuery), 989: %% Compare as binaries 990: ?assert_equal_extra({selected, [{value_to_binary(Value)}]}, 991: transform_selected(TransformResult, Config, SelectResult), 992: #{column => Column, 993: erase_result => EraseResult, 994: test_value => Value, 995: insert_query => InsertQuery, 996: prepare_result => PrepareResult, 997: select_query => SelectQuery, 998: select_result => SelectResult, 999: insert_result => InsertResult}), 1000: check_generic_filtered_prep(Config, Value, Column, TransformResult), 1001: case is_odbc() of 1002: true -> 1003: %% TOP is mssql feature, all other databases use LIMIT. 1004: check_generic_filtered_top_prep(Config, Value, Column, TransformResult); 1005: false -> 1006: ok 1007: end. 1008: 1009: %% We want to ensure that variable substitution works in SELECTS too. 1010: %% We also want to check the result value is encoded correctly. 1011: check_generic_filtered_prep(_Config, null, _Column, _TransformResult) -> 1012: skip_null_test; 1013: check_generic_filtered_prep(Config, Value, Column, TransformResult) -> 1014: SelectQuery = <<"SELECT ", Column/binary, 1015: " FROM test_types WHERE ", Column/binary, " = ?">>, 1016: Name = list_to_atom("select_filtered_" ++ binary_to_list(Column)), 1017: Table = test_types, 1018: Fields = [binary_to_atom(Column, utf8)], 1019: PrepareResult = sql_prepare(Config, Name, Table, Fields, SelectQuery), 1020: Parameters = [Value], 1021: SelectResult = sql_execute(Config, Name, Parameters), 1022: %% Compare as binaries 1023: ?assert_equal_extra({selected, [{value_to_binary(Value)}]}, 1024: transform_selected(TransformResult, Config, SelectResult), 1025: #{column => Column, 1026: test_value => Value, 1027: prepare_result => PrepareResult, 1028: select_query => SelectQuery, 1029: select_result => SelectResult}). 1030: 1031: check_generic_filtered_top_prep(_Config, null, _Column, _TransformResult) -> 1032: skip_null_test; 1033: check_generic_filtered_top_prep(Config, Value, Column, TransformResult) -> 1034: %% SQL Server requires you to place parenthesis around the argument to top if you pass in a variable: 1035: %% https://stackoverflow.com/questions/7038818/ms-sql-exception-incorrect-syntax-near-p0 1036: SelectQuery = <<"SELECT TOP (?) ", Column/binary, 1037: " FROM test_types WHERE ", Column/binary, " = ?">>, 1038: Name = list_to_atom("select_filtered_top_" ++ binary_to_list(Column)), 1039: Table = test_types, 1040: Fields = [limit, binary_to_atom(Column, utf8)], 1041: PrepareResult = sql_prepare(Config, Name, Table, Fields, SelectQuery), 1042: Parameters = [30, Value], 1043: SelectResult = sql_execute(Config, Name, Parameters), 1044: %% Compare as binaries 1045: ?assert_equal_extra({selected, [{value_to_binary(Value)}]}, 1046: transform_selected(TransformResult, Config, SelectResult), 1047: #{column => Column, 1048: test_value => Value, 1049: prepare_result => PrepareResult, 1050: select_query => SelectQuery, 1051: select_result => SelectResult}). 1052: 1053: 1054: 1055: transform_selected(to_binary, _Config, SelectResult) -> 1056: selected_to_binary(SelectResult); 1057: transform_selected(unescape_binary, Config, SelectResult) -> 1058: selected_unescape(Config, SelectResult); 1059: transform_selected(boolean_to_binary_int, Config, SelectResult) -> 1060: selected_boolean_to_binary_int(Config, SelectResult). 1061: 1062: %% To KISS, we just test on a table with one row. 1063: check_like(Config, TextMap = #{text := TextValue, 1064: matching := MatchingList, 1065: not_matching := NotMatchingList}) -> 1066: SValue = escape_string_or_null(Config, TextValue), 1067: EraseResult = erase_table(Config), 1068: InsertQuery = ["INSERT INTO test_types (unicode) " 1069: "VALUES (", use_escaped(Config, SValue), ")"], 1070: InsertResult = sql_query(Config, InsertQuery), 1071: Info = #{erase_result => EraseResult, 1072: insert_query => InsertQuery, 1073: insert_query_binary => iolist_to_binary(InsertQuery), 1074: insert_result => InsertResult, 1075: text_map => TextMap}, 1076: [check_like_matching(Config, TextValue, Matching, Info) 1077: || Matching <- MatchingList], 1078: [check_like_not_matching(Config, TextValue, NotMatching, Info) 1079: || NotMatching <- NotMatchingList]. 1080: 1081: check_like_matching(Config, TextValue, Matching, Info) -> 1082: SLike = escape_like(Config, Matching), 1083: SelectQuery = ["SELECT unicode FROM test_types " 1084: "WHERE unicode LIKE ", use_escaped_like(Config, SLike)], 1085: SelectResult = sql_query(Config, SelectQuery), 1086: %% Compare as binaries 1087: ?assert_equal_extra({selected, [{TextValue}]}, 1088: SelectResult, 1089: Info#{pattern => Matching, 1090: select_query => SelectQuery, 1091: select_result => SelectResult}). 1092: 1093: check_like_not_matching(Config, _TextValue, NotMatching, Info) -> 1094: SLike = escape_like(Config, NotMatching), 1095: SelectQuery = ["SELECT unicode FROM test_types " 1096: "WHERE unicode LIKE ", use_escaped_like(Config, SLike)], 1097: SelectResult = sql_query(Config, SelectQuery), 1098: %% Compare as binaries 1099: ?assert_equal_extra({selected, []}, 1100: SelectResult, 1101: Info#{pattern => NotMatching, 1102: select_query => SelectQuery, 1103: select_result => SelectResult}). 1104: 1105: compare_selected(_Config, {selected, [{SelValue}]}, Value) -> 1106: drop_common_prefix(0, SelValue, Value); 1107: compare_selected(_Config, _, _Value) -> 1108: nomatch. 1109: 1110: drop_common_prefix(Pos, <<X, SelValue/binary>>, <<X, Value/binary>>) -> 1111: drop_common_prefix(Pos+1, SelValue, Value); 1112: drop_common_prefix(Pos, SelValue, Value) -> 1113: #{pos => Pos, 1114: selected_suffix => safe_binary(100, SelValue), 1115: expected_suffix => safe_binary(100, Value)}. 1116: 1117: db_engine() -> 1118: escalus_ejabberd:rpc(mongoose_rdbms, db_engine, [host_type()]). 1119: 1120: is_odbc() -> 1121: db_engine() == odbc. 1122: 1123: is_pgsql() -> 1124: db_engine() == pgsql. 1125: 1126: is_mysql() -> 1127: db_engine() == mysql. 1128: 1129: stop_global_default_pool(Pools) -> 1130: [GlobalRdbmsPool] = [Pool || Pool = #{type := rdbms, scope := global, tag := default} <- Pools], 1131: ok = rpc(mim(), mongoose_wpool, stop, [rdbms, global, default]), 1132: GlobalRdbmsPool. 1133: 1134: start_local_host_type_pool(Pools) -> 1135: [GlobalRdbmsPool] = [Pool || Pool = #{type := rdbms, scope := global, tag := default} <- Pools], 1136: LocalHostTypePool = GlobalRdbmsPool#{scope := host_type(), tag := tag()}, 1137: rpc(mim(), mongoose_wpool, start_configured_pools, [[LocalHostTypePool], [host_type()]]). 1138: 1139: escape_column(Name) -> 1140: case is_mysql() of 1141: true -> 1142: <<"`", Name/binary, "`">>; 1143: false -> 1144: Name 1145: end. 1146: 1147: slow_rpc(M, F, A) -> 1148: Node = ct:get_config({hosts, mim, node}), 1149: Cookie = escalus_ct:get_config(ejabberd_cookie), 1150: Res = escalus_rpc:call(Node, M, F, A, timer:seconds(30), Cookie), 1151: case Res of 1152: {badrpc, timeout} -> 1153: {badrpc, {timeout, M, F}}; 1154: _ -> 1155: Res 1156: end. 1157: 1158: check_not_received(Msg) -> 1159: receive 1160: Msg -> 1161: error({msg_received, Msg}); 1162: _ -> 1163: check_not_received(Msg) 1164: after 0 -> 1165: ok 1166: end. 1167: 1168: check_like_prep(Config, TextMap = #{text := TextValue, 1169: matching := MatchingList, 1170: not_matching := NotMatchingList}) -> 1171: EraseResult = erase_table(Config), 1172: Name = insert_unicode_prep, 1173: SelName = select_unicode_prep, 1174: Table = test_types, 1175: Fields = [<<"unicode">>], 1176: InsertQuery = <<"INSERT INTO test_types (unicode) VALUES (?)">>, 1177: SelectQuery = <<"SELECT unicode FROM test_types WHERE unicode LIKE ? ESCAPE '$'">>, 1178: PrepareResult = sql_prepare(Config, Name, Table, Fields, InsertQuery), 1179: PrepareSelResult = sql_prepare(Config, SelName, Table, Fields, SelectQuery), 1180: Parameters = [TextValue], 1181: InsertResult = sql_execute(Config, Name, Parameters), 1182: Info = #{erase_result => EraseResult, 1183: insert_query => InsertQuery, 1184: prepare_result => PrepareResult, 1185: insert_result => InsertResult, 1186: prepare_select_result => PrepareSelResult, 1187: text_map => TextMap}, 1188: [check_like_matching_prep(SelName, Config, TextValue, Matching, Info) 1189: || Matching <- MatchingList], 1190: [check_like_not_matching_prep(SelName, Config, TextValue, NotMatching, Info) 1191: || NotMatching <- NotMatchingList]. 1192: 1193: check_like_matching_prep(SelName, Config, TextValue, Matching, Info) -> 1194: SMatching = escape_prepared_like(Config, Matching), 1195: Parameters = [<<"%", SMatching/binary, "%">>], 1196: SelectResult = sql_execute(Config, SelName, Parameters), 1197: %% Compare as binaries 1198: ?assert_equal_extra({selected, [{TextValue}]}, 1199: SelectResult, 1200: Info#{pattern => Matching, 1201: select_result => SelectResult}). 1202: 1203: check_like_not_matching_prep(SelName, Config, _TextValue, NotMatching, Info) -> 1204: SNotMatching = escape_prepared_like(Config, NotMatching), 1205: Parameters = [<<"%", SNotMatching/binary, "%">>], 1206: SelectResult = sql_execute(Config, SelName, Parameters), 1207: %% Compare as binaries 1208: ?assert_equal_extra({selected, []}, 1209: SelectResult, 1210: Info#{pattern => NotMatching, 1211: select_result => SelectResult}).