/** * SPDX-License-Identifier: GPL-2.0-or-later * * This file is part of osm2pgsql (https://osm2pgsql.org/). * * Copyright (C) 2006-2025 by the osm2pgsql developer community. * For a full list of authors see the git log. */ #include #include #include #include #include #include "middle-pgsql.hpp" #include "middle-ram.hpp" #include "osmdata.hpp" #include "output-null.hpp" #include "output-requirements.hpp" #include "common-buffer.hpp" #include "common-cleanup.hpp" #include "common-options.hpp" #include "common-pg.hpp" namespace { testing::pg::tempdb_t db; void check_locations_are_equal(osmium::Location a, osmium::Location b) { CHECK(a.lat() == Approx(b.lat())); CHECK(a.lon() == Approx(b.lon())); } } // anonymous namespace struct options_slim_default { static options_t options(testing::pg::tempdb_t const &tmpdb) { return testing::opt_t().slim(tmpdb); } }; struct options_slim_with_lc_prefix { static options_t options(testing::pg::tempdb_t const &tmpdb) { options_t o = testing::opt_t().slim(tmpdb); o.prefix = "pre"; return o; } }; struct options_slim_with_uc_prefix { static options_t options(testing::pg::tempdb_t const &tmpdb) { options_t o = testing::opt_t().slim(tmpdb); o.prefix = "PRE"; return o; } }; struct options_slim_with_schema { static options_t options(testing::pg::tempdb_t const &tmpdb) { options_t o = testing::opt_t().slim(tmpdb); o.middle_dbschema = "osm"; return o; } }; struct options_flat_node_cache { static options_t options(testing::pg::tempdb_t const &tmpdb) { return testing::opt_t().slim(tmpdb).flatnodes(); } }; struct options_ram_optimized { static options_t options(testing::pg::tempdb_t const &) { return testing::opt_t(); } }; TEMPLATE_TEST_CASE("middle import", "", options_slim_default, options_slim_with_lc_prefix, options_slim_with_uc_prefix, options_slim_with_schema, options_ram_optimized) { options_t const options = TestType::options(db); testing::cleanup::file_t const flatnode_cleaner{options.flat_node_file}; auto conn = db.connect(); auto const num_tables = conn.get_count("pg_catalog.pg_tables", "schemaname = 'public'"); auto const num_indexes = conn.get_count("pg_catalog.pg_indexes", "schemaname = 'public'"); auto const num_procs = conn.get_count("pg_catalog.pg_proc", "pronamespace = (SELECT oid FROM " "pg_catalog.pg_namespace WHERE nspname = 'public')"); if (options.middle_dbschema == "osm") { conn.exec("CREATE SCHEMA IF NOT EXISTS osm;"); } auto thread_pool = std::make_shared(1U); auto mid = create_middle(thread_pool, options); mid->start(); output_requirements requirements; requirements.full_ways = true; requirements.full_relations = true; mid->set_requirements(requirements); auto const mid_q = mid->get_query_instance(); test_buffer_t buffer; SECTION("Set and retrieve a single node") { auto const &node = buffer.add_node("n1234 x98.7654321 y12.3456789"); // set the node mid->node(node); mid->after_nodes(); mid->after_ways(); mid->after_relations(); // getting it back works only via a waylist auto &nodes = buffer.add_way("w3 Nn1234").nodes(); // get it back REQUIRE(mid_q->nodes_get_list(&nodes) == nodes.size()); check_locations_are_equal(nodes[0].location(), node.location()); // other nodes are not retrievable auto &n2 = buffer.add_way("w3 Nn1,n2,n1235").nodes(); REQUIRE(mid_q->nodes_get_list(&n2) == 0); // check the same thing again using get_node_location() function check_locations_are_equal(mid_q->get_node_location(1234), node.location()); CHECK_FALSE(mid_q->get_node_location(1).valid()); CHECK_FALSE(mid_q->get_node_location(2).valid()); CHECK_FALSE(mid_q->get_node_location(1235).valid()); } SECTION("Set and retrieve a single way") { osmid_t const way_id = 1; double const lon = 98.7654321; double const lat = 12.3456789; idlist_t nds; // set nodes for (osmid_t i = 1; i <= 10; ++i) { nds.push_back(i); auto const &node = buffer.add_node(fmt::format( "n{} x{:.7f} y{:.7f}", i, lon - static_cast(i) * 0.003, lat + static_cast(i) * 0.001)); mid->node(node); } mid->after_nodes(); // set the way mid->way(buffer.add_way(way_id, nds)); mid->after_ways(); mid->after_relations(); // get it back osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes}; REQUIRE(mid_q->way_get(way_id, &outbuf)); auto &way = outbuf.get(0); CHECK(way.id() == way_id); REQUIRE(way.nodes().size() == nds.size()); REQUIRE(mid_q->nodes_get_list(&(way.nodes())) == nds.size()); for (osmid_t i = 1; i <= 10; ++i) { auto const &nr = way.nodes()[static_cast(i) - 1]; CHECK(nr.ref() == i); CHECK(nr.location().lon() == Approx(lon - i * 0.003)); CHECK(nr.location().lat() == Approx(lat + i * 0.001)); } // other ways are not retrievable REQUIRE_FALSE(mid_q->way_get(way_id + 1, &outbuf)); } SECTION("Set and retrieve a single relation with supporting ways") { std::array const nds = { {{4, 5, 13, 14, 342}, {45, 90}, {30, 3, 45}}}; // set the node mid->node(buffer.add_node("n1 x4.1 y12.8")); mid->after_nodes(); // set the ways osmid_t wid = 10; for (auto const &n : nds) { mid->way(buffer.add_way(wid, n)); ++wid; } mid->after_ways(); // set the relation auto const &relation = buffer.add_relation("r123 Mw11@,w10@outer,n1@,w12@inner"); std::vector> const expected = { {osmium::item_type::way, 11}, {osmium::item_type::way, 10}, {osmium::item_type::node, 1}, {osmium::item_type::way, 12}, }; osmium::CRC orig_crc; orig_crc.update(relation); mid->relation(relation); mid->after_relations(); // retrieve the relation osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes}; REQUIRE(mid_q->relation_get(123, &outbuf)); auto const &rel = outbuf.get(0); CHECK(rel.id() == 123); CHECK(rel.members().size() == 4); osmium::CRC crc; crc.update(rel); CHECK(orig_crc().checksum() == crc().checksum()); // retrieve node members only osmium::memory::Buffer memberbuf{ 4096, osmium::memory::Buffer::auto_grow::yes}; REQUIRE(mid_q->rel_members_get(rel, &memberbuf, osmium::osm_entity_bits::node) == 1); { auto const objects = memberbuf.select(); auto it = objects.cbegin(); auto const end = objects.cend(); for (auto const &p : expected) { if (p.first == osmium::item_type::node) { REQUIRE(it != end); REQUIRE(it->type() == p.first); REQUIRE(it->id() == p.second); ++it; } } } memberbuf.clear(); // retrieve way members only REQUIRE(mid_q->rel_members_get(rel, &memberbuf, osmium::osm_entity_bits::way) == 3); { auto const objects = memberbuf.select(); auto it = objects.cbegin(); auto const end = objects.cend(); for (auto const &p : expected) { if (p.first == osmium::item_type::way) { REQUIRE(it != end); REQUIRE(it->type() == p.first); REQUIRE(it->id() == p.second); ++it; } } } memberbuf.clear(); // retrieve all members REQUIRE(mid_q->rel_members_get(rel, &memberbuf, osmium::osm_entity_bits::node | osmium::osm_entity_bits::way) == 4); { auto const objects = memberbuf.select(); auto it = objects.cbegin(); auto const end = objects.cend(); for (auto const &p : expected) { REQUIRE(it != end); REQUIRE(it->type() == p.first); REQUIRE(it->id() == p.second); ++it; } } // other relations are not retrievable REQUIRE_FALSE(mid_q->relation_get(999, &outbuf)); } if (options.middle_dbschema != "public") { REQUIRE(num_tables == conn.get_count("pg_catalog.pg_tables", "schemaname = 'public'")); REQUIRE(num_indexes == conn.get_count("pg_catalog.pg_indexes", "schemaname = 'public'")); REQUIRE(num_procs == conn.get_count( "pg_catalog.pg_proc", "pronamespace = (SELECT oid FROM " "pg_catalog.pg_namespace WHERE nspname = 'public')")); } } namespace { /** * Check that the node is in the mid with the right id and location. */ void check_node(std::shared_ptr const &mid, osmium::Node const &node) { test_buffer_t buffer; auto &nodes = buffer.add_way(999, {node.id()}).nodes(); auto const mid_q = mid->get_query_instance(); REQUIRE(mid_q->nodes_get_list(&nodes) == 1); REQUIRE(nodes[0].ref() == node.id()); REQUIRE(nodes[0].location() == node.location()); } /// Return true if the node with the specified id is not in the mid. bool no_node(std::shared_ptr const &mid, osmid_t id) { test_buffer_t buffer; auto &nodes = buffer.add_way(999, {id}).nodes(); auto const mid_q = mid->get_query_instance(); return mid_q->nodes_get_list(&nodes) == 0; } } // anonymous namespace TEMPLATE_TEST_CASE("middle: add, delete and update node", "", options_slim_default, options_flat_node_cache) { auto thread_pool = std::make_shared(1U); options_t options = TestType::options(db); testing::cleanup::file_t const flatnode_cleaner{options.flat_node_file}; // Prepare a buffer with some nodes which we will add and change. test_buffer_t buffer; auto const &node10 = buffer.add_node("n10 x1.0 y0.0"); auto const &node11 = buffer.add_node("n11 x1.1 y0.0"); auto const &node12 = buffer.add_node("n12 x1.2 y0.0"); auto const &node10a = buffer.add_node("n10 x1.0 y1.0"); auto const &node5d = buffer.add_node("n5 dD"); auto const &node10d = buffer.add_node("n10 dD"); auto const &node12d = buffer.add_node("n12 dD"); auto const &node42d = buffer.add_node("n42 dD"); // Set up middle in "create" mode to get a cleanly initialized database // and add some nodes. Does this in its own scope so that the mid is // closed properly. { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->node(node10); mid->node(node11); mid->after_nodes(); mid->after_ways(); mid->after_relations(); check_node(mid, node10); check_node(mid, node11); } // From now on use append mode to not destroy the data we just added. options.append = true; SECTION("Added nodes are there and no others") { auto mid = std::make_shared(thread_pool, &options); mid->start(); check_node(mid, node10); check_node(mid, node11); REQUIRE(no_node(mid, 5)); REQUIRE(no_node(mid, 42)); } SECTION("Delete existing and non-existing node") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->node(node5d); mid->node(node10d); mid->node(node42d); mid->after_nodes(); mid->after_ways(); mid->after_relations(); REQUIRE(no_node(mid, 5)); REQUIRE(no_node(mid, 10)); check_node(mid, node11); REQUIRE(no_node(mid, 42)); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_node(mid, 5)); REQUIRE(no_node(mid, 10)); check_node(mid, node11); REQUIRE(no_node(mid, 42)); } } SECTION("Change (delete and set) existing and non-existing node") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->node(node10d); mid->node(node10a); mid->node(node12d); mid->node(node12); mid->after_nodes(); mid->after_ways(); mid->after_relations(); check_node(mid, node10a); check_node(mid, node11); check_node(mid, node12); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); check_node(mid, node10a); check_node(mid, node11); check_node(mid, node12); } } SECTION("Add new node") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->node(node12); mid->after_nodes(); mid->after_ways(); mid->after_relations(); REQUIRE(no_node(mid, 5)); check_node(mid, node10); check_node(mid, node11); check_node(mid, node12); REQUIRE(no_node(mid, 42)); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_node(mid, 5)); check_node(mid, node10); check_node(mid, node11); check_node(mid, node12); REQUIRE(no_node(mid, 42)); } } } namespace { /** * Check that the way is in the mid with the right attributes and tags. * Does not check node locations. */ void check_way(std::shared_ptr const &mid, osmium::Way const &orig_way) { auto const mid_q = mid->get_query_instance(); osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes}; REQUIRE(mid_q->way_get(orig_way.id(), &outbuf)); auto const &way = outbuf.get(0); CHECK(orig_way.id() == way.id()); CHECK(orig_way.timestamp() == way.timestamp()); CHECK(orig_way.version() == way.version()); CHECK(orig_way.changeset() == way.changeset()); CHECK(orig_way.uid() == way.uid()); CHECK(std::strcmp(orig_way.user(), way.user()) == 0); REQUIRE(orig_way.tags().size() == way.tags().size()); for (auto it1 = orig_way.tags().cbegin(), it2 = way.tags().cbegin(); it1 != orig_way.tags().cend(); ++it1, ++it2) { CHECK(*it1 == *it2); } REQUIRE(orig_way.nodes().size() == way.nodes().size()); for (auto it1 = orig_way.nodes().cbegin(), it2 = way.nodes().cbegin(); it1 != orig_way.nodes().cend(); ++it1, ++it2) { CHECK(*it1 == *it2); } osmium::CRC orig_crc; orig_crc.update(orig_way); osmium::CRC test_crc; test_crc.update(way); REQUIRE(orig_crc().checksum() == test_crc().checksum()); } /** * Check that the nodes (ids and locations) of the way with the way_id in the * mid are identical to the nodes in the nodes vector. */ void check_way_nodes(std::shared_ptr const &mid, osmid_t way_id, std::vector const &nodes) { auto const mid_q = mid->get_query_instance(); osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes}; REQUIRE(mid_q->way_get(way_id, &outbuf)); auto &way = outbuf.get(0); REQUIRE(mid_q->nodes_get_list(&way.nodes()) == way.nodes().size()); REQUIRE(way.nodes().size() == nodes.size()); REQUIRE(std::equal(way.nodes().cbegin(), way.nodes().cend(), nodes.cbegin(), [](osmium::NodeRef const &nr, osmium::Node const *node) { return nr.ref() == node->id() && nr.location() == node->location(); })); } /// Return true if the way with the specified id is not in the mid. bool no_way(std::shared_ptr const &mid, osmid_t id) { auto const mid_q = mid->get_query_instance(); osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes}; return !mid_q->way_get(id, &outbuf); } } // anonymous namespace TEMPLATE_TEST_CASE("middle: add, delete and update way", "", options_slim_default, options_flat_node_cache) { auto thread_pool = std::make_shared(1U); options_t options = TestType::options(db); testing::cleanup::file_t const flatnode_cleaner{options.flat_node_file}; // Create some ways we'll use for the tests. test_buffer_t buffer; auto const &way20 = buffer.add_way("w20 Nn10,n11 Thighway=residential,name=High_Street"); auto const &way21 = buffer.add_way("w21 Nn11,n12"); auto const &way22 = buffer.add_way("w22 Nn12,n10 Tpower=line"); auto &way20a = buffer.add_way("w20 Nn10,n12 Thighway=primary,name=High_Street"); auto const &way5d = buffer.add_way("w5 dD"); auto &way20d = buffer.add_way("w20 dD"); auto &way22d = buffer.add_way("w22 dD"); auto &way42d = buffer.add_way("w42 dD"); // Set up middle in "create" mode to get a cleanly initialized database and // add some ways. Does this in its own scope so that the mid is closed // properly. { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->way(way20); mid->way(way21); mid->after_ways(); mid->after_relations(); check_way(mid, way20); check_way(mid, way21); } // From now on use append mode to not destroy the data we just added. options.append = true; SECTION("Added ways are there and no others") { auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_way(mid, 5)); check_way(mid, way20); check_way(mid, way21); REQUIRE(no_way(mid, 22)); } SECTION("Delete existing and non-existing way") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->way(way5d); mid->way(way20d); mid->way(way42d); mid->after_ways(); mid->after_relations(); REQUIRE(no_way(mid, 5)); REQUIRE(no_way(mid, 20)); check_way(mid, way21); REQUIRE(no_way(mid, 42)); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_way(mid, 5)); REQUIRE(no_way(mid, 20)); check_way(mid, way21); REQUIRE(no_way(mid, 42)); } } SECTION("Change (delete and set) existing and non-existing way") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->way(way20d); mid->way(way20a); mid->way(way22d); mid->way(way22); mid->after_ways(); mid->after_relations(); REQUIRE(no_way(mid, 5)); check_way(mid, way20a); check_way(mid, way21); check_way(mid, way22); REQUIRE(no_way(mid, 42)); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_way(mid, 5)); check_way(mid, way20a); check_way(mid, way21); check_way(mid, way22); REQUIRE(no_way(mid, 42)); } } SECTION("Add new way") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->way(way22); mid->after_ways(); mid->after_relations(); REQUIRE(no_way(mid, 5)); check_way(mid, way20); check_way(mid, way21); check_way(mid, way22); REQUIRE(no_way(mid, 42)); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_way(mid, 5)); check_way(mid, way20); check_way(mid, way21); check_way(mid, way22); REQUIRE(no_way(mid, 42)); } } } TEMPLATE_TEST_CASE("middle: add way with attributes", "", options_slim_default, options_flat_node_cache) { auto thread_pool = std::make_shared(1U); options_t options = TestType::options(db); SECTION("With attributes") { options.extra_attributes = true; } SECTION("No attributes") { options.extra_attributes = false; } testing::cleanup::file_t const flatnode_cleaner{options.flat_node_file}; // Create some ways we'll use for the tests. test_buffer_t buffer; auto &way20 = buffer.add_way("w20 Nn10,n11 Thighway=residential,name=High_Street"); way20.set_version(123); way20.set_timestamp(1234567890); way20.set_changeset(456); way20.set_uid(789); // The same way but with default attributes. auto &way20_no_attr = buffer.add_way("w20 Nn10,n11 Thighway=residential,name=High_Street"); { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->way(way20); mid->after_ways(); mid->after_relations(); check_way(mid, options.extra_attributes ? way20 : way20_no_attr); } // From now on use append mode to not destroy the data we just added. options.append = true; { auto mid = std::make_shared(thread_pool, &options); mid->start(); check_way(mid, options.extra_attributes ? way20 : way20_no_attr); } } namespace { /** * Check that the relation is in the mid with the right attributes, members * and tags. Only checks the relation, does not recurse into members. */ void check_relation(std::shared_ptr const &mid, osmium::Relation const &orig_relation) { auto const mid_q = mid->get_query_instance(); osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes}; REQUIRE(mid_q->relation_get(orig_relation.id(), &outbuf)); auto const &relation = outbuf.get(0); CHECK(orig_relation.id() == relation.id()); CHECK(orig_relation.timestamp() == relation.timestamp()); CHECK(orig_relation.version() == relation.version()); CHECK(orig_relation.changeset() == relation.changeset()); CHECK(orig_relation.uid() == relation.uid()); CHECK(std::strcmp(orig_relation.user(), relation.user()) == 0); REQUIRE(orig_relation.tags().size() == relation.tags().size()); for (auto it1 = orig_relation.tags().cbegin(), it2 = relation.tags().cbegin(); it1 != orig_relation.tags().cend(); ++it1, ++it2) { CHECK(*it1 == *it2); } REQUIRE(orig_relation.members().size() == relation.members().size()); for (auto it1 = orig_relation.members().cbegin(), it2 = relation.members().cbegin(); it1 != orig_relation.members().cend(); ++it1, ++it2) { CHECK(it1->type() == it2->type()); CHECK(it1->ref() == it2->ref()); CHECK(std::strcmp(it1->role(), it2->role()) == 0); } osmium::CRC orig_crc; orig_crc.update(orig_relation); osmium::CRC test_crc; test_crc.update(relation); REQUIRE(orig_crc().checksum() == test_crc().checksum()); } /// Return true if the relation with the specified id is not in the mid. bool no_relation(std::shared_ptr const &mid, osmid_t id) { auto const mid_q = mid->get_query_instance(); osmium::memory::Buffer outbuf{4096, osmium::memory::Buffer::auto_grow::yes}; return !mid_q->relation_get(id, &outbuf); } } // anonymous namespace TEMPLATE_TEST_CASE("middle: add, delete and update relation", "", options_slim_default, options_flat_node_cache) { auto thread_pool = std::make_shared(1U); options_t options = TestType::options(db); testing::cleanup::file_t const flatnode_cleaner{options.flat_node_file}; // Create some relations we'll use for the tests. test_buffer_t buffer; auto const &relation30 = buffer.add_relation( "r30 Mw10@outer,w11@innder Tname=Penguin_Park,type=multipolygon"); auto const &relation31 = buffer.add_relation("r31 Mn10@"); auto const &relation32 = buffer.add_relation("r32 Mr39@ Ttype=site"); auto const &relation30a = buffer.add_relation( "r30 Mw10@outer,w11@outer Tname=Pigeon_Park,type=multipolygon"); auto const &relation5d = buffer.add_relation("r5 dD"); auto const &relation30d = buffer.add_relation("r30 dD"); auto const &relation32d = buffer.add_relation("r32 dD"); auto const &relation42d = buffer.add_relation("r42 dD"); // Set up middle in "create" mode to get a cleanly initialized database and // add some relations. Does this in its own scope so that the mid is closed // properly. { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->after_ways(); mid->relation(relation30); mid->relation(relation31); mid->after_relations(); check_relation(mid, relation30); check_relation(mid, relation31); } // From now on use append mode to not destroy the data we just added. options.append = true; SECTION("Added relations are there and no others") { auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_relation(mid, 5)); check_relation(mid, relation30); check_relation(mid, relation31); REQUIRE(no_relation(mid, 32)); } SECTION("Delete existing and non-existing relation") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->after_ways(); mid->relation(relation5d); mid->relation(relation30d); mid->relation(relation42d); mid->after_relations(); REQUIRE(no_relation(mid, 5)); REQUIRE(no_relation(mid, 30)); check_relation(mid, relation31); REQUIRE(no_relation(mid, 42)); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_relation(mid, 5)); REQUIRE(no_relation(mid, 30)); check_relation(mid, relation31); REQUIRE(no_relation(mid, 42)); } } SECTION("Change (delete and set) existing and non-existing relation") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->after_ways(); mid->relation(relation30d); mid->relation(relation30a); mid->relation(relation32d); mid->relation(relation32); mid->after_relations(); REQUIRE(no_relation(mid, 5)); check_relation(mid, relation30a); check_relation(mid, relation31); check_relation(mid, relation32); REQUIRE(no_relation(mid, 42)); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_relation(mid, 5)); check_relation(mid, relation30a); check_relation(mid, relation31); check_relation(mid, relation32); REQUIRE(no_relation(mid, 42)); } } SECTION("Add new relation") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->after_ways(); mid->relation(relation32); mid->after_relations(); REQUIRE(no_relation(mid, 5)); check_relation(mid, relation30); check_relation(mid, relation31); check_relation(mid, relation32); REQUIRE(no_relation(mid, 42)); } { // Check with a new mid. auto mid = std::make_shared(thread_pool, &options); mid->start(); REQUIRE(no_relation(mid, 5)); check_relation(mid, relation30); check_relation(mid, relation31); check_relation(mid, relation32); REQUIRE(no_relation(mid, 42)); } } } TEMPLATE_TEST_CASE("middle: add relation with attributes", "", options_slim_default, options_flat_node_cache) { auto thread_pool = std::make_shared(1U); options_t options = TestType::options(db); SECTION("With attributes") { options.extra_attributes = true; } SECTION("No attributes") { options.extra_attributes = false; } testing::cleanup::file_t const flatnode_cleaner{options.flat_node_file}; // Create some relations we'll use for the tests. test_buffer_t buffer; auto const &relation30 = buffer.add_relation( "r30 v123 c456 i789 t2009-02-13T23:31:30Z Mw10@outer,w11@inner " "Tname=Penguin_Park,type=multipolygon"); // The same relation but with default attributes. auto const &relation30_no_attr = buffer.add_relation( "r30 Mw10@outer,w11@inner Tname=Penguin_Park,type=multipolygon"); { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->after_ways(); mid->relation(relation30); mid->after_relations(); check_relation(mid, options.extra_attributes ? relation30 : relation30_no_attr); } // From now on use append mode to not destroy the data we just added. options.append = true; { auto mid = std::make_shared(thread_pool, &options); mid->start(); check_relation(mid, options.extra_attributes ? relation30 : relation30_no_attr); } } TEMPLATE_TEST_CASE("middle: change nodes in way", "", options_slim_default, options_flat_node_cache) { auto thread_pool = std::make_shared(1U); options_t options = TestType::options(db); testing::cleanup::file_t const flatnode_cleaner{options.flat_node_file}; // create some nodes and ways we'll use for the tests test_buffer_t buffer; auto const &node10 = buffer.add_node("n10 x1.0 y0.0"); auto const &node11 = buffer.add_node("n11 x1.1 y0.0"); auto const &node12 = buffer.add_node("n12 x1.2 y0.0"); auto const &node10a = buffer.add_node("n10 x2.0 y0.0"); auto const &node10d = buffer.add_node("n10 dD"); auto &way20 = buffer.add_way("w20 Nn10,n11"); auto &way21 = buffer.add_way("w21 Nn11,n12"); auto &way22 = buffer.add_way("w22 Nn12,n10"); auto &way20a = buffer.add_way("w20 Nn11,n12"); auto &way20d = buffer.add_way("w20 dD"); // Set up middle in "create" mode to get a cleanly initialized database and // add some nodes and ways. Does this in its own scope so that the mid is // closed properly. { auto mid = std::make_shared(thread_pool, &options); mid->start(); auto output = std::make_shared(mid->get_query_instance(), thread_pool, options); osmdata_t osmdata{mid, output, options}; osmdata.node(node10); osmdata.node(node11); osmdata.node(node12); osmdata.after_nodes(); osmdata.way(way20); osmdata.way(way21); osmdata.after_ways(); osmdata.after_relations(); check_node(mid, node10); check_node(mid, node11); check_node(mid, node12); check_way(mid, way20); check_way_nodes(mid, way20.id(), {&node10, &node11}); check_way(mid, way21); check_way_nodes(mid, way21.id(), {&node11, &node12}); REQUIRE(osmdata.get_pending_way_ids().empty()); REQUIRE(osmdata.get_pending_relation_ids().empty()); osmdata.stop(); } // From now on use append mode to not destroy the data we just added. options.append = true; SECTION("Single way affected") { auto mid = std::make_shared(thread_pool, &options); mid->start(); auto output = std::make_shared(mid->get_query_instance(), thread_pool, options); osmdata_t osmdata{mid, output, options}; osmdata.node(node10d); osmdata.node(node10a); osmdata.after_nodes(); osmdata.after_ways(); osmdata.after_relations(); idlist_t const &way_ids = osmdata.get_pending_way_ids(); REQUIRE(way_ids == idlist_t{20}); REQUIRE(osmdata.get_pending_relation_ids().empty()); check_way(mid, way20); check_way_nodes(mid, way20.id(), {&node10a, &node11}); } SECTION("Two ways affected") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->after_nodes(); mid->way(way22); mid->after_ways(); mid->after_relations(); check_way(mid, way22); } { auto mid = std::make_shared(thread_pool, &options); mid->start(); auto output = std::make_shared( mid->get_query_instance(), thread_pool, options); osmdata_t osmdata{mid, output, options}; osmdata.node(node10d); osmdata.node(node10a); osmdata.after_nodes(); osmdata.after_ways(); osmdata.after_relations(); idlist_t const &way_ids = osmdata.get_pending_way_ids(); REQUIRE(way_ids == idlist_t{20, 22}); REQUIRE(osmdata.get_pending_relation_ids().empty()); check_way(mid, way20); check_way_nodes(mid, way20.id(), {&node10a, &node11}); check_way(mid, way22); check_way_nodes(mid, way22.id(), {&node12, &node10a}); } } SECTION("Change way so the changing node isn't in it any more") { { auto mid = std::make_shared(thread_pool, &options); mid->start(); auto output = std::make_shared( mid->get_query_instance(), thread_pool, options); osmdata_t osmdata{mid, output, options}; osmdata.after_nodes(); osmdata.way(way20d); osmdata.way(way20a); osmdata.after_ways(); osmdata.after_relations(); check_way(mid, way20a); check_way_nodes(mid, way20.id(), {&node11, &node12}); } { auto mid = std::make_shared(thread_pool, &options); mid->start(); auto output = std::make_shared( mid->get_query_instance(), thread_pool, options); osmdata_t osmdata{mid, output, options}; osmdata.node(node10d); osmdata.node(node10a); osmdata.after_nodes(); osmdata.after_ways(); osmdata.after_relations(); REQUIRE(osmdata.get_pending_way_ids().empty()); REQUIRE(osmdata.get_pending_relation_ids().empty()); } } } TEMPLATE_TEST_CASE("middle: change nodes in relation", "", options_slim_default, options_flat_node_cache) { auto thread_pool = std::make_shared(1U); options_t options = TestType::options(db); testing::cleanup::file_t const flatnode_cleaner{options.flat_node_file}; // create some nodes, ways, and relations we'll use for the tests test_buffer_t buffer; auto const &node10 = buffer.add_node("n10 x1.0 y0.0"); auto const &node11 = buffer.add_node("n11 x1.1 y0.0"); auto const &node12 = buffer.add_node("n12 x1.2 y0.0"); auto const &node10a = buffer.add_node("n10 x1.0 y1.0"); auto const &node11a = buffer.add_node("n11 x1.1 y1.0"); auto const &node10d = buffer.add_node("n10 dD"); auto const &node11d = buffer.add_node("n11 dD"); auto const &way20 = buffer.add_way("w20 Nn11,n12"); auto const &rel30 = buffer.add_relation("r30 Mn10@"); auto const &rel31 = buffer.add_relation("r31 Mw20@"); // Set up middle in "create" mode to get a cleanly initialized database and // add some nodes and ways. Does this in its own scope so that the mid is // closed properly. { auto mid = std::make_shared(thread_pool, &options); mid->start(); mid->node(node10); mid->node(node11); mid->node(node12); mid->after_nodes(); mid->way(way20); mid->after_ways(); mid->relation(rel30); mid->relation(rel31); mid->after_relations(); mid->stop(); mid->wait(); } // From now on use append mode to not destroy the data we just added. options.append = true; SECTION("Single relation directly affected") { auto mid = std::make_shared(thread_pool, &options); mid->start(); auto output = std::make_shared(mid->get_query_instance(), thread_pool, options); osmdata_t osmdata{mid, output, options}; osmdata.node(node10d); osmdata.node(node10a); osmdata.after_nodes(); osmdata.after_ways(); osmdata.after_relations(); REQUIRE(osmdata.get_pending_way_ids().empty()); idlist_t const &rel_ids = osmdata.get_pending_relation_ids(); REQUIRE(rel_ids == idlist_t{30}); check_relation(mid, rel30); } SECTION("Single relation indirectly affected (through way)") { auto mid = std::make_shared(thread_pool, &options); mid->start(); auto output = std::make_shared(mid->get_query_instance(), thread_pool, options); osmdata_t osmdata{mid, output, options}; osmdata.node(node11d); osmdata.node(node11a); osmdata.after_nodes(); osmdata.after_ways(); osmdata.after_relations(); idlist_t const &way_ids = osmdata.get_pending_way_ids(); REQUIRE(way_ids == idlist_t{20}); idlist_t const &rel_ids = osmdata.get_pending_relation_ids(); REQUIRE(rel_ids == idlist_t{31}); check_relation(mid, rel31); } }