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("eunit/include/eunit.hrl"). 21: 22: %% We need assert from it 23: -include("mam_helper.hrl"). 24: 25: -import(domain_helper, [host_type/0]). 26: 27: %%-------------------------------------------------------------------- 28: %% Suite configuration 29: %%-------------------------------------------------------------------- 30: 31: all() -> 32: [{group, rdbms_queries}]. 33: 34: groups() -> 35: [{rdbms_queries, [], rdbms_queries_cases()}]. 36: 37: rdbms_queries_cases() -> 38: [select_one_works_case, 39: select_ascii_string_works_case, 40: read_int32_case, 41: read_int64_case, 42: read_unicode_case, 43: read_unicode250_case, 44: read_ascii_char_case, 45: read_ascii_string_case, 46: read_binary_8k_case, 47: read_binary_65k_case, 48: read_binary_16m_case, 49: read_enum_char_case, 50: read_boolean_case, 51: 52: read_prep_int32_case, 53: read_prep_int64_case, 54: read_prep_unicode_case, 55: read_prep_unicode250_case, 56: read_prep_ascii_char_case, 57: read_prep_ascii_string_case, 58: read_prep_binary_8k_case, 59: read_prep_binary_65k_case, 60: read_prep_binary_16m_case, 61: read_prep_enum_char_case, 62: read_prep_boolean_case, 63: 64: select_like_case, 65: select_like_prep_case, 66: 67: insert_batch_with_null_case, 68: test_cast_insert, 69: test_request_insert, 70: arguments_from_two_tables]. 71: 72: suite() -> 73: escalus:suite(). 74: 75: %%-------------------------------------------------------------------- 76: %% Init & teardown 77: %%-------------------------------------------------------------------- 78: init_per_suite(Config) -> 79: case not ct_helper:is_ct_running() 80: orelse mongoose_helper:is_rdbms_enabled(host_type()) of 81: false -> {skip, rdbms_or_ct_not_running}; 82: true -> escalus:init_per_suite(Config) 83: end. 84: 85: end_per_suite(Config) -> 86: escalus:end_per_suite(Config). 87: 88: init_per_testcase(CaseName, Config) -> 89: escalus:init_per_testcase(CaseName, Config). 90: 91: end_per_testcase(CaseName, Config) -> 92: escalus:end_per_testcase(CaseName, Config). 93: 94: %%-------------------------------------------------------------------- 95: %% Data for cases 96: %%-------------------------------------------------------------------- 97: 98: int32_values() -> 99: [1, -1, 0, 42, 2147483647, null]. 100: 101: int64_values() -> 102: [9223372036854775807, 103: null]. 104: 105: ascii_string_values() -> 106: [<<>>, <<"1">>, <<"test">>, 107: <<"\\">>, <<"\\\\">>, 108: <<"'">>, <<"''">>, <<"'''">>, 109: <<"\"">>, <<"\"\"">>, 110: <<"\r\n">>, <<"\r">>, <<"\n">>, 111: binary:copy(<<"a">>, 250), null]. 112: 113: unicode_values() -> 114: ascii_string_values() ++ 115: [<<"юникод"/utf8>>, <<"😁"/utf8>>] 116: ++ 117: %% Would fail with binary_data_8k and mssql. 118: %% For some reason mssql returns string "7878...." of length 4000. 119: %% What is 78? 16#78 = 120 = $x. 120: %% i.e. half of 8000 bytes for data. 121: %% Probably 2 bytes encoding is used for this. 122: % [binary:copy(<<$x>>, 4001), 123: %% Helps to debug if we don't consume all data from a buffer. 124: %% Than there would be a gap of missing numbers in the middle. 125: %% 1000 of 1-es, 1000 of 2-s, ..., 1000 of 10-s. 126: %% 127: %% In one version of eodbc, it returns 5,5,5,5... instead of 1,1,1,1... 128: %% 129: %% Also, 130: %% eodbc:sql_query(Conn, "SELECT convert(varbinary(max), binary_data_8k) FROM test_types") = gives correct result. 131: %% but 132: %% eodbc:sql_query(Conn, "SELECT binary_data_8k FROM test_types") = gives not correct result. 133: %% 134: %% eodbc:sql_query(Conn, "SELECT convert(varbinary(1000), binary_data_8k) FROM test_types") = gives correct result. 135: %% gives 010101.... as expected 136: [iolist_to_binary([lists:duplicate(1000, X) || X <- lists:seq(1, 10)]), 137: binary:copy(<<$a>>, 10000), 138: %% There is a bug with 8001 chars limit in upstream odbc 139: %% We use a fork arcusfelis/eodbc, that has the bug fixed 140: %% https://bugs.erlang.org/browse/ERL-421 141: binary:copy(<<10>>, 10000), null]. 142: 143: binary_values() -> 144: [<<0>>, <<"255">>, 145: <<240,159>>, %% Incomplete unicode 146: <<240,159,0>>, %% Invalid unicode 147: iolist_to_binary(lists:seq(0, 255)), 148: %% one kilobyte 149: binary:copy(<<1>>, 1024), 150: %% two kilobytes 151: binary:copy(<<2>>, 2048), 152: binary:copy(<<5>>, 1024*5), 153: %% There is a bug with 8001 chars limit in upstream odbc 154: %% We use a fork arcusfelis/eodbc, that has the bug fixed 155: %% https://bugs.erlang.org/browse/ERL-421 156: binary:copy(<<8>>, 8002), 157: binary:copy(<<0>>, 100000), 158: null 159: ] ++ 160: case is_odbc() orelse is_pgsql() of 161: true -> 162: []; 163: false -> 164: %% FIXME long data causes timeout with mssql 165: %% 166: %% FIXME %% epgsql_sock:handle_info/2 is not optimized 167: %% The query takes 30 seconds on Postgres 168: %% mongoose_rdbms:sql_query(<<"localhost">>, <<"SELECT binary_data_16m FROM test_types">>). 169: [binary:copy(<<16>>, 16777215)] 170: end. 171: 172: binary_8k_values() -> 173: truncate_binaries(8000, unicode_values() ++ binary_values()). 174: 175: binary_65k_values() -> 176: truncate_binaries(65535, unicode_values() ++ binary_values()). 177: 178: binary_16m_values() -> 179: truncate_binaries(16777215, unicode_values() ++ binary_values()). 180: 181: ascii_char_values() -> 182: [<<"a">>, <<"b">>]. 183: 184: enum_char_values() -> 185: [<<"A">>, <<"B">>, <<"C">>]. 186: 187: simple_like_texts() -> 188: [#{text => <<"hello user!">>, 189: not_matching => [<<"hi">>, <<"help">>], 190: matching => [<<"hello">>, <<"user">>, <<"hell">>]}, 191: #{text => <<60,79,67,32,59,48,63,58,48>>, 192: not_matching => [<<62,66,64,48,65,66,53,66>>], 193: matching => [<<60,79,67>>]}]. 194: 195: like_texts() -> 196: simple_like_texts() ++ 197: [#{text => <<"abc%">>, 198: not_matching => [<<"ab%">>, <<"%bc%">>], 199: matching => [<<"abc%">>, <<"abc">>]}, 200: #{text => <<"żółć_"/utf8>>, 201: not_matching => [<<"_ółć_"/utf8>>], 202: matching => [<<"żół"/utf8>>, <<"ółć_"/utf8>>]}]. 203: 204: %%-------------------------------------------------------------------- 205: %% Test cases 206: %%-------------------------------------------------------------------- 207: 208: %% Checks, that we at least can connect to db 209: select_one_works_case(Config) -> 210: ?assert_equal({selected, [{<<"1">>}]}, 211: %% Postgres direct driver returns <<"1">> 212: %% Everyone else return 1 213: selected_to_binary(sql_query(Config, <<"SELECT 1">>))). 214: 215: %% Should be binary. 216: %% Unicode is another case. 217: select_ascii_string_works_case(Config) -> 218: ?assert_equal({selected, [{<<"ascii">>}]}, 219: sql_query(Config, <<"SELECT 'ascii'">>)). 220: 221: %% Writes and reads values of this datatype back 222: read_int32_case(Config) -> 223: Values = int32_values(), 224: [check_int32(Config, Value) || Value <- Values]. 225: 226: read_int64_case(Config) -> 227: Values = int32_values() ++ int64_values(), 228: [check_int64(Config, Value) || Value <- Values]. 229: 230: read_unicode_case(Config) -> 231: [check_unicode(Config, Value) || Value <- unicode_values()]. 232: 233: read_unicode250_case(Config) -> 234: [check_unicode250(Config, Value) || Value <- unicode_values(), byte_size(Value) < 250]. 235: 236: read_ascii_char_case(Config) -> 237: [check_ascii_char(Config, Value) || Value <- ascii_char_values()]. 238: 239: read_ascii_string_case(Config) -> 240: [check_ascii_string(Config, Value) 241: || Value <- ascii_char_values() ++ ascii_string_values()]. 242: 243: read_binary_8k_case(Config) -> 244: [check_binary_8k(Config, Value) || Value <- binary_8k_values()]. 245: 246: read_binary_65k_case(Config) -> 247: [check_binary_65k(Config, Value) || Value <- binary_65k_values()]. 248: 249: read_binary_16m_case(Config) -> 250: [check_binary_16m(Config, Value) || Value <- binary_16m_values()]. 251: 252: read_enum_char_case(Config) -> 253: [check_enum_char(Config, Value) || Value <- enum_char_values()]. 254: 255: read_boolean_case(Config) -> 256: [check_boolean(Config, Value) || Value <- [true, false]]. 257: 258: %%-------------------------------------------------------------------- 259: %% Prepared cases 260: %%-------------------------------------------------------------------- 261: 262: read_prep_int32_case(Config) -> 263: Values = int32_values(), 264: [check_prep_int32(Config, Value) || Value <- Values]. 265: 266: read_prep_int64_case(Config) -> 267: Values = int32_values() ++ int64_values(), 268: [check_prep_int64(Config, Value) || Value <- Values]. 269: 270: read_prep_unicode_case(Config) -> 271: [check_prep_unicode(Config, Value) || Value <- unicode_values()]. 272: 273: read_prep_unicode250_case(Config) -> 274: [check_prep_unicode250(Config, Value) || Value <- unicode_values(), byte_size(Value) < 250]. 275: 276: read_prep_ascii_char_case(Config) -> 277: [check_prep_ascii_char(Config, Value) || Value <- ascii_char_values()]. 278: 279: read_prep_ascii_string_case(Config) -> 280: [check_prep_ascii_string(Config, Value) 281: || Value <- ascii_char_values() ++ ascii_string_values()]. 282: 283: read_prep_binary_8k_case(Config) -> 284: [check_prep_binary_8k(Config, Value) || Value <- binary_8k_values()]. 285: 286: read_prep_binary_65k_case(Config) -> 287: [check_prep_binary_65k(Config, Value) || Value <- binary_65k_values()]. 288: 289: read_prep_binary_16m_case(Config) -> 290: [check_prep_binary_16m(Config, Value) || Value <- binary_16m_values()]. 291: 292: read_prep_enum_char_case(Config) -> 293: [check_prep_enum_char(Config, Value) || Value <- enum_char_values()]. 294: 295: read_prep_boolean_case(Config) -> 296: [check_prep_boolean(Config, Value) || Value <- [0, 1]]. 297: 298: truncate_binaries(Len, List) -> 299: [truncate_binary(Len, Bin) || Bin <- List]. 300: 301: truncate_binary(Len, Bin) when byte_size(Bin) > Len -> 302: binary:part(Bin, {0,Len}); 303: truncate_binary(_Len, Bin) -> 304: Bin. 305: 306: safe_binary(Len, Bin) when byte_size(Bin) > Len -> 307: #{what => truncated_safe_binary, 308: truncated_length => Len, 309: total_length => byte_size(Bin), 310: truncated_binary => binary:part(Bin, {0,Len})}; 311: safe_binary(_Len, Bin) -> 312: Bin. 313: 314: arguments_from_two_tables(Config) -> 315: erase_users(Config), 316: sql_prepare(Config, select_multi_args, users, [password, 'last.seconds'], 317: <<"SELECT users.username from users " 318: " LEFT JOIN last ON (last.username = users.username) " 319: " WHERE users.password = ? AND last.seconds > ?">>), 320: UserInsert = "INSERT INTO users (username, server, password) VALUES ", 321: sql_query(Config, UserInsert ++ "('alice', 'domain', 'secret')"), 322: sql_query(Config, UserInsert ++ "('bob', 'domain', 'billy')"), 323: LastInsert = "INSERT INTO last (username, server, seconds, state) VALUES ", 324: sql_query(Config, LastInsert ++ "('alice', 'domain', 1615368268, 'ok')"), 325: sql_query(Config, LastInsert ++ "('bob', 'domain', 1610000000, 'cool')"), 326: SelectResult = sql_execute(Config, select_multi_args, [<<"secret">>, 1611000000]), 327: ?assert_equal({selected, [{<<"alice">>}]}, SelectResult), 328: erase_users(Config), 329: ok. 330: 331: %% Ensures that ODBC uses a correct type when encoding NULL 332: %% and it does not interfere with non-null values 333: insert_batch_with_null_case(Config) -> 334: erase_table(Config), 335: sql_prepare(Config, insert_batch, test_types, [unicode, unicode], 336: <<"INSERT INTO test_types(unicode) VALUES (?), (?)">>), 337: sql_execute(Config, insert_batch, [null, <<"check1">>]), 338: sql_execute(Config, insert_batch, [<<"check2">>, null]), 339: SelectResult = sql_query(Config, "SELECT unicode FROM test_types"), 340: %% Sorting with null values is DB specific, so sort it with Erlang 341: ?assert_equal({selected, [{null}, {null}, {<<"check1">>}, {<<"check2">>}]}, 342: selected_to_sorted(SelectResult)). 343: 344: test_cast_insert(Config) -> 345: erase_table(Config), 346: sql_prepare(Config, insert_one, test_types, [unicode], 347: <<"INSERT INTO test_types(unicode) VALUES (?)">>), 348: sql_execute_cast(Config, insert_one, [<<"check1">>]), 349: sql_query_cast(Config, <<"INSERT INTO test_types(unicode) VALUES ('check2')">>), 350: mongoose_helper:wait_until( 351: fun() -> 352: SelectResult = sql_query(Config, "SELECT unicode FROM test_types"), 353: ?assertEqual({selected, [{<<"check1">>}, {<<"check2">>}]}, 354: selected_to_sorted(SelectResult)) 355: end, ok, #{name => cast_queries}). 356: 357: test_request_insert(Config) -> 358: erase_table(Config), 359: sql_prepare(Config, insert_one, test_types, [unicode], 360: <<"INSERT INTO test_types(unicode) VALUES (?)">>), 361: sql_execute_request(Config, insert_one, [<<"check1">>]), 362: sql_query_request(Config, <<"INSERT INTO test_types(unicode) VALUES ('check2')">>), 363: mongoose_helper:wait_until( 364: fun() -> 365: SelectResult = sql_query(Config, "SELECT unicode FROM test_types"), 366: ?assertEqual({selected, [{<<"check1">>}, {<<"check2">>}]}, 367: selected_to_sorted(SelectResult)) 368: end, ok, #{name => request_queries}). 369: 370: %%-------------------------------------------------------------------- 371: %% Text searching 372: %%-------------------------------------------------------------------- 373: 374: select_like_case(Config) -> 375: %% Non-prepared queries don't support proper LIKE escaping 376: [check_like(Config, TextMap) || TextMap <- simple_like_texts()]. 377: 378: select_like_prep_case(Config) -> 379: [check_like_prep(Config, TextMap) || TextMap <- like_texts()]. 380: 381: %%-------------------------------------------------------------------- 382: %% Helpers 383: %%-------------------------------------------------------------------- 384: 385: sql_query(_Config, Query) -> 386: slow_rpc(mongoose_rdbms, sql_query, [host_type(), Query]). 387: 388: sql_prepare(_Config, Name, Table, Fields, Query) -> 389: escalus_ejabberd:rpc(mongoose_rdbms, prepare, [Name, Table, Fields, Query]). 390: 391: sql_execute(_Config, Name, Parameters) -> 392: slow_rpc(mongoose_rdbms, execute, [host_type(), Name, Parameters]). 393: 394: sql_execute_cast(_Config, Name, Parameters) -> 395: slow_rpc(mongoose_rdbms, execute_cast, [host_type(), Name, Parameters]). 396: 397: sql_query_cast(_Config, Query) -> 398: slow_rpc(mongoose_rdbms, sql_query_cast, [host_type(), Query]). 399: 400: sql_execute_request(_Config, Name, Parameters) -> 401: slow_rpc(mongoose_rdbms, execute_request, [host_type(), Name, Parameters]). 402: 403: sql_query_request(_Config, Query) -> 404: slow_rpc(mongoose_rdbms, sql_query_request, [host_type(), Query]). 405: 406: escape_null(_Config) -> 407: escalus_ejabberd:rpc(mongoose_rdbms, escape_null, []). 408: 409: escape_string(_Config, Value) -> 410: escalus_ejabberd:rpc(mongoose_rdbms, escape_string, [Value]). 411: 412: escape_binary(_Config, Value) -> 413: slow_rpc(mongoose_rdbms, escape_binary, [host_type(), Value]). 414: 415: escape_boolean(_Config, Value) -> 416: escalus_ejabberd:rpc(mongoose_rdbms, escape_boolean, [Value]). 417: 418: escape_like(_Config, Value) -> 419: escalus_ejabberd:rpc(mongoose_rdbms, escape_like, [Value]). 420: 421: escape_prepared_like(_Config, Value) -> 422: escalus_ejabberd:rpc(mongoose_rdbms, escape_prepared_like, [Value]). 423: 424: unescape_binary(_Config, Value) -> 425: escalus_ejabberd:rpc(mongoose_rdbms, unescape_binary, [host_type(), Value]). 426: 427: use_escaped(_Config, Value) -> 428: escalus_ejabberd:rpc(mongoose_rdbms, use_escaped, [Value]). 429: 430: use_escaped_like(_Config, Value) -> 431: escalus_ejabberd:rpc(mongoose_rdbms, use_escaped_like, [Value]). 432: 433: escape_string_or_null(Config, null) -> 434: escape_null(Config); 435: escape_string_or_null(Config, TextValue) -> 436: escape_string(Config, TextValue). 437: 438: escape_binary_or_null(Config, null) -> 439: escape_null(Config); 440: escape_binary_or_null(Config, Value) -> 441: escape_binary(Config, Value). 442: 443: decode_boolean(_Config, Value) -> 444: escalus_ejabberd:rpc(mongoose_rdbms, to_bool, [Value]). 445: 446: erase_table(Config) -> 447: sql_query(Config, <<"TRUNCATE TABLE test_types">>). 448: 449: erase_users(Config) -> 450: sql_query(Config, <<"TRUNCATE TABLE users">>), 451: sql_query(Config, <<"TRUNCATE TABLE last">>). 452: 453: check_int32(Config, Value) -> 454: check_generic_integer(Config, Value, <<"int32">>). 455: 456: check_int64(Config, Value) -> 457: check_generic_integer(Config, Value, <<"int64">>). 458: 459: check_generic_integer(Config, Value, Column) -> 460: EraseResult = erase_table(Config), 461: InsertQuery = <<"INSERT INTO test_types (", Column/binary, ") " 462: "VALUES (", (integer_or_null_to_binary(Value))/binary, ")">>, 463: SelectQuery = <<"SELECT ", Column/binary, " FROM test_types">>, 464: InsertResult = sql_query(Config, InsertQuery), 465: SelectResult = sql_query(Config, SelectQuery), 466: %% Compare as binaries 467: ?assert_equal_extra({selected, [{integer_to_binary_or_null(Value)}]}, 468: selected_to_binary(SelectResult), 469: #{column => Column, 470: erase_result => EraseResult, 471: test_value => Value, 472: insert_query => InsertQuery, 473: select_query => SelectQuery, 474: select_result => SelectResult, 475: insert_result => InsertResult}). 476: 477: integer_or_null_to_binary(null) -> <<"NULL">>; 478: integer_or_null_to_binary(X) -> integer_to_binary(X). 479: 480: integer_to_binary_or_null(null) -> null; 481: integer_to_binary_or_null(X) -> integer_to_binary(X). 482: 483: %% Helper function to transform values to an uniform format. 484: %% Single tuple, single element case. 485: %% In ODBC int32 is integer, but int64 is binary. 486: selected_to_binary({selected, [{Value}]}) when is_integer(Value) -> 487: {selected, [{integer_to_binary(Value)}]}; 488: selected_to_binary(Other) -> 489: Other. 490: 491: selected_to_sorted({selected, Rows}) -> 492: {selected, lists:sort(Rows)}; 493: selected_to_sorted(Other) -> 494: Other. 495: 496: value_to_binary(Value) when is_integer(Value) -> 497: integer_to_binary(Value); 498: value_to_binary(Value) -> 499: Value. 500: 501: check_unicode250(Config, Value) -> 502: check_unicode_generic(Config, Value, <<"unicode250">>). 503: 504: check_unicode(Config, Value) -> 505: check_unicode_generic(Config, Value, <<"unicode">>). 506: 507: check_unicode_generic(Config, Value, Column) -> 508: SValue = escape_string_or_null(Config, Value), 509: EraseResult = erase_table(Config), 510: InsertQuery = ["INSERT INTO test_types (", Column, ") " 511: "VALUES (", use_escaped(Config, SValue), ")"], 512: SelectQuery = <<"SELECT ", Column/binary, " FROM test_types">>, 513: InsertResult = sql_query(Config, InsertQuery), 514: SelectResult = sql_query(Config, SelectQuery), 515: %% Compare as binaries 516: ?assert_equal_extra({selected, [{Value}]}, 517: SelectResult, 518: #{column => Column, 519: erase_result => EraseResult, 520: expected_length => byte_size_or_null(Value), 521: selected_length => maybe_selected_length(Config, SelectResult), 522: compare_selected => compare_selected(Config, SelectResult, Value), 523: test_value => Value, 524: insert_query => InsertQuery, 525: insert_query_binary => iolist_to_binary(InsertQuery), 526: select_query => SelectQuery, 527: select_result => SelectResult, 528: insert_result => InsertResult}). 529: 530: check_ascii_char(Config, Value) -> 531: SValue = escape_string_or_null(Config, Value), 532: EraseResult = erase_table(Config), 533: InsertQuery = ["INSERT INTO test_types (ascii_char) " 534: "VALUES (", use_escaped(Config, SValue), ")"], 535: SelectQuery = <<"SELECT ascii_char FROM test_types">>, 536: InsertResult = sql_query(Config, InsertQuery), 537: SelectResult = sql_query(Config, SelectQuery), 538: %% Compare as binaries 539: ?assert_equal_extra({selected, [{Value}]}, 540: SelectResult, 541: #{erase_result => EraseResult, 542: test_value => Value, 543: insert_query => InsertQuery, 544: insert_query_binary => iolist_to_binary(InsertQuery), 545: select_query => SelectQuery, 546: select_result => SelectResult, 547: insert_result => InsertResult}). 548: 549: check_ascii_string(Config, Value) -> 550: SValue = escape_string_or_null(Config, Value), 551: EraseResult = erase_table(Config), 552: InsertQuery = ["INSERT INTO test_types (ascii_string) " 553: "VALUES (", use_escaped(Config, SValue), ")"], 554: SelectQuery = <<"SELECT ascii_string FROM test_types">>, 555: InsertResult = sql_query(Config, InsertQuery), 556: SelectResult = sql_query(Config, SelectQuery), 557: %% Compare as binaries 558: ?assert_equal_extra({selected, [{Value}]}, 559: SelectResult, 560: #{erase_result => EraseResult, 561: test_value => Value, 562: insert_query => InsertQuery, 563: insert_query_binary => iolist_to_binary(InsertQuery), 564: select_query => SelectQuery, 565: select_result => SelectResult, 566: insert_result => InsertResult}). 567: 568: check_binary_8k(Config, Value) -> 569: check_binary(Config, Value, <<"binary_data_8k">>). 570: 571: check_binary_65k(Config, Value) -> 572: check_binary(Config, Value, <<"binary_data_65k">>). 573: 574: check_binary_16m(Config, Value) -> 575: check_binary(Config, Value, <<"binary_data_16m">>). 576: 577: check_binary(Config, Value, Column) -> 578: SValue = escape_binary_or_null(Config, Value), 579: EraseResult = erase_table(Config), 580: InsertQuery = ["INSERT INTO test_types (", Column, ") " 581: "VALUES (", use_escaped(Config, SValue), ")"], 582: SelectQuery = <<"SELECT ", Column/binary, " FROM test_types">>, 583: InsertResult = sql_query(Config, InsertQuery), 584: SelectResult = sql_query(Config, SelectQuery), 585: %% Compare as binaries 586: ?assert_equal_extra({selected, [{Value}]}, 587: selected_unescape(Config, SelectResult), 588: #{erase_result => EraseResult, 589: inserted_length => byte_size_or_null(Value), 590: %% pgsql+odbc can truncate binaries 591: maybe_selected_length => maybe_selected_length(Config, SelectResult), 592: maybe_selected_tail => maybe_selected_tail(Config, SelectResult), 593: compare_selected => compare_selected(Config, selected_unescape(Config, SelectResult), Value), 594: test_value => Value, 595: insert_query_binary => iolist_to_binary(InsertQuery), 596: select_query => SelectQuery, 597: select_result => SelectResult, 598: insert_result => InsertResult}). 599: 600: byte_size_or_null(null) -> 601: null; 602: byte_size_or_null(Value) -> 603: byte_size(Value). 604: 605: check_enum_char(Config, Value) when is_binary(Value) -> 606: SValue = escape_string_or_null(Config, Value), 607: EraseResult = erase_table(Config), 608: InsertQuery = ["INSERT INTO test_types (enum_char) " 609: "VALUES (", use_escaped(Config, SValue), ")"], 610: SelectQuery = <<"SELECT enum_char FROM test_types">>, 611: InsertResult = sql_query(Config, InsertQuery), 612: SelectResult = sql_query(Config, SelectQuery), 613: %% Compare as binaries 614: ?assert_equal_extra({selected, [{Value}]}, 615: SelectResult, 616: #{erase_result => EraseResult, 617: test_value => Value, 618: insert_query => InsertQuery, 619: insert_query_binary => iolist_to_binary(InsertQuery), 620: select_query => SelectQuery, 621: select_result => SelectResult, 622: insert_result => InsertResult}). 623: 624: check_boolean(Config, Value) when is_boolean(Value) -> 625: SValue = escape_boolean(Config, Value), 626: EraseResult = erase_table(Config), 627: InsertQuery = ["INSERT INTO test_types (bool_flag) " 628: "VALUES (", use_escaped(Config, SValue), ")"], 629: SelectQuery = <<"SELECT bool_flag FROM test_types">>, 630: InsertResult = sql_query(Config, InsertQuery), 631: SelectResult = sql_query(Config, SelectQuery), 632: %% Compare as binaries 633: ?assert_equal_extra({selected, [{Value}]}, 634: selected_decode_boolean(Config, SelectResult), 635: #{erase_result => EraseResult, 636: test_value => Value, 637: insert_query => InsertQuery, 638: insert_query_binary => iolist_to_binary(InsertQuery), 639: select_query => SelectQuery, 640: select_result => SelectResult, 641: insert_result => InsertResult}). 642: 643: selected_unescape(_Config, {selected, [{null}]}) -> 644: {selected, [{null}]}; 645: selected_unescape(Config, {selected, [{Value}]}) -> 646: {selected, [{unescape_binary(Config, Value)}]}; 647: selected_unescape(_Config, Other) -> 648: Other. 649: 650: selected_decode_boolean(Config, {selected, [{Value}]}) -> 651: {selected, [{decode_boolean(Config, Value)}]}; 652: selected_decode_boolean(_Config, Other) -> 653: Other. 654: 655: selected_boolean_to_binary_int(Config, {selected, [{Value}]}) -> 656: {selected, [{boolean_to_binary_int(decode_boolean(Config, Value))}]}; 657: selected_boolean_to_binary_int(_Config, Other) -> 658: Other. 659: 660: boolean_to_binary_int(true) -> <<"1">>; 661: boolean_to_binary_int(false) -> <<"0">>. 662: 663: maybe_selected_length(_Config, {selected, [{Value}]}) when is_binary(Value) -> 664: byte_size(Value); 665: maybe_selected_length(_Config, _Other) -> 666: unknown. 667: 668: maybe_selected_tail(Config, Selected) -> 669: maybe_selected_tail(Config, Selected, 100). 670: 671: maybe_selected_tail(_Config, {selected, [{Value}]}, TailLen) 672: when is_binary(Value), byte_size(Value) > TailLen -> 673: binary:part(Value, {byte_size(Value), -TailLen}); 674: maybe_selected_tail(_Config, {selected, [{Value}]}, _TailLen) -> 675: Value; 676: maybe_selected_tail(_Config, _Other, _TailLen) -> 677: unknown. 678: 679: check_prep_int32(Config, Value) -> 680: check_generic_prep_integer(Config, Value, <<"int32">>). 681: 682: check_prep_int64(Config, Value) -> 683: check_generic_prep_integer(Config, Value, <<"int64">>). 684: 685: check_prep_unicode(Config, Value) -> 686: check_generic_prep(Config, Value, <<"unicode">>). 687: 688: check_prep_unicode250(Config, Value) -> 689: check_generic_prep(Config, Value, <<"unicode250">>). 690: 691: %% Char is ascii string of length one 692: check_prep_ascii_char(Config, Value) -> 693: check_generic_prep(Config, Value, <<"unicode">>). 694: 695: %% Varchar 696: check_prep_ascii_string(Config, Value) -> 697: check_generic_prep(Config, Value, <<"ascii_string">>). 698: 699: check_prep_binary_65k(Config, Value) -> 700: %% MSSQL returns binaries in HEX encoding 701: check_generic_prep(Config, Value, <<"binary_data_65k">>, unescape_binary). 702: 703: check_prep_binary_8k(Config, Value) -> 704: %% MSSQL returns binaries in HEX encoding 705: check_generic_prep(Config, Value, <<"binary_data_8k">>, unescape_binary). 706: 707: check_prep_binary_16m(Config, Value) -> 708: %% MSSQL returns binaries in HEX encoding 709: check_generic_prep(Config, Value, <<"binary_data_16m">>, unescape_binary). 710: 711: check_generic_prep_integer(Config, Value, Column) -> 712: check_generic_prep(Config, Value, Column). 713: 714: check_prep_enum_char(Config, Value) -> 715: check_generic_prep(Config, Value, <<"enum_char">>). 716: 717: check_prep_boolean(Config, Value) -> 718: check_generic_prep(Config, Value, <<"bool_flag">>, boolean_to_binary_int). 719: 720: %% Data types 721: %% {ok, Conn} = odbc:connect("DSN=mongoose-mssql;UID=sa;PWD=mongooseim_secret+ESL123", []). 722: %% odbc:describe_table(Conn, "test_types"). 723: %% [{"unicode",{sql_wvarchar,536870911}}, 724: %% {"binary_data_65k",'SQL_VARBINARY'}, 725: %% {"ascii_char",{sql_char,1}}, 726: %% {"ascii_string",{sql_varchar,250}}, 727: %% {"int32",sql_integer}, 728: %% {"int64",'SQL_BIGINT'}, 729: %% {"int8",sql_tinyint}] 730: 731: check_generic_prep(Config, Value, Column) -> 732: check_generic_prep(Config, Value, Column, to_binary). 733: 734: check_generic_prep(Config, Value, Column, TransformResult) -> 735: EraseResult = erase_table(Config), 736: 737: InsertQuery = <<"INSERT INTO test_types (", Column/binary, ") " 738: "VALUES (?)">>, 739: SelectQuery = <<"SELECT ", Column/binary, " FROM test_types">>, 740: Name = list_to_atom("insert_" ++ binary_to_list(Column)), 741: Table = test_types, 742: Fields = [binary_to_atom(Column, utf8)], 743: PrepareResult = sql_prepare(Config, Name, Table, Fields, InsertQuery), 744: Parameters = [Value], 745: InsertResult = sql_execute(Config, Name, Parameters), 746: SelectResult = sql_query(Config, SelectQuery), 747: %% Compare as binaries 748: ?assert_equal_extra({selected, [{value_to_binary(Value)}]}, 749: transform_selected(TransformResult, Config, SelectResult), 750: #{column => Column, 751: erase_result => EraseResult, 752: test_value => Value, 753: insert_query => InsertQuery, 754: prepare_result => PrepareResult, 755: select_query => SelectQuery, 756: select_result => SelectResult, 757: insert_result => InsertResult}), 758: check_generic_filtered_prep(Config, Value, Column, TransformResult), 759: case is_odbc() of 760: true -> 761: %% TOP is mssql feature, all other databases use LIMIT. 762: check_generic_filtered_top_prep(Config, Value, Column, TransformResult); 763: false -> 764: ok 765: end. 766: 767: %% We want to ensure that variable substitution works in SELECTS too. 768: %% We also want to check the result value is encoded correctly. 769: check_generic_filtered_prep(_Config, null, _Column, _TransformResult) -> 770: skip_null_test; 771: check_generic_filtered_prep(Config, Value, Column, TransformResult) -> 772: SelectQuery = <<"SELECT ", Column/binary, 773: " FROM test_types WHERE ", Column/binary, " = ?">>, 774: Name = list_to_atom("select_filtered_" ++ binary_to_list(Column)), 775: Table = test_types, 776: Fields = [binary_to_atom(Column, utf8)], 777: PrepareResult = sql_prepare(Config, Name, Table, Fields, SelectQuery), 778: Parameters = [Value], 779: SelectResult = sql_execute(Config, Name, Parameters), 780: %% Compare as binaries 781: ?assert_equal_extra({selected, [{value_to_binary(Value)}]}, 782: transform_selected(TransformResult, Config, SelectResult), 783: #{column => Column, 784: test_value => Value, 785: prepare_result => PrepareResult, 786: select_query => SelectQuery, 787: select_result => SelectResult}). 788: 789: check_generic_filtered_top_prep(_Config, null, _Column, _TransformResult) -> 790: skip_null_test; 791: check_generic_filtered_top_prep(Config, Value, Column, TransformResult) -> 792: %% SQL Server requires you to place parenthesis around the argument to top if you pass in a variable: 793: %% https://stackoverflow.com/questions/7038818/ms-sql-exception-incorrect-syntax-near-p0 794: SelectQuery = <<"SELECT TOP (?) ", Column/binary, 795: " FROM test_types WHERE ", Column/binary, " = ?">>, 796: Name = list_to_atom("select_filtered_top_" ++ binary_to_list(Column)), 797: Table = test_types, 798: Fields = [limit, binary_to_atom(Column, utf8)], 799: PrepareResult = sql_prepare(Config, Name, Table, Fields, SelectQuery), 800: Parameters = [30, Value], 801: SelectResult = sql_execute(Config, Name, Parameters), 802: %% Compare as binaries 803: ?assert_equal_extra({selected, [{value_to_binary(Value)}]}, 804: transform_selected(TransformResult, Config, SelectResult), 805: #{column => Column, 806: test_value => Value, 807: prepare_result => PrepareResult, 808: select_query => SelectQuery, 809: select_result => SelectResult}). 810: 811: 812: 813: transform_selected(to_binary, _Config, SelectResult) -> 814: selected_to_binary(SelectResult); 815: transform_selected(unescape_binary, Config, SelectResult) -> 816: selected_unescape(Config, SelectResult); 817: transform_selected(boolean_to_binary_int, Config, SelectResult) -> 818: selected_boolean_to_binary_int(Config, SelectResult). 819: 820: %% To KISS, we just test on a table with one row. 821: check_like(Config, TextMap = #{text := TextValue, 822: matching := MatchingList, 823: not_matching := NotMatchingList}) -> 824: SValue = escape_string_or_null(Config, TextValue), 825: EraseResult = erase_table(Config), 826: InsertQuery = ["INSERT INTO test_types (unicode) " 827: "VALUES (", use_escaped(Config, SValue), ")"], 828: InsertResult = sql_query(Config, InsertQuery), 829: Info = #{erase_result => EraseResult, 830: insert_query => InsertQuery, 831: insert_query_binary => iolist_to_binary(InsertQuery), 832: insert_result => InsertResult, 833: text_map => TextMap}, 834: [check_like_matching(Config, TextValue, Matching, Info) 835: || Matching <- MatchingList], 836: [check_like_not_matching(Config, TextValue, NotMatching, Info) 837: || NotMatching <- NotMatchingList]. 838: 839: check_like_matching(Config, TextValue, Matching, Info) -> 840: SLike = escape_like(Config, Matching), 841: SelectQuery = ["SELECT unicode FROM test_types " 842: "WHERE unicode LIKE ", use_escaped_like(Config, SLike)], 843: SelectResult = sql_query(Config, SelectQuery), 844: %% Compare as binaries 845: ?assert_equal_extra({selected, [{TextValue}]}, 846: SelectResult, 847: Info#{pattern => Matching, 848: select_query => SelectQuery, 849: select_result => SelectResult}). 850: 851: check_like_not_matching(Config, _TextValue, NotMatching, Info) -> 852: SLike = escape_like(Config, NotMatching), 853: SelectQuery = ["SELECT unicode FROM test_types " 854: "WHERE unicode LIKE ", use_escaped_like(Config, SLike)], 855: SelectResult = sql_query(Config, SelectQuery), 856: %% Compare as binaries 857: ?assert_equal_extra({selected, []}, 858: SelectResult, 859: Info#{pattern => NotMatching, 860: select_query => SelectQuery, 861: select_result => SelectResult}). 862: 863: compare_selected(_Config, {selected, [{SelValue}]}, Value) -> 864: drop_common_prefix(0, SelValue, Value); 865: compare_selected(_Config, _, _Value) -> 866: nomatch. 867: 868: drop_common_prefix(Pos, <<X, SelValue/binary>>, <<X, Value/binary>>) -> 869: drop_common_prefix(Pos+1, SelValue, Value); 870: drop_common_prefix(Pos, SelValue, Value) -> 871: #{pos => Pos, 872: selected_suffix => safe_binary(100, SelValue), 873: expected_suffix => safe_binary(100, Value)}. 874: 875: is_odbc() -> 876: escalus_ejabberd:rpc(mongoose_rdbms, db_engine, [host_type()]) == odbc. 877: 878: is_pgsql() -> 879: escalus_ejabberd:rpc(mongoose_rdbms, db_engine, [host_type()]) == pgsql. 880: 881: slow_rpc(M, F, A) -> 882: Node = ct:get_config({hosts, mim, node}), 883: Cookie = escalus_ct:get_config(ejabberd_cookie), 884: Res = escalus_rpc:call(Node, M, F, A, timer:seconds(30), Cookie), 885: case Res of 886: {badrpc, timeout} -> 887: {badrpc, {timeout, M, F}}; 888: _ -> 889: Res 890: end. 891: 892: check_like_prep(Config, TextMap = #{text := TextValue, 893: matching := MatchingList, 894: not_matching := NotMatchingList}) -> 895: EraseResult = erase_table(Config), 896: Name = insert_unicode_prep, 897: SelName = select_unicode_prep, 898: Table = test_types, 899: Fields = [<<"unicode">>], 900: InsertQuery = <<"INSERT INTO test_types (unicode) VALUES (?)">>, 901: SelectQuery = <<"SELECT unicode FROM test_types WHERE unicode LIKE ? ESCAPE '$'">>, 902: PrepareResult = sql_prepare(Config, Name, Table, Fields, InsertQuery), 903: PrepareSelResult = sql_prepare(Config, SelName, Table, Fields, SelectQuery), 904: Parameters = [TextValue], 905: InsertResult = sql_execute(Config, Name, Parameters), 906: Info = #{erase_result => EraseResult, 907: insert_query => InsertQuery, 908: prepare_result => PrepareResult, 909: insert_result => InsertResult, 910: prepare_select_result => PrepareSelResult, 911: text_map => TextMap}, 912: [check_like_matching_prep(SelName, Config, TextValue, Matching, Info) 913: || Matching <- MatchingList], 914: [check_like_not_matching_prep(SelName, Config, TextValue, NotMatching, Info) 915: || NotMatching <- NotMatchingList]. 916: 917: check_like_matching_prep(SelName, Config, TextValue, Matching, Info) -> 918: SMatching = escape_prepared_like(Config, Matching), 919: Parameters = [<<"%", SMatching/binary, "%">>], 920: SelectResult = sql_execute(Config, SelName, Parameters), 921: %% Compare as binaries 922: ?assert_equal_extra({selected, [{TextValue}]}, 923: SelectResult, 924: Info#{pattern => Matching, 925: select_result => SelectResult}). 926: 927: check_like_not_matching_prep(SelName, Config, _TextValue, NotMatching, Info) -> 928: SNotMatching = escape_prepared_like(Config, NotMatching), 929: Parameters = [<<"%", SNotMatching/binary, "%">>], 930: SelectResult = sql_execute(Config, SelName, Parameters), 931: %% Compare as binaries 932: ?assert_equal_extra({selected, []}, 933: SelectResult, 934: Info#{pattern => NotMatching, 935: select_result => SelectResult}).