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 <algorithm>
13 
14 #include <osmium/osm/crc.hpp>
15 #include <osmium/osm/crc_zlib.hpp>
16 
17 #include "dependency-manager.hpp"
18 #include "middle-pgsql.hpp"
19 #include "middle-ram.hpp"
20 
21 #include "common-buffer.hpp"
22 #include "common-cleanup.hpp"
23 #include "common-options.hpp"
24 #include "common-pg.hpp"
25 
26 static testing::pg::tempdb_t db;
27 
28 namespace {
29 
expect_location(osmium::Location loc,osmium::Node const & expected)30 void expect_location(osmium::Location loc, osmium::Node const &expected)
31 {
32     CHECK(loc.lat() == Approx(expected.location().lat()));
33     CHECK(loc.lon() == Approx(expected.location().lon()));
34 }
35 
36 } // namespace
37 
38 struct options_slim_default
39 {
optionsoptions_slim_default40     static options_t options(testing::pg::tempdb_t const &tmpdb)
41     {
42         return testing::opt_t().slim(tmpdb);
43     }
44 };
45 
46 struct options_slim_with_lc_prefix
47 {
optionsoptions_slim_with_lc_prefix48     static options_t options(testing::pg::tempdb_t const &tmpdb)
49     {
50         options_t o = testing::opt_t().slim(tmpdb);
51         o.prefix = "pre";
52         return o;
53     }
54 };
55 
56 struct options_slim_with_uc_prefix
57 {
optionsoptions_slim_with_uc_prefix58     static options_t options(testing::pg::tempdb_t const &tmpdb)
59     {
60         options_t o = testing::opt_t().slim(tmpdb);
61         o.prefix = "PRE";
62         return o;
63     }
64 };
65 
66 struct options_slim_with_schema
67 {
optionsoptions_slim_with_schema68     static options_t options(testing::pg::tempdb_t const &tmpdb)
69     {
70         options_t o = testing::opt_t().slim(tmpdb);
71         o.middle_dbschema = "osm";
72         return o;
73     }
74 };
75 
76 struct options_flat_node_cache
77 {
optionsoptions_flat_node_cache78     static options_t options(testing::pg::tempdb_t const &tmpdb)
79     {
80         return testing::opt_t().slim(tmpdb).flatnodes();
81     }
82 };
83 
84 struct options_ram_optimized
85 {
optionsoptions_ram_optimized86     static options_t options(testing::pg::tempdb_t const &)
87     {
88         return testing::opt_t();
89     }
90 };
91 
92 TEMPLATE_TEST_CASE("middle import", "", options_slim_default,
93                    options_slim_with_lc_prefix, options_slim_with_uc_prefix,
94                    options_slim_with_schema, options_ram_optimized)
95 {
96     options_t const options = TestType::options(db);
97     testing::cleanup::file_t flatnode_cleaner{options.flat_node_file};
98 
99     auto conn = db.connect();
100     auto const num_tables =
101         conn.get_count("pg_tables", "schemaname = 'public'");
102     auto const num_indexes =
103         conn.get_count("pg_indexes", "schemaname = 'public'");
104     auto const num_procs =
105         conn.get_count("pg_proc", "pronamespace = (SELECT oid FROM "
106                                   "pg_namespace WHERE nspname = 'public')");
107 
108     if (!options.middle_dbschema.empty()) {
109         conn.exec("CREATE SCHEMA IF NOT EXISTS osm;");
110     }
111 
112     auto thread_pool = std::make_shared<thread_pool_t>(1U);
113     auto mid = create_middle(thread_pool, options);
114     mid->start();
115 
116     output_requirements requirements;
117     requirements.full_ways = true;
118     requirements.full_relations = true;
119     mid->set_requirements(requirements);
120 
121     auto const mid_q = mid->get_query_instance();
122 
123     test_buffer_t buffer;
124 
125     SECTION("Set and retrieve a single node")
126     {
127         auto const &node = buffer.add_node("n1234 x98.7654321 y12.3456789");
128 
129         // set the node
130         mid->node(node);
131         mid->after_nodes();
132 
133         // getting it back works only via a waylist
134         auto &nodes = buffer.add_way("w3 Nn1234").nodes();
135 
136         // get it back
137         REQUIRE(mid_q->nodes_get_list(&nodes) == nodes.size());
138         expect_location(nodes[0].location(), node);
139 
140         // other nodes are not retrievable
141         auto &n2 = buffer.add_way("w3 Nn1,n2,n1235").nodes();
142         REQUIRE(mid_q->nodes_get_list(&n2) == 0);
143     }
144 
145     SECTION("Set and retrieve a single way")
146     {
147         osmid_t const way_id = 1;
148         double const lon = 98.7654321;
149         double const lat = 12.3456789;
150         idlist_t nds;
151 
152         // set nodes
153         for (osmid_t i = 1; i <= 10; ++i) {
154             nds.push_back(i);
155             auto const &node = buffer.add_node(
156                 "n{} x{} y{}"_format(i, lon - i * 0.003, lat + i * 0.001));
157             mid->node(node);
158         }
159         mid->after_nodes();
160 
161         // set the way
162         mid->way(buffer.add_way(way_id, nds));
163         mid->after_ways();
164 
165         // get it back
166         osmium::memory::Buffer outbuf{4096,
167                                       osmium::memory::Buffer::auto_grow::yes};
168 
169         REQUIRE(mid_q->way_get(way_id, &outbuf));
170 
171         auto &way = outbuf.get<osmium::Way>(0);
172 
173         CHECK(way.id() == way_id);
174         REQUIRE(way.nodes().size() == nds.size());
175 
176         REQUIRE(mid_q->nodes_get_list(&(way.nodes())) == nds.size());
177         for (osmid_t i = 1; i <= 10; ++i) {
178             CHECK(way.nodes()[(size_t)i - 1].ref() == i);
179         }
180 
181         // other ways are not retrievable
182         REQUIRE_FALSE(mid_q->way_get(way_id + 1, &outbuf));
183     }
184 
185     SECTION("Set and retrieve a single relation with supporting ways")
186     {
187         idlist_t const nds[] = {{4, 5, 13, 14, 342}, {45, 90}, {30, 3, 45}};
188 
189         // set the node
190         mid->node(buffer.add_node("n1 x4.1 y12.8"));
191         mid->after_nodes();
192 
193         // set the ways
194         osmid_t wid = 10;
195         for (auto const &n : nds) {
196             mid->way(buffer.add_way(wid, n));
197             ++wid;
198         }
199         mid->after_ways();
200 
201         // set the relation
202         auto const &relation =
203             buffer.add_relation("r123 Mw11@,w10@outer,n1@,w12@inner");
204         osmium::CRC<osmium::CRC_zlib> orig_crc;
205         orig_crc.update(relation);
206 
207         mid->relation(relation);
208         mid->after_relations();
209 
210         // retrieve the relation
211         osmium::memory::Buffer outbuf{4096,
212                                       osmium::memory::Buffer::auto_grow::yes};
213         REQUIRE(mid_q->relation_get(123, &outbuf));
214         auto const &rel = outbuf.get<osmium::Relation>(0);
215 
216         CHECK(rel.id() == 123);
217         CHECK(rel.members().size() == 4);
218 
219         osmium::CRC<osmium::CRC_zlib> crc;
220         crc.update(rel);
221         CHECK(orig_crc().checksum() == crc().checksum());
222 
223         // retrieve the supporting ways
224         REQUIRE(mid_q->rel_members_get(rel, &outbuf,
225                                        osmium::osm_entity_bits::way) == 3);
226 
227         for (auto &w : outbuf.select<osmium::Way>()) {
228             REQUIRE(w.id() >= 10);
229             REQUIRE(w.id() <= 12);
230             auto const &expected = nds[w.id() - 10];
231             REQUIRE(w.nodes().size() == expected.size());
232             for (size_t i = 0; i < expected.size(); ++i) {
233                 REQUIRE(w.nodes()[i].ref() == expected[i]);
234             }
235         }
236 
237         // other relations are not retrievable
238         REQUIRE_FALSE(mid_q->relation_get(999, &outbuf));
239     }
240 
241     if (!options.middle_dbschema.empty()) {
242         REQUIRE(num_tables ==
243                 conn.get_count("pg_tables", "schemaname = 'public'"));
244         REQUIRE(num_indexes ==
245                 conn.get_count("pg_indexes", "schemaname = 'public'"));
246         REQUIRE(num_procs ==
247                 conn.get_count("pg_proc",
248                                "pronamespace = (SELECT oid FROM "
249                                "pg_namespace WHERE nspname = 'public')"));
250     }
251 }
252 
253 /**
254  * Check that the node is in the mid with the right id and location.
255  */
check_node(std::shared_ptr<middle_pgsql_t> const & mid,osmium::Node const & node)256 static void check_node(std::shared_ptr<middle_pgsql_t> const &mid,
257                        osmium::Node const &node)
258 {
259     test_buffer_t buffer;
260     auto &nodes = buffer.add_way(999, {node.id()}).nodes();
261     auto const mid_q = mid->get_query_instance();
262     REQUIRE(mid_q->nodes_get_list(&nodes) == 1);
263     REQUIRE(nodes[0].ref() == node.id());
264     REQUIRE(nodes[0].location() == node.location());
265 }
266 
267 /// Return true if the node with the specified id is not in the mid.
no_node(std::shared_ptr<middle_pgsql_t> const & mid,osmid_t id)268 static bool no_node(std::shared_ptr<middle_pgsql_t> const &mid, osmid_t id)
269 {
270     test_buffer_t buffer;
271     auto &nodes = buffer.add_way(999, {id}).nodes();
272     auto const mid_q = mid->get_query_instance();
273     return mid_q->nodes_get_list(&nodes) == 0;
274 }
275 
276 TEMPLATE_TEST_CASE("middle: add, delete and update node", "",
277                    options_slim_default, options_flat_node_cache)
278 {
279     auto thread_pool = std::make_shared<thread_pool_t>(1U);
280 
281     options_t options = TestType::options(db);
282 
283     testing::cleanup::file_t flatnode_cleaner{options.flat_node_file};
284 
285     // Prepare a buffer with some nodes which we will add and change.
286     test_buffer_t buffer;
287     auto const &node10 = buffer.add_node("n10 x1.0 y0.0");
288     auto const &node11 = buffer.add_node("n11 x1.1 y0.0");
289     auto const &node12 = buffer.add_node("n12 x1.2 y0.0");
290 
291     auto const &node10a = buffer.add_node("n10 x1.0 y1.0");
292 
293     auto const &node5d = buffer.add_node("n5 dD");
294     auto const &node10d = buffer.add_node("n10 dD");
295     auto const &node12d = buffer.add_node("n12 dD");
296     auto const &node42d = buffer.add_node("n42 dD");
297 
298     // Set up middle in "create" mode to get a cleanly initialized database
299     // and add some nodes. Does this in its own scope so that the mid is
300     // closed properly.
301     {
302         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
303 
304         mid->start();
305 
306         mid->node(node10);
307         mid->node(node11);
308         mid->after_nodes();
309         mid->after_relations();
310 
311         check_node(mid, node10);
312         check_node(mid, node11);
313     }
314 
315     // From now on use append mode to not destroy the data we just added.
316     options.append = true;
317 
318     SECTION("Added nodes are there and no others")
319     {
320         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
321         mid->start();
322 
323         check_node(mid, node10);
324         check_node(mid, node11);
325         REQUIRE(no_node(mid, 5));
326         REQUIRE(no_node(mid, 42));
327     }
328 
329     SECTION("Delete existing and non-existing node")
330     {
331         {
332             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
333             mid->start();
334 
335             mid->node(node5d);
336             mid->node(node10d);
337             mid->node(node42d);
338             mid->after_nodes();
339             mid->after_relations();
340 
341             REQUIRE(no_node(mid, 5));
342             REQUIRE(no_node(mid, 10));
343             check_node(mid, node11);
344             REQUIRE(no_node(mid, 42));
345         }
346         {
347             // Check with a new mid.
348             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
349             mid->start();
350 
351             REQUIRE(no_node(mid, 5));
352             REQUIRE(no_node(mid, 10));
353             check_node(mid, node11);
354             REQUIRE(no_node(mid, 42));
355         }
356     }
357 
358     SECTION("Change (delete and set) existing and non-existing node")
359     {
360         {
361             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
362             mid->start();
363 
364             mid->node(node10d);
365             mid->node(node10a);
366             mid->node(node12d);
367             mid->node(node12);
368             mid->after_nodes();
369             mid->after_relations();
370 
371             check_node(mid, node10a);
372             check_node(mid, node11);
373             check_node(mid, node12);
374         }
375         {
376             // Check with a new mid.
377             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
378             mid->start();
379 
380             check_node(mid, node10a);
381             check_node(mid, node11);
382             check_node(mid, node12);
383         }
384     }
385 
386     SECTION("Add new node")
387     {
388         {
389             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
390             mid->start();
391 
392             mid->node(node12);
393             mid->after_nodes();
394             mid->after_relations();
395 
396             REQUIRE(no_node(mid, 5));
397             check_node(mid, node10);
398             check_node(mid, node11);
399             check_node(mid, node12);
400             REQUIRE(no_node(mid, 42));
401         }
402         {
403             // Check with a new mid.
404             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
405             mid->start();
406 
407             REQUIRE(no_node(mid, 5));
408             check_node(mid, node10);
409             check_node(mid, node11);
410             check_node(mid, node12);
411             REQUIRE(no_node(mid, 42));
412         }
413     }
414 }
415 
416 /**
417  * Check that the way is in the mid with the right attributes and tags.
418  * Does not check node locations.
419  */
check_way(std::shared_ptr<middle_pgsql_t> const & mid,osmium::Way const & orig_way)420 static void check_way(std::shared_ptr<middle_pgsql_t> const &mid,
421                       osmium::Way const &orig_way)
422 {
423     auto const mid_q = mid->get_query_instance();
424 
425     osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes};
426     REQUIRE(mid_q->way_get(orig_way.id(), &outbuf));
427     auto const &way = outbuf.get<osmium::Way>(0);
428 
429     osmium::CRC<osmium::CRC_zlib> orig_crc;
430     orig_crc.update(orig_way);
431 
432     osmium::CRC<osmium::CRC_zlib> test_crc;
433     test_crc.update(way);
434 
435     REQUIRE(orig_crc().checksum() == test_crc().checksum());
436 }
437 
438 /**
439  * Check that the nodes (ids and locations) of the way with the way_id in the
440  * mid are identical to the nodes in the nodes vector.
441  */
check_way_nodes(std::shared_ptr<middle_pgsql_t> const & mid,osmid_t way_id,std::vector<osmium::Node const * > const & nodes)442 static void check_way_nodes(std::shared_ptr<middle_pgsql_t> const &mid,
443                             osmid_t way_id,
444                             std::vector<osmium::Node const *> const &nodes)
445 {
446     auto const mid_q = mid->get_query_instance();
447 
448     osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes};
449     REQUIRE(mid_q->way_get(way_id, &outbuf));
450     auto &way = outbuf.get<osmium::Way>(0);
451 
452     REQUIRE(mid_q->nodes_get_list(&way.nodes()) == way.nodes().size());
453     REQUIRE(way.nodes().size() == nodes.size());
454 
455     REQUIRE(std::equal(way.nodes().cbegin(), way.nodes().cend(), nodes.cbegin(),
456                        [](osmium::NodeRef const &nr, osmium::Node const *node) {
457                            return nr.ref() == node->id() &&
458                                   nr.location() == node->location();
459                        }));
460 }
461 
462 /// Return true if the way with the specified id is not in the mid.
no_way(std::shared_ptr<middle_pgsql_t> const & mid,osmid_t id)463 static bool no_way(std::shared_ptr<middle_pgsql_t> const &mid, osmid_t id)
464 {
465     auto const mid_q = mid->get_query_instance();
466     osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes};
467     return !mid_q->way_get(id, &outbuf);
468 }
469 
470 TEMPLATE_TEST_CASE("middle: add, delete and update way", "",
471                    options_slim_default, options_flat_node_cache)
472 {
473     auto thread_pool = std::make_shared<thread_pool_t>(1U);
474 
475     options_t options = TestType::options(db);
476 
477     testing::cleanup::file_t flatnode_cleaner{options.flat_node_file};
478 
479     // Create some ways we'll use for the tests.
480     test_buffer_t buffer;
481     auto const &way20 =
482         buffer.add_way("w20 Nn10,n11 Thighway=residential,name=High_Street");
483 
484     auto const &way21 = buffer.add_way("w21 Nn11,n12");
485 
486     auto const &way22 = buffer.add_way("w22 Nn12,n10 Tpower=line");
487 
488     auto const &way20a =
489         buffer.add_way("w20 Nn10,n12 Thighway=primary,name=High_Street");
490 
491     auto const &way5d = buffer.add_way("w5 dD");
492     auto const &way20d = buffer.add_way("w20 dD");
493     auto const &way22d = buffer.add_way("w22 dD");
494     auto const &way42d = buffer.add_way("w42 dD");
495 
496     // Set up middle in "create" mode to get a cleanly initialized database and
497     // add some ways. Does this in its own scope so that the mid is closed
498     // properly.
499     {
500         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
501         mid->start();
502 
503         mid->way(way20);
504         mid->way(way21);
505         mid->after_ways();
506         mid->after_relations();
507 
508         check_way(mid, way20);
509         check_way(mid, way21);
510     }
511 
512     // From now on use append mode to not destroy the data we just added.
513     options.append = true;
514 
515     SECTION("Added ways are there and no others")
516     {
517         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
518         mid->start();
519 
520         REQUIRE(no_way(mid, 5));
521         check_way(mid, way20);
522         check_way(mid, way21);
523         REQUIRE(no_way(mid, 22));
524     }
525 
526     SECTION("Delete existing and non-existing way")
527     {
528         {
529             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
530             mid->start();
531 
532             mid->way(way5d);
533             mid->way(way20d);
534             mid->way(way42d);
535             mid->after_ways();
536             mid->after_relations();
537 
538             REQUIRE(no_way(mid, 5));
539             REQUIRE(no_way(mid, 20));
540             check_way(mid, way21);
541             REQUIRE(no_way(mid, 42));
542         }
543         {
544             // Check with a new mid.
545             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
546             mid->start();
547 
548             REQUIRE(no_way(mid, 5));
549             REQUIRE(no_way(mid, 20));
550             check_way(mid, way21);
551             REQUIRE(no_way(mid, 42));
552         }
553     }
554 
555     SECTION("Change (delete and set) existing and non-existing way")
556     {
557         {
558             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
559             mid->start();
560 
561             mid->way(way20d);
562             mid->way(way20a);
563             mid->way(way22d);
564             mid->way(way22);
565             mid->after_ways();
566             mid->after_relations();
567 
568             REQUIRE(no_way(mid, 5));
569             check_way(mid, way20a);
570             check_way(mid, way21);
571             check_way(mid, way22);
572             REQUIRE(no_way(mid, 42));
573         }
574         {
575             // Check with a new mid.
576             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
577             mid->start();
578 
579             REQUIRE(no_way(mid, 5));
580             check_way(mid, way20a);
581             check_way(mid, way21);
582             check_way(mid, way22);
583             REQUIRE(no_way(mid, 42));
584         }
585     }
586 
587     SECTION("Add new way")
588     {
589         {
590             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
591             mid->start();
592 
593             mid->way(way22);
594             mid->after_ways();
595             mid->after_relations();
596 
597             REQUIRE(no_way(mid, 5));
598             check_way(mid, way20);
599             check_way(mid, way21);
600             check_way(mid, way22);
601             REQUIRE(no_way(mid, 42));
602         }
603         {
604             // Check with a new mid.
605             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
606             mid->start();
607 
608             REQUIRE(no_way(mid, 5));
609             check_way(mid, way20);
610             check_way(mid, way21);
611             check_way(mid, way22);
612             REQUIRE(no_way(mid, 42));
613         }
614     }
615 }
616 
617 TEMPLATE_TEST_CASE("middle: add way with attributes", "", options_slim_default,
618                    options_flat_node_cache)
619 {
620     auto thread_pool = std::make_shared<thread_pool_t>(1U);
621 
622     options_t options = TestType::options(db);
623 
624     SECTION("With attributes") { options.extra_attributes = true; }
625     SECTION("No attributes") { options.extra_attributes = false; }
626 
627     testing::cleanup::file_t flatnode_cleaner{options.flat_node_file};
628 
629     // Create some ways we'll use for the tests.
630     test_buffer_t buffer;
631     auto &way20 =
632         buffer.add_way("w20 Nn10,n11 Thighway=residential,name=High_Street");
633     way20.set_version(123);
634     way20.set_timestamp(1234567890);
635     way20.set_changeset(456);
636     way20.set_uid(789);
637 
638     // The same way but with default attributes.
639     auto &way20_no_attr =
640         buffer.add_way("w20 Nn10,n11 Thighway=residential,name=High_Street");
641 
642     // The same way but with attributes in tags.
643     // The order of the tags is important here!
644     auto &way20_attr_tags = buffer.add_way(
645         "w20 Nn10,n11 "
646         "Thighway=residential,name=High_Street,osm_user=,osm_uid=789,"
647         "osm_version=123,osm_timestamp=2009-02-13T23:31:30Z,osm_changeset=456");
648 
649     {
650         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
651         mid->start();
652 
653         mid->way(way20);
654         mid->after_ways();
655         mid->after_relations();
656 
657         check_way(mid,
658                   options.extra_attributes ? way20_attr_tags : way20_no_attr);
659     }
660 
661     // From now on use append mode to not destroy the data we just added.
662     options.append = true;
663 
664     {
665         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
666         mid->start();
667 
668         check_way(mid,
669                   options.extra_attributes ? way20_attr_tags : way20_no_attr);
670     }
671 }
672 
673 /**
674  * Check that the relation is in the mid with the right attributes, members
675  * and tags. Only checks the relation, does not recurse into members.
676  */
check_relation(std::shared_ptr<middle_pgsql_t> const & mid,osmium::Relation const & orig_relation)677 static void check_relation(std::shared_ptr<middle_pgsql_t> const &mid,
678                            osmium::Relation const &orig_relation)
679 {
680     auto const mid_q = mid->get_query_instance();
681 
682     osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes};
683     REQUIRE(mid_q->relation_get(orig_relation.id(), &outbuf));
684     auto const &relation = outbuf.get<osmium::Relation>(0);
685 
686     osmium::CRC<osmium::CRC_zlib> orig_crc;
687     orig_crc.update(orig_relation);
688 
689     osmium::CRC<osmium::CRC_zlib> test_crc;
690     test_crc.update(relation);
691 
692     REQUIRE(orig_crc().checksum() == test_crc().checksum());
693 }
694 
695 /// Return true if the relation with the specified id is not in the mid.
no_relation(std::shared_ptr<middle_pgsql_t> const & mid,osmid_t id)696 static bool no_relation(std::shared_ptr<middle_pgsql_t> const &mid, osmid_t id)
697 {
698     auto const mid_q = mid->get_query_instance();
699     osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes};
700     return !mid_q->relation_get(id, &outbuf);
701 }
702 
703 TEMPLATE_TEST_CASE("middle: add, delete and update relation", "",
704                    options_slim_default, options_flat_node_cache)
705 {
706     auto thread_pool = std::make_shared<thread_pool_t>(1U);
707 
708     options_t options = TestType::options(db);
709 
710     testing::cleanup::file_t flatnode_cleaner{options.flat_node_file};
711 
712     // Create some relations we'll use for the tests.
713     test_buffer_t buffer;
714     auto const &relation30 = buffer.add_relation(
715         "r30 Mw10@outer,w11@innder Ttype=multipolygon,name=Penguin_Park");
716 
717     auto const &relation31 = buffer.add_relation("r31 Mn10@");
718 
719     auto const &relation32 = buffer.add_relation("r32 Mr39@ Ttype=site");
720 
721     auto const &relation30a = buffer.add_relation(
722         "r30 Mw10@outer,w11@outer Ttype=multipolygon,name=Pigeon_Park");
723 
724     auto const &relation5d = buffer.add_relation("r5 dD");
725     auto const &relation30d = buffer.add_relation("r30 dD");
726     auto const &relation32d = buffer.add_relation("r32 dD");
727     auto const &relation42d = buffer.add_relation("r42 dD");
728 
729     // Set up middle in "create" mode to get a cleanly initialized database and
730     // add some relations. Does this in its own scope so that the mid is closed
731     // properly.
732     {
733         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
734         mid->start();
735 
736         mid->relation(relation30);
737         mid->relation(relation31);
738         mid->after_relations();
739 
740         check_relation(mid, relation30);
741         check_relation(mid, relation31);
742     }
743 
744     // From now on use append mode to not destroy the data we just added.
745     options.append = true;
746 
747     SECTION("Added relations are there and no others")
748     {
749         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
750         mid->start();
751 
752         REQUIRE(no_relation(mid, 5));
753         check_relation(mid, relation30);
754         check_relation(mid, relation31);
755         REQUIRE(no_relation(mid, 32));
756     }
757 
758     SECTION("Delete existing and non-existing relation")
759     {
760         {
761             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
762             mid->start();
763 
764             mid->relation(relation5d);
765             mid->relation(relation30d);
766             mid->relation(relation42d);
767             mid->after_relations();
768 
769             REQUIRE(no_relation(mid, 5));
770             REQUIRE(no_relation(mid, 30));
771             check_relation(mid, relation31);
772             REQUIRE(no_relation(mid, 42));
773         }
774         {
775             // Check with a new mid.
776             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
777             mid->start();
778 
779             REQUIRE(no_relation(mid, 5));
780             REQUIRE(no_relation(mid, 30));
781             check_relation(mid, relation31);
782             REQUIRE(no_relation(mid, 42));
783         }
784     }
785 
786     SECTION("Change (delete and set) existing and non-existing relation")
787     {
788         {
789             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
790             mid->start();
791 
792             mid->relation(relation30d);
793             mid->relation(relation30a);
794             mid->relation(relation32d);
795             mid->relation(relation32);
796             mid->after_relations();
797 
798             REQUIRE(no_relation(mid, 5));
799             check_relation(mid, relation30a);
800             check_relation(mid, relation31);
801             check_relation(mid, relation32);
802             REQUIRE(no_relation(mid, 42));
803         }
804         {
805             // Check with a new mid.
806             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
807             mid->start();
808 
809             REQUIRE(no_relation(mid, 5));
810             check_relation(mid, relation30a);
811             check_relation(mid, relation31);
812             check_relation(mid, relation32);
813             REQUIRE(no_relation(mid, 42));
814         }
815     }
816 
817     SECTION("Add new relation")
818     {
819         {
820             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
821             mid->start();
822 
823             mid->relation(relation32);
824             mid->after_relations();
825 
826             REQUIRE(no_relation(mid, 5));
827             check_relation(mid, relation30);
828             check_relation(mid, relation31);
829             check_relation(mid, relation32);
830             REQUIRE(no_relation(mid, 42));
831         }
832         {
833             // Check with a new mid.
834             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
835             mid->start();
836 
837             REQUIRE(no_relation(mid, 5));
838             check_relation(mid, relation30);
839             check_relation(mid, relation31);
840             check_relation(mid, relation32);
841             REQUIRE(no_relation(mid, 42));
842         }
843     }
844 }
845 
846 TEMPLATE_TEST_CASE("middle: add relation with attributes", "",
847                    options_slim_default, options_flat_node_cache)
848 {
849     auto thread_pool = std::make_shared<thread_pool_t>(1U);
850 
851     options_t options = TestType::options(db);
852 
853     SECTION("With attributes") { options.extra_attributes = true; }
854     SECTION("No attributes") { options.extra_attributes = false; }
855 
856     testing::cleanup::file_t flatnode_cleaner{options.flat_node_file};
857 
858     // Create some relations we'll use for the tests.
859     test_buffer_t buffer;
860     auto &relation30 = buffer.add_relation(
861         "r30 v123 c456 i789 t2009-02-13T23:31:30Z Mw10@outer,w11@inner "
862         "Ttype=multipolygon,name=Penguin_Park");
863 
864     // The same relation but with default attributes.
865     auto &relation30_no_attr = buffer.add_relation(
866         "r30 Mw10@outer,w11@inner Ttype=multipolygon,name=Penguin_Park");
867 
868     // The same relation but with attributes in tags.
869     // The order of the tags is important here!
870     auto &relation30_attr_tags = buffer.add_relation(
871         "r30 Mw10@outer,w11@inner "
872         "Ttype=multipolygon,name=Penguin_Park,osm_user=,osm_uid=789,"
873         "osm_version=123,osm_timestamp=2009-02-13T23:31:30Z,osm_changeset=456");
874 
875     {
876         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
877         mid->start();
878 
879         mid->relation(relation30);
880         mid->after_relations();
881 
882         check_relation(mid, options.extra_attributes ? relation30_attr_tags
883                                                      : relation30_no_attr);
884     }
885 
886     // From now on use append mode to not destroy the data we just added.
887     options.append = true;
888 
889     {
890         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
891         mid->start();
892 
893         check_relation(mid, options.extra_attributes ? relation30_attr_tags
894                                                      : relation30_no_attr);
895     }
896 }
897 
898 TEMPLATE_TEST_CASE("middle: change nodes in way", "", options_slim_default,
899                    options_flat_node_cache)
900 {
901     auto thread_pool = std::make_shared<thread_pool_t>(1U);
902 
903     options_t options = TestType::options(db);
904 
905     testing::cleanup::file_t flatnode_cleaner{options.flat_node_file};
906 
907     // create some nodes and ways we'll use for the tests
908     test_buffer_t buffer;
909     auto const &node10 = buffer.add_node("n10 x1.0 y0.0");
910     auto const &node11 = buffer.add_node("n11 x1.1 y0.0");
911     auto const &node12 = buffer.add_node("n12 x1.2 y0.0");
912     auto const &node10a = buffer.add_node("n10 x2.0 y0.0");
913 
914     auto const &node10d = buffer.add_node("n10 dD");
915 
916     auto const &way20 = buffer.add_way("w20 Nn10,n11");
917     auto const &way21 = buffer.add_way("w21 Nn11,n12");
918     auto const &way22 = buffer.add_way("w22 Nn12,n10");
919     auto const &way20a = buffer.add_way("w20 Nn11,n12");
920 
921     auto const &way20d = buffer.add_way("w20 dD");
922 
923     // Set up middle in "create" mode to get a cleanly initialized database and
924     // add some nodes and ways. Does this in its own scope so that the mid is
925     // closed properly.
926     {
927         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
928         full_dependency_manager_t dependency_manager{mid};
929         mid->start();
930 
931         mid->node(node10);
932         mid->node(node11);
933         mid->node(node12);
934         mid->after_nodes();
935         mid->way(way20);
936         mid->way(way21);
937         mid->after_ways();
938         mid->after_relations();
939 
940         check_node(mid, node10);
941         check_node(mid, node11);
942         check_node(mid, node12);
943         check_way(mid, way20);
944         check_way_nodes(mid, way20.id(), {&node10, &node11});
945         check_way(mid, way21);
946         check_way_nodes(mid, way21.id(), {&node11, &node12});
947 
948         REQUIRE_FALSE(dependency_manager.has_pending());
949     }
950 
951     // From now on use append mode to not destroy the data we just added.
952     options.append = true;
953 
954     SECTION("Single way affected")
955     {
956         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
957         full_dependency_manager_t dependency_manager{mid};
958         mid->start();
959 
960         mid->node(node10d);
961         mid->node(node10a);
962         dependency_manager.node_changed(10);
963         mid->after_nodes();
964 
965         REQUIRE(dependency_manager.has_pending());
966         idlist_t const way_ids = dependency_manager.get_pending_way_ids();
967         REQUIRE_THAT(way_ids, Catch::Equals<osmid_t>({20}));
968 
969         check_way(mid, way20);
970         check_way_nodes(mid, way20.id(), {&node10a, &node11});
971     }
972 
973     SECTION("Two ways affected")
974     {
975         {
976             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
977             mid->start();
978 
979             mid->way(way22);
980             mid->after_ways();
981             mid->after_relations();
982             check_way(mid, way22);
983         }
984         {
985             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
986             full_dependency_manager_t dependency_manager{mid};
987             mid->start();
988 
989             mid->node(node10d);
990             mid->node(node10a);
991             dependency_manager.node_changed(10);
992             mid->after_nodes();
993 
994             REQUIRE(dependency_manager.has_pending());
995             idlist_t const way_ids = dependency_manager.get_pending_way_ids();
996             REQUIRE_THAT(way_ids, Catch::Equals<osmid_t>({20, 22}));
997 
998             check_way(mid, way20);
999             check_way_nodes(mid, way20.id(), {&node10a, &node11});
1000             check_way(mid, way22);
1001             check_way_nodes(mid, way22.id(), {&node12, &node10a});
1002         }
1003     }
1004 
1005     SECTION("Change way so the changing node isn't in it any more")
1006     {
1007         {
1008             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
1009             mid->start();
1010 
1011             mid->way(way20d);
1012             mid->way(way20a);
1013             mid->after_ways();
1014             mid->after_relations();
1015 
1016             check_way(mid, way20a);
1017             check_way_nodes(mid, way20.id(), {&node11, &node12});
1018         }
1019 
1020         {
1021             auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
1022             full_dependency_manager_t dependency_manager{mid};
1023             mid->start();
1024 
1025             mid->node(node10d);
1026             mid->node(node10a);
1027             dependency_manager.node_changed(10);
1028             mid->after_nodes();
1029 
1030             REQUIRE_FALSE(dependency_manager.has_pending());
1031         }
1032     }
1033 }
1034 
1035 TEMPLATE_TEST_CASE("middle: change nodes in relation", "", options_slim_default,
1036                    options_flat_node_cache)
1037 {
1038     auto thread_pool = std::make_shared<thread_pool_t>(1U);
1039 
1040     options_t options = TestType::options(db);
1041 
1042     testing::cleanup::file_t flatnode_cleaner{options.flat_node_file};
1043 
1044     // create some nodes, ways, and relations we'll use for the tests
1045     test_buffer_t buffer;
1046     auto const &node10 = buffer.add_node("n10 x1.0 y0.0");
1047     auto const &node11 = buffer.add_node("n11 x1.1 y0.0");
1048     auto const &node12 = buffer.add_node("n12 x1.2 y0.0");
1049     auto const &node10a = buffer.add_node("n10 x1.0 y1.0");
1050     auto const &node11a = buffer.add_node("n11 x1.1 y1.0");
1051 
1052     auto const &node10d = buffer.add_node("n10 dD");
1053     auto const &node11d = buffer.add_node("n11 dD");
1054 
1055     auto const &way20 = buffer.add_way("w20 Nn11,n12");
1056 
1057     auto const &rel30 = buffer.add_relation("r30 Mn10@");
1058     auto const &rel31 = buffer.add_relation("r31 Mw20@");
1059 
1060     // Set up middle in "create" mode to get a cleanly initialized database and
1061     // add some nodes and ways. Does this in its own scope so that the mid is
1062     // closed properly.
1063     {
1064         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
1065         mid->start();
1066 
1067         mid->node(node10);
1068         mid->node(node11);
1069         mid->node(node12);
1070         mid->after_nodes();
1071         mid->way(way20);
1072         mid->after_ways();
1073         mid->relation(rel30);
1074         mid->relation(rel31);
1075         mid->after_relations();
1076     }
1077 
1078     // From now on use append mode to not destroy the data we just added.
1079     options.append = true;
1080 
1081     SECTION("Single relation directly affected")
1082     {
1083         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
1084         full_dependency_manager_t dependency_manager{mid};
1085         mid->start();
1086 
1087         mid->node(node10d);
1088         mid->node(node10a);
1089         dependency_manager.node_changed(10);
1090         mid->after_nodes();
1091         mid->after_relations();
1092 
1093         REQUIRE(dependency_manager.has_pending());
1094         idlist_t const rel_ids = dependency_manager.get_pending_relation_ids();
1095 
1096         REQUIRE_THAT(rel_ids, Catch::Equals<osmid_t>({30}));
1097         check_relation(mid, rel30);
1098     }
1099 
1100     SECTION("Single relation indirectly affected (through way)")
1101     {
1102         auto mid = std::make_shared<middle_pgsql_t>(thread_pool, &options);
1103         full_dependency_manager_t dependency_manager{mid};
1104         mid->start();
1105 
1106         mid->node(node11d);
1107         mid->node(node11a);
1108         dependency_manager.node_changed(11);
1109         mid->after_nodes();
1110         mid->after_relations();
1111 
1112         REQUIRE(dependency_manager.has_pending());
1113         idlist_t const way_ids = dependency_manager.get_pending_way_ids();
1114         REQUIRE_THAT(way_ids, Catch::Equals<osmid_t>({20}));
1115         idlist_t const rel_ids = dependency_manager.get_pending_relation_ids();
1116         REQUIRE_THAT(rel_ids, Catch::Equals<osmid_t>({31}));
1117         check_relation(mid, rel31);
1118     }
1119 }
1120