| 1 | #include "catch.hpp" |
| 2 | #include "test_helpers.hpp" |
| 3 | |
| 4 | using namespace duckdb; |
| 5 | using namespace std; |
| 6 | |
| 7 | TEST_CASE("Test scalar printf" , "[printf]" ) { |
| 8 | unique_ptr<QueryResult> result; |
| 9 | DuckDB db(nullptr); |
| 10 | Connection con(db); |
| 11 | con.EnableQueryVerification(); |
| 12 | |
| 13 | // printf without format specifiers |
| 14 | result = con.Query("SELECT printf('hello'), printf(NULL)" ); |
| 15 | REQUIRE(CHECK_COLUMN(result, 0, {"hello" })); |
| 16 | REQUIRE(CHECK_COLUMN(result, 1, {Value()})); |
| 17 | |
| 18 | // format strings |
| 19 | result = con.Query("SELECT printf('%s', 'hello'), printf('%s: %s', 'hello', 'world')" ); |
| 20 | REQUIRE(CHECK_COLUMN(result, 0, {"hello" })); |
| 21 | REQUIRE(CHECK_COLUMN(result, 1, {"hello: world" })); |
| 22 | |
| 23 | // format strings with NULL values |
| 24 | result = con.Query("SELECT printf('%s', NULL), printf(NULL, 'hello', 'world')" ); |
| 25 | REQUIRE(CHECK_COLUMN(result, 0, {Value()})); |
| 26 | REQUIRE(CHECK_COLUMN(result, 1, {Value()})); |
| 27 | |
| 28 | // booleans |
| 29 | result = con.Query("SELECT printf('%d', TRUE)" ); |
| 30 | REQUIRE(CHECK_COLUMN(result, 0, {"1" })); |
| 31 | |
| 32 | // integers |
| 33 | result = con.Query("SELECT printf('%d', 33), printf('%d + %d = %d', 3, 5, 3 + 5)" ); |
| 34 | REQUIRE(CHECK_COLUMN(result, 0, {"33" })); |
| 35 | REQUIRE(CHECK_COLUMN(result, 1, {"3 + 5 = 8" })); |
| 36 | |
| 37 | // integers with special formatting specifiers |
| 38 | result = con.Query( |
| 39 | "SELECT printf('%04d', 33), printf('%s %02d:%02d:%02d %s', 'time', 12, 3, 16, 'AM'), printf('%10d', 1992)" ); |
| 40 | REQUIRE(CHECK_COLUMN(result, 0, {"0033" })); |
| 41 | REQUIRE(CHECK_COLUMN(result, 1, {"time 12:03:16 AM" })); |
| 42 | REQUIRE(CHECK_COLUMN(result, 2, {" 1992" })); |
| 43 | |
| 44 | // different integer types |
| 45 | result = con.Query("SELECT printf('%hhd %hd %d %lld', 33::TINYINT, 12::SMALLINT, 40::INTEGER, 80::BIGINT)" ); |
| 46 | REQUIRE(CHECK_COLUMN(result, 0, {"33 12 40 80" })); |
| 47 | // ...but really any of these can be used |
| 48 | result = con.Query("SELECT printf('%d %lld %hhd %hd', 33::TINYINT, 12::SMALLINT, 40::INTEGER, 80::BIGINT)" ); |
| 49 | REQUIRE(CHECK_COLUMN(result, 0, {"33 12 40 80" })); |
| 50 | |
| 51 | // octal hex etc |
| 52 | result = con.Query("SELECT printf('%d %x %o %#x %#o', 100, 100, 100, 100, 100)" ); |
| 53 | REQUIRE(CHECK_COLUMN(result, 0, {"100 64 144 0x64 0144" })); |
| 54 | |
| 55 | // ascii characters |
| 56 | result = con.Query("SELECT printf('%c', 65)" ); |
| 57 | REQUIRE(CHECK_COLUMN(result, 0, {"A" })); |
| 58 | |
| 59 | // width trick |
| 60 | result = con.Query("SELECT printf('%*d', 5, 10)" ); |
| 61 | REQUIRE(CHECK_COLUMN(result, 0, {" 10" })); |
| 62 | |
| 63 | // floating point numbers |
| 64 | result = con.Query("SELECT printf('%.2f', 10.0::FLOAT), printf('%.4f', 0.5)" ); |
| 65 | REQUIRE(CHECK_COLUMN(result, 0, {"10.00" })); |
| 66 | REQUIRE(CHECK_COLUMN(result, 1, {"0.5000" })); |
| 67 | |
| 68 | // weird float stuff |
| 69 | result = con.Query("SELECT printf('floats: %4.2f %+.0e %E', 3.1416, 3.1416, 3.1416)" ); |
| 70 | REQUIRE(CHECK_COLUMN(result, 0, {"floats: 3.14 +3e+00 3.141600E+00" })); |
| 71 | |
| 72 | // incorrect number of parameters |
| 73 | // too few parameters |
| 74 | REQUIRE_FAIL(con.Query("SELECT printf('%s')" )); |
| 75 | REQUIRE_FAIL(con.Query("SELECT printf('%s %s', 'hello')" )); |
| 76 | // excess parameters are ignored |
| 77 | result = con.Query("SELECT printf('%s', 'hello', 'world')" ); |
| 78 | REQUIRE(CHECK_COLUMN(result, 0, {"hello" })); |
| 79 | // incorrect types |
| 80 | REQUIRE_FAIL(con.Query("SELECT printf('%s', 42)" )); |
| 81 | REQUIRE_FAIL(con.Query("SELECT printf('%d', 'hello')" )); |
| 82 | } |
| 83 | |
| 84 | TEST_CASE("Test printf with vectors" , "[printf]" ) { |
| 85 | unique_ptr<QueryResult> result; |
| 86 | DuckDB db(nullptr); |
| 87 | Connection con(db); |
| 88 | con.EnableQueryVerification(); |
| 89 | |
| 90 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE strings(idx INTEGER, fmt STRING, pint INTEGER, pstring STRING)" )); |
| 91 | REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES (1, '%d: %s', 10, 'hello')" )); |
| 92 | REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES (2, 'blabla %d blabla %s', 20, 'blabla')" )); |
| 93 | REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES (3, NULL, 30, 'abcde')" )); |
| 94 | |
| 95 | // printf without format specifiers: too few parameters |
| 96 | REQUIRE_FAIL(con.Query("SELECT printf(fmt) FROM strings ORDER BY idx" )); |
| 97 | |
| 98 | result = con.Query("SELECT printf(CASE WHEN pint < 15 THEN NULL ELSE pint END) FROM strings ORDER BY idx" ); |
| 99 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), "20" , "30" })); |
| 100 | |
| 101 | // standard vectorized printf |
| 102 | result = con.Query("SELECT printf(fmt, pint, pstring) FROM strings ORDER BY idx" ); |
| 103 | REQUIRE(CHECK_COLUMN(result, 0, {"10: hello" , "blabla 20 blabla blabla" , Value()})); |
| 104 | |
| 105 | // printf with constants in format arguments |
| 106 | result = con.Query("SELECT printf(fmt, 10, pstring) FROM strings ORDER BY idx" ); |
| 107 | REQUIRE(CHECK_COLUMN(result, 0, {"10: hello" , "blabla 10 blabla blabla" , Value()})); |
| 108 | |
| 109 | // printf with constant format string |
| 110 | result = con.Query("SELECT printf('%s: %s', pstring, pstring) FROM strings ORDER BY idx" ); |
| 111 | REQUIRE(CHECK_COLUMN(result, 0, {"hello: hello" , "blabla: blabla" , "abcde: abcde" })); |
| 112 | |
| 113 | // printf with selection vector |
| 114 | result = con.Query("SELECT printf('%s: %s', pstring, pstring) FROM strings WHERE idx <> 2 ORDER BY idx" ); |
| 115 | REQUIRE(CHECK_COLUMN(result, 0, {"hello: hello" , "abcde: abcde" })); |
| 116 | } |
| 117 | |
| 118 | TEST_CASE("Test scalar format" , "[printf]" ) { |
| 119 | unique_ptr<QueryResult> result; |
| 120 | DuckDB db(nullptr); |
| 121 | Connection con(db); |
| 122 | con.EnableQueryVerification(); |
| 123 | |
| 124 | // format without format specifiers |
| 125 | result = con.Query("SELECT format('hello'), format(NULL)" ); |
| 126 | REQUIRE(CHECK_COLUMN(result, 0, {"hello" })); |
| 127 | REQUIRE(CHECK_COLUMN(result, 1, {Value()})); |
| 128 | |
| 129 | // format strings |
| 130 | result = con.Query("SELECT format('{}', 'hello'), format('{}: {}', 'hello', 'world')" ); |
| 131 | REQUIRE(CHECK_COLUMN(result, 0, {"hello" })); |
| 132 | REQUIRE(CHECK_COLUMN(result, 1, {"hello: world" })); |
| 133 | |
| 134 | // format strings with NULL values |
| 135 | result = con.Query("SELECT format('{}', NULL), format(NULL, 'hello', 'world')" ); |
| 136 | REQUIRE(CHECK_COLUMN(result, 0, {Value()})); |
| 137 | REQUIRE(CHECK_COLUMN(result, 1, {Value()})); |
| 138 | |
| 139 | // booleans |
| 140 | result = con.Query("SELECT format('{} {}', TRUE, FALSE)" ); |
| 141 | REQUIRE(CHECK_COLUMN(result, 0, {"true false" })); |
| 142 | |
| 143 | // integers |
| 144 | result = con.Query("SELECT format('{}', 33), format('{} + {} = {}', 3, 5, 3 + 5)" ); |
| 145 | REQUIRE(CHECK_COLUMN(result, 0, {"33" })); |
| 146 | REQUIRE(CHECK_COLUMN(result, 1, {"3 + 5 = 8" })); |
| 147 | |
| 148 | // integers with special formatting specifiers |
| 149 | result = con.Query("SELECT format('{:04d}', 33), format('{} {:02d}:{:02d}:{:02d} {}', 'time', 12, 3, 16, 'AM'), " |
| 150 | "format('{:10d}', 1992)" ); |
| 151 | REQUIRE(CHECK_COLUMN(result, 0, {"0033" })); |
| 152 | REQUIRE(CHECK_COLUMN(result, 1, {"time 12:03:16 AM" })); |
| 153 | REQUIRE(CHECK_COLUMN(result, 2, {" 1992" })); |
| 154 | |
| 155 | // numeric input of arguments |
| 156 | result = con.Query("SELECT format('{1} {1} {0} {0}', 1, 2)" ); |
| 157 | REQUIRE(CHECK_COLUMN(result, 0, {"2 2 1 1" })); |
| 158 | |
| 159 | // incorrect number of parameters |
| 160 | // too few parameters |
| 161 | REQUIRE_FAIL(con.Query("SELECT format('{}')" )); |
| 162 | REQUIRE_FAIL(con.Query("SELECT format('{} {}', 'hello')" )); |
| 163 | // excess parameters are ignored |
| 164 | result = con.Query("SELECT format('{}', 'hello', 'world')" ); |
| 165 | REQUIRE(CHECK_COLUMN(result, 0, {"hello" })); |
| 166 | // incorrect types |
| 167 | REQUIRE_FAIL(con.Query("SELECT format('{:s}', 42)" )); |
| 168 | REQUIRE_FAIL(con.Query("SELECT format('{:d}', 'hello')" )); |
| 169 | } |
| 170 | |