#include "maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/work/isocode/propagate_geometry.h"
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/tests/fixtures/ymapsdf_fixture.h>

#include <yandex/maps/wiki/unittest/localdb.h>
#include <yandex/maps/wiki/unittest/query_helpers.h>
#include <boost/test/unit_test.hpp>

namespace maps {
namespace wiki {
namespace tests {
namespace {

unittest::RandomDatabaseFixture& globalFixture()
{
    static unittest::RandomDatabaseFixture fixture;
    return fixture;
}


class PropagateGeometry: public YmapsdfFixture {
public:
    const size_t THREADS_NUM = 5;
    std::shared_ptr<std::atomic<bool>> fail;

    PropagateGeometry()
        : YmapsdfFixture(
            globalFixture().pool().getMasterConnection(),
            "propagate_geometry", // YmapsdfFixture::SCHEMA
            YMAPSDF_EXPORT_FILE)
        , fail(new std::atomic<bool>(false))
    {
        // Remove constraints to make tests easier (no need to fill additional stuff).
        applyQueryToSchema(
            SCHEMA,
            "ALTER TABLE addr DROP CONSTRAINT addr_ad_id_fkey;"
            "ALTER TABLE addr DROP CONSTRAINT addr_check;"
            "ALTER TABLE addr DROP CONSTRAINT addr_ft_id_fkey;"
            "ALTER TABLE addr DROP CONSTRAINT addr_node_id_fkey;"
            "ALTER TABLE addr DROP CONSTRAINT addr_rd_id_fkey;"
            "ALTER TABLE bld ALTER ft_type_id DROP NOT NULL;"
            "ALTER TABLE bld DROP CONSTRAINT bld_model3d_id_fkey;"
            "ALTER TABLE bld_face DROP CONSTRAINT bld_face_bld_id_fkey;"
            "ALTER TABLE bld_face DROP CONSTRAINT bld_face_face_id_fkey;"
            "ALTER TABLE bound_jc DROP CONSTRAINT bound_jc_rd_jc_id_fkey;"
            "ALTER TABLE cond ALTER cond_type DROP NOT NULL;"
            "ALTER TABLE cond_rd_seq DROP CONSTRAINT cond_rd_seq_rd_el_id_fkey;"
            "ALTER TABLE edge ALTER shape DROP NOT NULL;"
            "ALTER TABLE edge DROP CONSTRAINT edge_f_node_id_fkey;"
            "ALTER TABLE edge DROP CONSTRAINT edge_t_node_id_fkey;"
            "ALTER TABLE face_edge DROP CONSTRAINT face_edge_edge_id_fkey;"
            "ALTER TABLE face_edge DROP CONSTRAINT face_edge_face_id_fkey;"
            "ALTER TABLE ft ALTER ft_type_id DROP NOT NULL;"
            "ALTER TABLE ft_center DROP CONSTRAINT ft_center_ft_id_fkey;"
            "ALTER TABLE ft_center DROP CONSTRAINT ft_center_node_id_fkey;"
            "ALTER TABLE ft_edge DROP CONSTRAINT ft_edge_edge_id_fkey;"
            "ALTER TABLE ft_edge DROP CONSTRAINT ft_edge_ft_id_fkey;"
            "ALTER TABLE ft_face DROP CONSTRAINT ft_face_face_id_fkey;"
            "ALTER TABLE ft_face DROP CONSTRAINT ft_face_ft_id_fkey;"
            "ALTER TABLE ft_rd_el DROP CONSTRAINT ft_rd_el_ft_id_fkey;"
            "ALTER TABLE ft_rd_el DROP CONSTRAINT ft_rd_el_rd_el_id_fkey;"
            "ALTER TABLE rd_el ALTER shape DROP NOT NULL;"
            "ALTER TABLE rd_el DROP CONSTRAINT rd_el_f_rd_jc_id_fkey;"
            "ALTER TABLE rd_el DROP CONSTRAINT rd_el_t_rd_jc_id_fkey;"
            "ALTER TABLE rd_rd_el DROP CONSTRAINT rd_rd_el_rd_el_id_fkey;"
            "ALTER TABLE rd_rd_el DROP CONSTRAINT rd_rd_el_rd_id_fkey;"
        );
    }
};

} // namespace


BOOST_FIXTURE_TEST_SUITE(main, PropagateGeometry)

using json2ymapsdf::isocode::propagateGeometry;


/*****************************************************************************/
/*                   S I M P L E   P R O P A G A T I O N S                   */
/*****************************************************************************/
BOOST_AUTO_TEST_CASE(shouldPropagateAdToAddr)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ad_isocode (ad_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
        "INSERT INTO addr (addr_id, node_id, ad_id, isocode) VALUES"
        "(1, 1, 1, 'CC'),"
        "(2, 2, 2, 'CC');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("addr", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("addr", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFtToAddr)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft_isocode (ft_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
        "INSERT INTO addr (addr_id, node_id, ft_id, isocode) VALUES"
        "(1, 1, 1, 'CC'),"
        "(2, 2, 2, 'CC');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("addr", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("addr", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateRdToAddr)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO rd_isocode (rd_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
        "INSERT INTO addr (addr_id, node_id, rd_id, isocode) VALUES"
        "(1, 1, 1, 'CC'),"
        "(2, 2, 2, 'CC');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("addr", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("addr", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFaceToBld)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO bld_face (bld_id, face_id, is_interior) VALUES"
        "(1, 1, false),"
        "(2, 2, true);"
        "INSERT INTO face_isocode (face_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("bld", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("bld", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateBldToParentBld)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO bld (bld_id, p_bld_id, isocode) VALUES"
        "(1, NULL, 'CC'),"
        "(2,    1, 'CC'),"
        "(3,    2, 'CC');" // It is incorrect for a bld to has
                           // grandchildren. This test just 'frozes' the
                           // behaviour, so it won't be changed unnoticed.
        "INSERT INTO bld_isocode (bld_id, isocode) VALUES"
        "(2, 'AA'),"
        "(3, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("bld", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("bld", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("bld", 3, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateRdElToCond_oneRdElWithIsocode)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(1, 2, 1);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("cond", 1, {"AA"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateRdElToCond_sameIsocodes)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(1, 2, 1);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("cond", 1, {"AA"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateRdElToCond_differentIsocodes)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(1, 2, 1);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("cond", 1, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateRdElToCond_longCond)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(1, 2, 1),"
        "(1, 3, 2),"
        "(1, 4, 3);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(3, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("cond", 1, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateCrossborderFaceToEdge)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO face_edge (face_id, edge_id) VALUES"
        "(1, 1);"
        "INSERT INTO face_isocode (face_id, isocode) VALUES"
        "(1, 'AA'),"
        "(1, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("edge", 1, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldNotPropagateNonCrossborderFaceToEdge)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO face_edge (face_id, edge_id) VALUES"
        "(1, 1);"
        "INSERT INTO face_isocode (face_id, isocode) VALUES"
        "(1, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("edge", 1, {}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateCrossborderBldToFace)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO bld_face (bld_id, face_id, is_interior) VALUES"
        "(1, 1, true);"
        "INSERT INTO bld_isocode (bld_id, isocode) VALUES"
        "(1, 'AA'),"
        "(1, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("face", 1, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldNotPropagateNonCrossborderBldToFace)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO bld_face (bld_id, face_id, is_interior) VALUES"
        "(1, 1, true);"
        "INSERT INTO bld_isocode (bld_id, isocode) VALUES"
        "(1, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("face", 1, {}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateEdgeToFace)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO face (face_id) VALUES"
        "(1),"
        "(2),"
        "(3);"
        "INSERT INTO face_edge (face_id, edge_id) VALUES"
        "(1, 1),"
        "(2, 2),"
        "(3, 1),"
        "(3, 2);"
        "INSERT INTO edge_isocode (edge_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("face", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("face", 2, {"BB"}));
    BOOST_CHECK(hasIsocodes("face", 3, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFtFaceToFt)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft_face (ft_id, face_id, is_interior) VALUES"
        "(1, 1, false),"
        "(2, 2, true),"
        "(3, 1, true),"
        "(3, 2, false);"
        "INSERT INTO face_isocode (face_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"BB"}));
    BOOST_CHECK(hasIsocodes("ft", 3, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFtEdgeToFt)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft_edge (ft_id, edge_id) VALUES"
        "(1, 1),"
        "(2, 2),"
        "(3, 1),"
        "(3, 2);"
        "INSERT INTO edge_isocode (edge_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"BB"}));
    BOOST_CHECK(hasIsocodes("ft", 3, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFtCenterToFt)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft_center (ft_id, node_id) VALUES"
        "(1, 1),"
        "(2, 2);"
        "INSERT INTO node_isocode (node_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFtRdElToFt)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft_rd_el (ft_id, rd_el_id) VALUES"
        "(1, 1),"
        "(2, 2),"
        "(3, 1),"
        "(3, 2);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"BB"}));
    BOOST_CHECK(hasIsocodes("ft", 3, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFtToSlaveFtWithoutIsocodesAndParentFt)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft (ft_id, p_ft_id, isocode) VALUES"
        "(1, NULL, 'CC'),"
        "(2,    1, 'CC'),"
        "(3,    2, 'CC'),"
        "(4,    3, 'CC');"
        "INSERT INTO ft_isocode (ft_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"BB"}));
    BOOST_CHECK(hasIsocodes("ft", 3, {"BB"}));
    BOOST_CHECK(hasIsocodes("ft", 4, {"BB"}));
}

BOOST_AUTO_TEST_CASE(shouldPropagateFtToParent)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft (ft_id, p_ft_id, isocode) VALUES"
        "(1, NULL, 'CC'),"
        "(2,    1, 'CC'),"
        "(3,    2, 'CC'),"
        "(4,    3, 'CC');"
        "INSERT INTO ft_isocode (ft_id, isocode) VALUES"
        "(2, 'AA'),"
        "(4, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("ft", 3, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("ft", 4, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateBldToModel3d)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO bld (bld_id, isocode, model3d_id) VALUES"
        "(1, 'CC', 1),"
        "(2, 'CC', 2);"
        "INSERT INTO bld_isocode (bld_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("model3d", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("model3d", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateCrossborderEdgeToNode)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO edge (edge_id, f_node_id, t_node_id) VALUES"
        "(1, 1, 2);"
        "INSERT INTO edge_isocode (edge_id, isocode) VALUES"
        "(1, 'AA'),"
        "(1, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("node", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("node", 2, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldNotPropagateNonCrossborderEdgeToNode)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO edge (edge_id, f_node_id, t_node_id) VALUES"
        "(1, 1, 2);"
        "INSERT INTO edge_isocode (edge_id, isocode) VALUES"
        "(1, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("node", 1, {}));
    BOOST_CHECK(hasIsocodes("node", 2, {}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateAddrToNode)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO addr (addr_id, node_id) VALUES"
        "(1, 1),"
        "(2, 2);"
        "INSERT INTO addr_isocode (addr_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("node", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("node", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFtToNode)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft_center (ft_id, node_id) VALUES"
        "(1, 1),"
        "(2, 2);"
        "INSERT INTO ft_isocode (ft_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("node", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("node", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateCondToRdEl)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1),"
        "(2, 2);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(1, 2, 1),"
        "(2, 2, 1),"
        "(2, 3, 0);"
        "INSERT INTO cond_isocode (cond_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("rd_el", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("rd_el", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("rd_el", 3, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateRdElToRd)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO rd_rd_el (rd_el_id, rd_id) VALUES"
        "(1, 1),"
        "(2, 2);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("rd", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("rd", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateCrossborderRdElToRdJc)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO rd_el (rd_el_id, f_rd_jc_id, t_rd_jc_id, isocode) VALUES"
        "(1, 1, 2, 'CC');"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(1, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("rd_jc", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("rd_jc", 2, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldNotPropagateNonCrossborderRdElToRdJc)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO rd_el (rd_el_id, f_rd_jc_id, t_rd_jc_id, isocode) VALUES"
        "(1, 1, 2, 'BB');"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("rd_jc", 1, {}));
    BOOST_CHECK(hasIsocodes("rd_jc", 2, {}));
}


/*****************************************************************************/
/*                                C Y C L E S                                */
/*****************************************************************************/
BOOST_AUTO_TEST_CASE(shouldPropagateRdElToCondToRdEl)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(1, 2, 1);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("rd_el", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("rd_el", 2, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldNotPropagateCondToRdElToCond)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1),"
        "(2, 2);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(2, 1, 0);"
        "INSERT INTO cond_isocode (cond_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("cond", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("cond", 2, {"BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFtToSlaveFtToNodeAndAddr)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft (ft_id, p_ft_id) VALUES"
        "(1, NULL),"
        "(2,    1);"
        "INSERT INTO ft_center (ft_id, node_id) VALUES"
        "(2, 2);"               // node to be checked
        "INSERT INTO addr (addr_id, node_id, ft_id) VALUES"
        "(2, 3, 2);"            // addr to be checked
        "INSERT INTO ft_isocode (ft_id, isocode) VALUES"
        "(1, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("node", 2, {"AA"}));
    BOOST_CHECK(hasIsocodes("addr", 2, {"AA"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateCrossborderBldToFaceAndModel3dViaParentBld)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO bld (bld_id, p_bld_id, model3d_id) VALUES"
        "(1, NULL,    1),"
        "(2,    1, NULL);"
        "INSERT INTO bld_face (bld_id, face_id, is_interior) VALUES"
        "(1, 1, true);"
        "INSERT INTO bld_isocode (bld_id, isocode) VALUES"
        "(1, 'AA'),"
        "(1, 'BB'),"
        "(2, 'CC');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("face", 1, {"AA", "BB", "CC"}));
    BOOST_CHECK(hasIsocodes("model3d", 1, {"AA", "BB", "CC"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateEdgeToFaceToEdge)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO face (face_id) VALUES"
        "(1);"
        "INSERT INTO face_edge (face_id, edge_id) VALUES"
        "(1, 1),"
        "(1, 2);"
        "INSERT INTO edge_isocode (edge_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("face", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("edge", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("edge", 2, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFaceToBldToFace)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO face (face_id) VALUES"
        "(1),"
        "(2);"
        "INSERT INTO bld_face (bld_id, face_id, is_interior) VALUES"
        "(1, 1, false),"
        "(1, 2, true);"
        "INSERT INTO face_isocode (face_id, isocode) VALUES"
        "(1, 'AA'),"
        "(1, 'BB'),"
        "(2, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("bld", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("face", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("face", 2, {"AA", "BB"}));
}


/*****************************************************************************/
/*                        L O N G   S E Q U E N C E S                        */
/*****************************************************************************/
BOOST_AUTO_TEST_CASE(shouldPropagateRdElToNode)
{
    applyQueryToSchema(
        SCHEMA,
        // rd_el -> cond -> rd_el: rd_el#1 and rd_el#2 (AA, BB)
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(1, 2, 1);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"

        // rd_el -> ft: ft#1 (AA, BB)
        "INSERT INTO ft_rd_el (ft_id, rd_el_id) VALUES"
        "(1, 1);"

        // rd_el -> rd: rd#2 (AA, BB)
        "INSERT INTO rd_rd_el (rd_el_id, rd_id) VALUES"
        "(2, 2);"

        // ft -> addr -> node: addr#1, node#1 (AA, BB)
        "INSERT INTO addr (addr_id, node_id, ft_id) VALUES"
        "(1, 1, 1);"

        // rd -> addr -> node: addr#2, node#2 (AA, BB)
        "INSERT INTO addr (addr_id, node_id, rd_id) VALUES"
        "(2, 2, 2);"

        // ft -> node: node3 (AA, BB)
        "INSERT INTO ft_center (ft_id, node_id) VALUES"
        "(1, 3);"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("rd_el", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("rd_el", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("ft", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("rd", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("addr", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("addr", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("node", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("node", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("node", 3, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateAdToNode)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ad_isocode (ad_id, isocode) VALUES"
        "(1, 'AA');"
        "INSERT INTO addr (addr_id, node_id, ad_id) VALUES"
        "(1, 1, 1);"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("addr", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("node", 1, {"AA"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateGeomToNode)
{
    applyQueryToSchema(
        SCHEMA,
        // face -> ft: ft#1 (AA)
        "INSERT INTO ft_face (ft_id, face_id, is_interior) VALUES"
        "(1, 1, false);"
        "INSERT INTO face (face_id) VALUES"
        "(1);"
        "INSERT INTO face_isocode (face_id, isocode) VALUES"
        "(1, 'AA');"

        // edge -> ft: ft#1 (AA, BB)
        "INSERT INTO ft_edge (ft_id, edge_id) VALUES"
        "(1, 1);"
        "INSERT INTO edge_isocode (edge_id, isocode) VALUES"
        "(1, 'BB');"

        // node -> ft -> node: ft#1, node#1 (AA, BB, CC)
        "INSERT INTO ft_center (ft_id, node_id) VALUES"
        "(1, 1);"
        "INSERT INTO node_isocode (node_id, isocode) VALUES"
        "(1, 'CC');"

        // ft -> slave ft: ft#2 (AA, BB, CC)
        "INSERT INTO ft (ft_id, p_ft_id) VALUES"
        "(1, NULL),"
        "(2,    1);"
     );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA", "BB", "CC"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"AA", "BB", "CC"}));
    BOOST_CHECK(hasIsocodes("node", 1, {"AA", "BB", "CC"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFaceToModel3d)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO bld (bld_id, model3d_id) VALUES"
        "(1, 1);"
        "INSERT INTO bld_face (bld_id, face_id, is_interior) VALUES"
        "(1, 1, false);"
        "INSERT INTO face_isocode (face_id, isocode) VALUES"
        "(1, 'AA');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("model3d", 1, {"AA"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateRdElToRdJc)
{
    applyQueryToSchema(
        SCHEMA,
        // rd_el -> cond -> rd_el: rd_el#1 and rd_el#2 (AA, BB)
        "INSERT INTO cond (cond_id, cond_seq_id) VALUES"
        "(1, 1);"
        "INSERT INTO cond_rd_seq (cond_seq_id, rd_el_id, seq_num) VALUES"
        "(1, 1, 0),"
        "(1, 2, 1);"
        "INSERT INTO rd_el_isocode (rd_el_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"

        // rd_el -> rd_jc: rd_jc (AA, BB)
        "INSERT INTO rd_el (rd_el_id, f_rd_jc_id, t_rd_jc_id) VALUES"
        "(1, 1, 2);"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("rd_jc", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("rd_jc", 2, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateEdgeToNode)
{
    applyQueryToSchema(
        SCHEMA,
        // edge -> face -> edge: edges (AA, BB)
        "INSERT INTO face (face_id) VALUES"
        "(1),"
        "(2);"
        "INSERT INTO face_edge (face_id, edge_id) VALUES"
        "(1, 1),"
        "(1, 2);"
        "INSERT INTO edge_isocode (edge_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB');"

        // edge -> node: nodes (AA, BB)
        "INSERT INTO edge (edge_id, f_node_id, t_node_id) VALUES"
        "(1, 1, 2),"
        "(2, 3, 4);"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("edge", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("edge", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("node", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("node", 2, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("node", 3, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("node", 4, {"AA", "BB"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateEdgeToFtToEdge)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft (ft_id, p_ft_id) VALUES"
        "(1, NULL),"
        "(2, NULL);"
        "INSERT INTO ft_edge (ft_id, edge_id) VALUES"
        "(1, 1),"
        "(1, 2),"
        "(2, 2),"
        "(2, 3),"
        "(2, 4);"
        "INSERT INTO edge_isocode (edge_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'AA'),"
        "(3, 'BB'),"
        "(4, 'CC');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"AA", "BB", "CC"}));
    BOOST_CHECK(hasIsocodes("edge", 1, {"AA"}));
    BOOST_CHECK(hasIsocodes("edge", 2, {"AA", "BB", "CC"}));
    BOOST_CHECK(hasIsocodes("edge", 3, {"AA", "BB", "CC"}));
    BOOST_CHECK(hasIsocodes("edge", 4, {"AA", "BB", "CC"}));
}


BOOST_AUTO_TEST_CASE(shouldPropagateFaceToFtToFace)
{
    applyQueryToSchema(
        SCHEMA,
        "INSERT INTO ft (ft_id, p_ft_id) VALUES"
        "(1, NULL),"
        "(2, NULL);"
        "INSERT INTO ft_face (ft_id, face_id, is_interior) VALUES"
        "(1, 1, false),"
        "(1, 2, false),"
        "(2, 2, false),"
        "(2, 3, true),"
        "(2, 4, false);"
        "INSERT INTO face_isocode (face_id, isocode) VALUES"
        "(1, 'AA'),"
        "(2, 'BB'),"
        "(3, 'CC'),"
        "(4, 'DD');"
    );

    propagateGeometry(globalFixture().pool(), SCHEMA, THREADS_NUM, fail);

    BOOST_CHECK(hasIsocodes("ft", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("ft", 2, {"BB", "CC", "DD"}));

    BOOST_CHECK(hasIsocodes("face", 1, {"AA", "BB"}));
    BOOST_CHECK(hasIsocodes("face", 2, {"AA", "BB", "CC", "DD"}));
    BOOST_CHECK(hasIsocodes("face", 3, {"BB", "CC", "DD"}));
    BOOST_CHECK(hasIsocodes("face", 4, {"BB", "CC", "DD"}));
}


BOOST_AUTO_TEST_SUITE_END()

} // namespace tests
} // namespace wiki
} // namespace maps
