1 /**
2  * SPDX-License-Identifier: GPL-2.0-or-later
3  *
4  * This file is part of osm2pgsql (https://osm2pgsql.org/).
5  *
6  * Copyright (C) 2006-2021 by the osm2pgsql developer community.
7  * For a full list of authors see the git log.
8  */
9 
10 #include <catch.hpp>
11 
12 #include "common-import.hpp"
13 #include "common-options.hpp"
14 
15 static testing::db::import_t db;
16 
17 static char const *const conf_file = "test_output_flex_types.lua";
18 
19 TEST_CASE("type nil")
20 {
21     testing::opt_t const options = testing::opt_t().flex(conf_file);
22 
23     REQUIRE_NOTHROW(
24         db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=nil\n"));
25 
26     auto conn = db.db().connect();
27 
28     CHECK(1 == conn.get_count("nodes"));
29     CHECK(1 ==
30           conn.get_count(
31               "nodes",
32               "ttext IS NULL AND tbool IS NULL AND tint2 IS NULL AND "
33               "tint4 IS NULL AND tint8 IS NULL AND treal IS NULL AND "
34               "thstr IS NULL AND tjson IS NULL AND tdirn IS NULL AND "
35               "tsqlt IS NULL"));
36 }
37 
38 TEST_CASE("type boolean")
39 {
40     testing::opt_t const options = testing::opt_t().flex(conf_file);
41 
42     REQUIRE_NOTHROW(
43         db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=boolean\n"));
44 
45     auto conn = db.db().connect();
46 
47     CHECK(2 == conn.get_count("nodes"));
48     CHECK(1 == conn.get_count("nodes", "tbool = true AND tint2 = 1 AND "
49                                        "tint4 = 1 AND tint8 = 1 AND "
50                                        "tjson = 'true'::jsonb AND tdirn = 1"));
51     CHECK(1 == conn.get_count("nodes", "tbool = false AND tint2 = 0 AND "
52                                        "tint4 = 0 AND tint8 = 0 AND "
53                                        "tjson = 'false'::jsonb AND tdirn = 0"));
54 }
55 
56 TEST_CASE("type boolean in column where it doesn't belong")
57 {
58     testing::opt_t const options = testing::opt_t().flex(conf_file);
59 
60     REQUIRE_THROWS(db.run_import(
61         options, "n10 v1 dV x10.0 y10.0 Ttype=boolean-fail column=ttext\n"));
62     REQUIRE_THROWS(db.run_import(
63         options, "n10 v1 dV x10.0 y10.0 Ttype=boolean-fail column=treal\n"));
64     REQUIRE_THROWS(db.run_import(
65         options, "n10 v1 dV x10.0 y10.0 Ttype=boolean-fail column=thstr\n"));
66     REQUIRE_THROWS(db.run_import(
67         options, "n10 v1 dV x10.0 y10.0 Ttype=boolean-fail column=tsqlt\n"));
68 
69     auto conn = db.db().connect();
70 
71     CHECK(0 == conn.get_count("nodes"));
72 }
73 
74 TEST_CASE("type number")
75 {
76     testing::opt_t const options = testing::opt_t().flex(conf_file);
77 
78     REQUIRE_NOTHROW(
79         db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=number\n"));
80 
81     auto conn = db.db().connect();
82 
83     CHECK(19 == conn.get_count("nodes"));
84 
85     // clang-format off
86     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '-2147483649' AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 IS NULL       AND tint8 = -2147483649               AND tjson = '-2147483649'::jsonb AND tdirn = -1"));
87     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '-2147483648' AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 = -2147483648 AND tint8 = -2147483648               AND tjson = '-2147483648'::jsonb AND tdirn = -1"));
88     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '-2147483647' AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 = -2147483647 AND tint8 = -2147483647               AND tjson = '-2147483647'::jsonb AND tdirn = -1"));
89     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '-32769'      AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 = -32769      AND tint8 = -32769 AND treal = -32769 AND tjson =      '-32769'::jsonb AND tdirn = -1"));
90     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '-32768'      AND ttext = tsqlt AND tbool = true  AND tint2 = -32768 AND tint4 = -32768      AND tint8 = -32768 AND treal = -32768 AND tjson =      '-32768'::jsonb AND tdirn = -1"));
91     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '-32767'      AND ttext = tsqlt AND tbool = true  AND tint2 = -32767 AND tint4 = -32767      AND tint8 = -32767 AND treal = -32767 AND tjson =      '-32767'::jsonb AND tdirn = -1"));
92     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '-2'          AND ttext = tsqlt AND tbool = true  AND tint2 = -2     AND tint4 = -2          AND tint8 = -2     AND treal =   -2   AND tjson =          '-2'::jsonb AND tdirn = -1"));
93     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '-1'          AND ttext = tsqlt AND tbool = true  AND tint2 = -1     AND tint4 = -1          AND tint8 = -1     AND treal =   -1   AND tjson =          '-1'::jsonb AND tdirn = -1"));
94     CHECK(1 == conn.get_count("nodes", "           tsqlt          = '-0.5'        AND ttext = tsqlt AND tbool = true  AND tint2 =  0     AND tint4 =  0          AND tint8 =  0     AND treal = -0.5   AND tjson =        '-0.5'::jsonb AND tdirn = -1"));
95     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '0'           AND ttext = tsqlt AND tbool = false AND tint2 =  0     AND tint4 =  0          AND tint8 =  0     AND treal =    0   AND tjson =           '0'::jsonb AND tdirn = 0"));
96     CHECK(1 == conn.get_count("nodes", "           tsqlt          = '0.5'         AND ttext = tsqlt AND tbool = true  AND tint2 =  0     AND tint4 =  0          AND tint8 =  0     AND treal =  0.5   AND tjson =         '0.5'::jsonb AND tdirn = 1"));
97     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '1'           AND ttext = tsqlt AND tbool = true  AND tint2 =  1     AND tint4 =  1          AND tint8 =  1     AND treal =    1   AND tjson =           '1'::jsonb AND tdirn = 1"));
98     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '2'           AND ttext = tsqlt AND tbool = true  AND tint2 =  2     AND tint4 =  2          AND tint8 =  2     AND treal =    2   AND tjson =           '2'::jsonb AND tdirn = 1"));
99     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '32767'       AND ttext = tsqlt AND tbool = true  AND tint2 = 32767  AND tint4 = 32767       AND tint8 = 32767  AND treal = 32767  AND tjson =       '32767'::jsonb AND tdirn = 1"));
100     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '32768'       AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 = 32768       AND tint8 = 32768  AND treal = 32768  AND tjson =       '32768'::jsonb AND tdirn = 1"));
101     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '32769'       AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 = 32769       AND tint8 = 32769  AND treal = 32769  AND tjson =       '32769'::jsonb AND tdirn = 1"));
102     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '2147483647'  AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 = 2147483647  AND tint8 = 2147483647                AND tjson =  '2147483647'::jsonb AND tdirn = 1"));
103     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '2147483648'  AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 IS NULL       AND tint8 = 2147483648                AND tjson =  '2147483648'::jsonb AND tdirn = 1"));
104     CHECK(1 == conn.get_count("nodes", "split_part(tsqlt, '.', 1) = '2147483649'  AND ttext = tsqlt AND tbool = true  AND tint2 IS NULL  AND tint4 IS NULL       AND tint8 = 2147483649                AND tjson =  '2147483649'::jsonb AND tdirn = 1"));
105     // clang-format on
106 }
107 
108 TEST_CASE("type string (with bool)")
109 {
110     testing::opt_t const options = testing::opt_t().flex(conf_file);
111 
112     REQUIRE_NOTHROW(
113         db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=string-bool\n"));
114 
115     auto conn = db.db().connect();
116 
117     CHECK(9 == conn.get_count("nodes"));
118     CHECK(3 == conn.get_count("nodes", "tbool = true  AND ttext = 'istrue'"));
119     CHECK(3 == conn.get_count("nodes", "tbool = false AND ttext = 'isfalse'"));
120     CHECK(3 == conn.get_count("nodes", "tbool IS NULL AND ttext = 'isnull'"));
121 }
122 
123 TEST_CASE("type string (with direction)")
124 {
125     testing::opt_t const options = testing::opt_t().flex(conf_file);
126 
127     REQUIRE_NOTHROW(db.run_import(
128         options, "n10 v1 dV x10.0 y10.0 Ttype=string-direction\n"));
129 
130     auto conn = db.db().connect();
131 
132     CHECK(9 == conn.get_count("nodes"));
133     CHECK(5 == conn.get_count("nodes", "tdirn = tint2"));
134     CHECK(4 == conn.get_count("nodes", "tdirn IS NULL AND tint2 IS NULL"));
135 }
136 
137 TEST_CASE("type string (with number)")
138 {
139     testing::opt_t const options = testing::opt_t().flex(conf_file);
140 
141     REQUIRE_NOTHROW(db.run_import(
142         options, "n10 v1 dV x10.0 y10.0 Ttype=string-with-number\n"));
143 
144     auto conn = db.db().connect();
145 
146     CHECK(18 == conn.get_count("nodes"));
147 
148     // clang-format off
149     CHECK(1 == conn.get_count("nodes", "tsqlt = '-2147483649' AND ttext = tsqlt AND tint2 IS NULL  AND tint4 IS NULL       AND tint8 = -2147483649              "));
150     CHECK(1 == conn.get_count("nodes", "tsqlt = '-2147483648' AND ttext = tsqlt AND tint2 IS NULL  AND tint4 = -2147483648 AND tint8 = -2147483648              "));
151     CHECK(1 == conn.get_count("nodes", "tsqlt = '-2147483647' AND ttext = tsqlt AND tint2 IS NULL  AND tint4 = -2147483647 AND tint8 = -2147483647              "));
152     CHECK(1 == conn.get_count("nodes", "tsqlt = '-32769'      AND ttext = tsqlt AND tint2 IS NULL  AND tint4 = -32769      AND tint8 = -32769 AND treal = -32769"));
153     CHECK(1 == conn.get_count("nodes", "tsqlt = '-32768'      AND ttext = tsqlt AND tint2 = -32768 AND tint4 = -32768      AND tint8 = -32768 AND treal = -32768"));
154     CHECK(1 == conn.get_count("nodes", "tsqlt = '-32767'      AND ttext = tsqlt AND tint2 = -32767 AND tint4 = -32767      AND tint8 = -32767 AND treal = -32767"));
155     CHECK(1 == conn.get_count("nodes", "tsqlt = '-2'          AND ttext = tsqlt AND tint2 = -2     AND tint4 = -2          AND tint8 = -2     AND treal =   -2  "));
156     CHECK(1 == conn.get_count("nodes", "tsqlt = '-1'          AND ttext = tsqlt AND tint2 = -1     AND tint4 = -1          AND tint8 = -1     AND treal =   -1  "));
157     CHECK(1 == conn.get_count("nodes", "tsqlt = '0'           AND ttext = tsqlt AND tint2 =  0     AND tint4 =  0          AND tint8 =  0     AND treal =    0  "));
158     CHECK(1 == conn.get_count("nodes", "tsqlt = '1'           AND ttext = tsqlt AND tint2 =  1     AND tint4 =  1          AND tint8 =  1     AND treal =    1  "));
159     CHECK(1 == conn.get_count("nodes", "tsqlt = '2'           AND ttext = tsqlt AND tint2 =  2     AND tint4 =  2          AND tint8 =  2     AND treal =    2  "));
160     CHECK(1 == conn.get_count("nodes", "tsqlt = '32767'       AND ttext = tsqlt AND tint2 = 32767  AND tint4 = 32767       AND tint8 = 32767  AND treal = 32767 "));
161     CHECK(1 == conn.get_count("nodes", "tsqlt = '32768'       AND ttext = tsqlt AND tint2 IS NULL  AND tint4 = 32768       AND tint8 = 32768  AND treal = 32768 "));
162     CHECK(1 == conn.get_count("nodes", "tsqlt = '32769'       AND ttext = tsqlt AND tint2 IS NULL  AND tint4 = 32769       AND tint8 = 32769  AND treal = 32769 "));
163     CHECK(1 == conn.get_count("nodes", "tsqlt = '2147483647'  AND ttext = tsqlt AND tint2 IS NULL  AND tint4 = 2147483647  AND tint8 = 2147483647               "));
164     CHECK(1 == conn.get_count("nodes", "tsqlt = '2147483648'  AND ttext = tsqlt AND tint2 IS NULL  AND tint4 IS NULL       AND tint8 = 2147483648               "));
165     CHECK(1 == conn.get_count("nodes", "tsqlt = '2147483649'  AND ttext = tsqlt AND tint2 IS NULL  AND tint4 IS NULL       AND tint8 = 2147483649               "));
166     CHECK(1 == conn.get_count("nodes", "tsqlt = ' 42'         AND ttext = tsqlt AND tint2 = 42     AND tint4 = 42          AND tint8 = 42     AND treal =   42  "));
167     // clang-format on
168 }
169 
170 TEST_CASE("type string (with invalid number)")
171 {
172     testing::opt_t const options = testing::opt_t().flex(conf_file);
173 
174     REQUIRE_NOTHROW(db.run_import(
175         options, "n10 v1 dV x10.0 y10.0 Ttype=string-with-invalid-number\n"));
176 
177     auto conn = db.db().connect();
178 
179     CHECK(7 == conn.get_count("nodes"));
180 
181     // clang-format off
182     CHECK(1 == conn.get_count("nodes", "ttext = ''     AND tint2 IS NULL AND tint4 IS NULL AND tint8 IS NULL AND treal IS NULL"));
183     CHECK(1 == conn.get_count("nodes", "ttext = 'abc'  AND tint2 IS NULL AND tint4 IS NULL AND tint8 IS NULL AND treal IS NULL"));
184     CHECK(1 == conn.get_count("nodes", "ttext = '0a'   AND tint2 IS NULL AND tint4 IS NULL AND tint8 IS NULL AND treal IS NULL"));
185     CHECK(1 == conn.get_count("nodes", "ttext = '0xa'  AND tint2 IS NULL AND tint4 IS NULL AND tint8 IS NULL AND abs(treal - 10) < 0.0000001"));
186     CHECK(1 == conn.get_count("nodes", "ttext = '--1'  AND tint2 IS NULL AND tint4 IS NULL AND tint8 IS NULL AND treal IS NULL"));
187     CHECK(1 == conn.get_count("nodes", "ttext = '1foo' AND tint2 IS NULL AND tint4 IS NULL AND tint8 IS NULL AND treal IS NULL"));
188     CHECK(1 == conn.get_count("nodes", "ttext = '1.2'  AND tint2 IS NULL AND tint4 IS NULL AND tint8 IS NULL AND abs(treal - 1.2) < 0.0000001"));
189     // clang-format on
190 }
191 
192 TEST_CASE("type number in column where it doesn't belong")
193 {
194     testing::opt_t const options = testing::opt_t().flex(conf_file);
195 
196     REQUIRE_THROWS(db.run_import(
197         options, "n10 v1 dV x10.0 y10.0 Ttype=number-fail column=thstr\n"));
198 
199     auto conn = db.db().connect();
200 
201     CHECK(0 == conn.get_count("nodes"));
202 }
203 
204 TEST_CASE("Adding a function should always fail")
205 {
206     testing::opt_t const options = testing::opt_t().flex(conf_file);
207 
208     std::string const types[] = {"ttext", "tbool", "tint2", "tint4", "tint8",
209                                  "treal", "thstr", "tdirn", "tsqlt"};
210 
211     for (auto const &type : types) {
212         auto const line =
213             "n10 v1 dV x10.0 y10.0 Ttype=function-fail column=" + type + "\n";
214         REQUIRE_THROWS(db.run_import(options, line.c_str()));
215     }
216 
217     auto conn = db.db().connect();
218 
219     CHECK(0 == conn.get_count("nodes"));
220 }
221 
222 TEST_CASE("type table")
223 {
224     testing::opt_t const options = testing::opt_t().flex(conf_file);
225 
226     REQUIRE_NOTHROW(
227         db.run_import(options, "n10 v1 dV x10.0 y10.0 Ttype=table\n"));
228 
229     auto conn = db.db().connect();
230 
231     CHECK(2 == conn.get_count("nodes"));
232 
233     CHECK(1 == conn.get_count("nodes", "thstr = '' AND tjson = '{}'::jsonb"));
234     CHECK(1 == conn.get_count("nodes",
235                               "thstr = 'a=>b,c=>d' AND "
236                               "tjson = '{\"a\": \"b\", \"c\": \"d\"}'::jsonb"));
237 }
238 
239 TEST_CASE("Adding a table with non-strings should fail for hstore")
240 {
241     testing::opt_t const options = testing::opt_t().flex(conf_file);
242 
243     char const *const line = "n10 v1 dV x10.0 y10.0 Ttype=table-hstore-fail\n";
244     REQUIRE_THROWS(db.run_import(options, line));
245 
246     auto conn = db.db().connect();
247 
248     CHECK(0 == conn.get_count("nodes"));
249 }
250 
251 TEST_CASE("Adding a table should fail except for hstore and json/jsonb")
252 {
253     testing::opt_t const options = testing::opt_t().flex(conf_file);
254 
255     std::string const types[] = {"ttext", "tbool", "tint2", "tint4",
256                                  "tint8", "treal", "tdirn", "tsqlt"};
257 
258     for (auto const &type : types) {
259         auto const line =
260             "n10 v1 dV x10.0 y10.0 Ttype=table-fail column=" + type + "\n";
261         REQUIRE_THROWS(db.run_import(options, line.c_str()));
262     }
263 
264     auto conn = db.db().connect();
265 
266     CHECK(0 == conn.get_count("nodes"));
267 }
268 
269 TEST_CASE("Adding a complex table in jsonb")
270 {
271     testing::opt_t const options = testing::opt_t().flex(conf_file);
272 
273     char const *const line = "n10 v1 dV x10.0 y10.0 Ttype=json\n";
274     REQUIRE_NOTHROW(db.run_import(options, line));
275 
276     auto conn = db.db().connect();
277 
278     CHECK(1 ==
279           conn.get_count(
280               "nodes",
281               "tjson = '{"
282               "\"astring\": \"123\", \"aninteger\": 124, \"anumber\": 12.5, "
283               "\"atrue\": true, \"afalse\": false, "
284               "\"atable\": {\"a\": \"nested\", \"tab\": \"le\"}, "
285               "\"anarray\": [4, 3, 7]"
286               "}'::jsonb"));
287 }
288 
289 TEST_CASE("Adding a table with a loop should fail")
290 {
291     testing::opt_t const options = testing::opt_t().flex(conf_file);
292 
293     char const *const line = "n10 v1 dV x10.0 y10.0 Ttype=json-loop\n";
294     REQUIRE_THROWS(db.run_import(options, line));
295 
296     auto conn = db.db().connect();
297 
298     CHECK(0 == conn.get_count("nodes"));
299 }
300