Rework migrations system and tag relationships

This commit is contained in:
2025-08-24 16:29:54 -04:00
parent 4d8e47e32a
commit df07023cfe
20 changed files with 335 additions and 107 deletions

4
.gitignore vendored
View File

@@ -35,5 +35,5 @@ read.lock
/IDHAN/include/hydrus_client_constants_gen.hpp
# Used for allowing easy setup of development environments for testing
/dev.sql
dev.sql
IDHANServer/src/db/dev.sql
IDHANServer/src/db/pre-dev.sql

View File

@@ -20,7 +20,3 @@ file(GLOB_RECURSE CPP_SOURCES CONFIGURE_DEPENDS
add_library(IDHANMigration STATIC ${MIGRATION_SOURCE} ${CPP_SOURCES})
target_include_directories(IDHANMigration PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(IDHANMigration PRIVATE pqxx spdlog::spdlog)
if (DEFINED ALLOW_TABLE_DESTRUCTION AND ALLOW_TABLE_DESTRUCTION)
target_compile_definitions(IDHANMigration PUBLIC ALLOW_TABLE_DESTRUCTION=1)
endif ()

View File

@@ -22,13 +22,4 @@ std::uint16_t getTableVersion( pqxx::nontransaction& tx, std::string_view name )
void addTableToInfo(
pqxx::nontransaction& tx, std::string_view name, std::string_view creation_query, std::size_t migration_id );
#ifdef ALLOW_TABLE_DESTRUCTION
void destroyTables( pqxx::nontransaction& );
#else
inline void destroyTables( [[maybe_unused]] pqxx::nontransaction& )
{
return;
}
#endif
} // namespace idhan::db

View File

@@ -5,4 +5,5 @@ CREATE TABLE idhan_info
queries TEXT[] NOT NULL
);
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

View File

@@ -6,6 +6,11 @@ AS
$$
BEGIN
IF EXISTS(SELECT 1 FROM tag_aliases ta WHERE COALESCE(ta.ideal_alias_id, ta.alias_id) = new.aliased_id AND ta.tag_domain_id = new.tag_domain_id AND ta.aliased_id <> COALESCE(new.ideal_alias_id, new.alias_id))
THEN
RAISE EXCEPTION 'Recursive alias detected during update';
END IF;
UPDATE tag_aliases
SET ideal_alias_id = COALESCE(new.ideal_alias_id, new.alias_id)
WHERE tag_aliases.alias_id = new.aliased_id

View File

@@ -5,6 +5,6 @@ CREATE TABLE active_tag_mappings_parents
origin_id BIGINT REFERENCES tags (tag_id) NOT NULL,
tag_domain_id SMALLINT REFERENCES tag_domains (tag_domain_id) NOT NULL,
internal BOOLEAN DEFAULT FALSE NOT NULL,
internal_count INTEGER DEFAULT 0,
internal_count INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY (record_id, tag_id, origin_id, tag_domain_id)
);

View File

@@ -9,9 +9,25 @@ BEGIN
WHERE tm.tag_domain_id = new.tag_domain_id
AND tm.tag_id = new.child_id;
INSERT INTO active_tag_mappings_parents (record_id, tag_id, origin_id, tag_domain_id, internal, internal_count)
SELECT DISTINCT atmp.record_id,
new.parent_id,
new.child_id,
new.tag_domain_id,
TRUE AS internal,
(SELECT COUNT(*)
FROM active_tag_mappings_parents atmp_count
WHERE atmp_count.tag_domain_id = new.tag_domain_id
AND atmp_count.tag_id = new.child_id
AND atmp_count.record_id = atmp.record_id) AS internal_count
FROM active_tag_mappings_parents atmp
WHERE atmp.tag_domain_id = new.tag_domain_id
AND atmp.tag_id = new.child_id
ON CONFLICT (record_id, tag_id, origin_id, tag_domain_id) DO UPDATE SET internal_count = excluded.internal_count + 1;
RETURN new;
END;
$$ LANGUAGE plpgsql;
$$ LANGUAGE plpgsql VOLATILE;
-- Create the trigger
CREATE TRIGGER trg_insert_active_tag_mapping_parent
@@ -20,7 +36,6 @@ CREATE TRIGGER trg_insert_active_tag_mapping_parent
FOR EACH ROW
EXECUTE FUNCTION insert_active_tag_mapping_parent();
CREATE OR REPLACE FUNCTION delete_active_tag_mapping_parent()
RETURNS TRIGGER AS
$$
@@ -34,7 +49,7 @@ BEGIN
RETURN old;
END;
$$ LANGUAGE plpgsql;
$$ LANGUAGE plpgsql VOLATILE;
-- Create the trigger for deletion
CREATE TRIGGER trg_delete_active_tag_mapping_parent
@@ -47,15 +62,24 @@ CREATE OR REPLACE FUNCTION insert_active_tag_mappings_parents_from_mappings()
RETURNS TRIGGER AS
$$
BEGIN
INSERT INTO active_tag_mappings_parents (record_id, tag_id, origin_id, tag_domain_id)
SELECT new.record_id, parent_id, new.tag_id, new.tag_domain_id
-- whenever a mapping is inserted into the active mappings
-- insert it's parents as well
INSERT INTO active_tag_mappings_parents (record_id, tag_id, origin_id, tag_domain_id, internal, internal_count)
SELECT new.record_id,
parent_id,
new.tag_id,
new.tag_domain_id,
FALSE AS internal,
0 AS internal_count
FROM tag_parents tp
WHERE tp.tag_domain_id = new.tag_domain_id
AND tp.child_id = new.tag_id;
AND tp.child_id = new.tag_id
ON CONFLICT (record_id, tag_id, origin_id, tag_domain_id) DO UPDATE SET internal = FALSE;
RETURN new;
END;
$$ LANGUAGE plpgsql;
$$ LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER trg_insert_active_tag_mappings_parents_from_mappings
AFTER INSERT
@@ -63,6 +87,39 @@ CREATE TRIGGER trg_insert_active_tag_mappings_parents_from_mappings
FOR EACH ROW
EXECUTE FUNCTION insert_active_tag_mappings_parents_from_mappings();
CREATE OR REPLACE FUNCTION delete_active_tag_mappings_parents_from_mappings()
RETURNS TRIGGER AS
$$
BEGIN
UPDATE active_tag_mappings_parents
SET internal = TRUE
WHERE record_id = old.record_id
AND origin_id = old.tag_id
AND tag_domain_id = old.tag_domain_id
AND internal_count > 0;
-- When a mapping is deleted from active_tag_mappings
-- remove its corresponding parent mappings
DELETE
FROM active_tag_mappings_parents
WHERE record_id = old.record_id
AND origin_id = old.tag_id
AND tag_domain_id = old.tag_domain_id
AND NOT internal;
RETURN old;
END;
$$ LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER trg_delete_active_tag_mappings_parents_from_mappings
AFTER DELETE
ON active_tag_mappings
FOR EACH ROW
EXECUTE FUNCTION delete_active_tag_mappings_parents_from_mappings();
INSERT INTO active_tag_mappings_parents (record_id, tag_id, origin_id, tag_domain_id)
SELECT tm.record_id, tp.parent_id, tp.child_id, tp.tag_domain_id
FROM active_tag_mappings tm
@@ -80,7 +137,7 @@ BEGIN
RETURN new;
END;
$$ LANGUAGE plpgsql;
$$ LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER trg_intercept_active_tag_mappings_parents
BEFORE INSERT

View File

@@ -7,10 +7,53 @@ CREATE TABLE tag_counts
PRIMARY KEY (tag_id, tag_domain_id)
);
CREATE TABLE total_tag_counts
(
tag_id BIGINT REFERENCES tags (tag_id),
storage_count INTEGER NOT NULL DEFAULT 0,
display_count INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (tag_id)
);
CREATE OR REPLACE FUNCTION update_tag_counts(target_tag_id BIGINT) RETURNS VOID AS
$$
BEGIN
-- Lock the tag_counts table to prevent concurrent updates
LOCK TABLE tag_counts IN EXCLUSIVE MODE;
-- Update/Insert counts using a single UPSERT operation
INSERT INTO total_tag_counts (tag_id, storage_count, display_count)
SELECT target_tag_id,
COUNT(DISTINCT record_id) FILTER (WHERE atm.tag_id = target_tag_id) AS storage_count,
COUNT(DISTINCT record_id) FILTER (WHERE COALESCE(atm.ideal_tag_id, atm.tag_id) = target_tag_id) AS display_count
FROM active_tag_mappings atm
WHERE atm.tag_id = target_tag_id
OR COALESCE(atm.ideal_tag_id, atm.tag_id) = target_tag_id
ON CONFLICT (tag_id) DO UPDATE
SET storage_count = excluded.storage_count,
display_count = excluded.display_count;
-- Update/Insert counts per domain using a single UPSERT operation
INSERT INTO tag_counts (tag_id, tag_domain_id, storage_count, display_count)
SELECT target_tag_id AS tag_id,
atm.tag_domain_id AS tag_domain_id,
COUNT(DISTINCT record_id) FILTER (WHERE atm.tag_id = target_tag_id) AS storage_count,
COUNT(DISTINCT record_id) FILTER (WHERE COALESCE(atm.ideal_tag_id, atm.tag_id) = target_tag_id) AS display_count
FROM active_tag_mappings atm
WHERE (atm.tag_id = target_tag_id OR COALESCE(atm.ideal_tag_id, atm.tag_id) = target_tag_id)
GROUP BY atm.tag_domain_id
ON CONFLICT (tag_id, tag_domain_id) DO UPDATE
SET storage_count = excluded.storage_count,
display_count = excluded.display_count;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION add_count(tag_id_i BIGINT, ideal_tag_id_i BIGINT, tag_domain_id_i SMALLINT) RETURNS VOID AS
$$
BEGIN
LOCK TABLE tag_counts IN EXCLUSIVE MODE;
IF tag_id_i IS NOT NULL THEN
INSERT INTO tag_counts (tag_id, tag_domain_id, storage_count)
VALUES (tag_id_i, tag_domain_id_i, 1)
@@ -28,6 +71,9 @@ $$ LANGUAGE plpgsql;
CREATE FUNCTION remove_count(tag_id_i BIGINT, ideal_tag_id_i BIGINT, tag_domain_id_i SMALLINT) RETURNS VOID AS
$$
BEGIN
LOCK TABLE tag_counts IN EXCLUSIVE MODE;
UPDATE tag_counts SET storage_count = storage_count - 1 WHERE tag_id = tag_id_i AND tag_domain_id = tag_domain_id_i;
UPDATE tag_counts SET display_count = display_count - 1 WHERE tag_id = COALESCE(ideal_tag_id_i, tag_id_i) AND tag_domain_id = tag_domain_id_i;
END;

View File

@@ -5,41 +5,25 @@ CREATE OR REPLACE FUNCTION atmp_internal_on_insert()
RETURNS trigger
AS
$$
DECLARE
row RECORD;
BEGIN
-- FOR new IN SELECT * FROM new_rows
-- LOOP
-- Process each new row here
RAISE NOTICE 'Inserted: %', new;
RAISE NOTICE 'Count: %', (SELECT COUNT(*) FROM tag_parents tp2);
FOR row IN SELECT * FROM tag_parents tp2
LOOP
RAISE NOTICE 'TP Row: %', row;
END LOOP;
-- INSERT INTO active_tag_mappings_parents (record_id, tag_id, origin_id, tag_domain_id)
INSERT INTO active_tag_mappings_parents (record_id, tag_id, origin_id, tag_domain_id, internal, internal_count)
SELECT new.record_id AS record_id,
COALESCE(tp.ideal_parent_id, tp.parent_id) AS tag_id,
new.tag_id AS origin_id,
new.tag_domain_id AS tag_domain_id
INTO row
new.tag_domain_id AS tag_domain_id,
TRUE AS internal,
1 AS internal_count
FROM tag_parents tp
WHERE COALESCE(tp.ideal_child_id, tp.child_id) = new.tag_id
AND tp.tag_domain_id = new.tag_domain_id
LIMIT 1;
RAISE NOTICE 'INSERT: %', row;
-- END LOOP;
ON CONFLICT (record_id, tag_id, origin_id, tag_domain_id) DO UPDATE SET internal_count = excluded.internal_count + 1;
-- if internal is true and there is an internal count, that means there are only internal tags
-- if internal is false but the count is non-zero, that means we have a direct parent
RETURN new;
END;
$$ LANGUAGE plpgsql;
$$ LANGUAGE plpgsql VOLATILE;
-- =========================
-- Delete trigger function
@@ -50,10 +34,18 @@ AS
$$
BEGIN
UPDATE active_tag_mappings_parents
SET internal_count = internal_count - 1
WHERE internal_count > 0
AND record_id = old.record_id
AND origin_id = old.tag_id
AND tag_domain_id = old.tag_domain_id;
DELETE FROM active_tag_mappings_parents WHERE internal AND internal_count = 0;
RETURN new;
END;
$$ LANGUAGE plpgsql;
$$ LANGUAGE plpgsql VOLATILE;
-- =========================
-- Triggers

View File

@@ -59,26 +59,4 @@ void addTableToInfo(
params );
}
#ifdef ALLOW_TABLE_DESTRUCTION
void destroyTables( pqxx::nontransaction& tx )
{
// log::critical(
// "We are about to drop the public schema since we are compiling with ALLOW_TABLE_DESTRUCTION! This will happen in 5 seconds. QUIT NOW IF YOU DON'T WANT THIS TO HAPPEN" );
// std::this_thread::sleep_for( std::chrono::seconds( 5 ) );
if ( !tx.exec( "SELECT schema_name FROM information_schema.schemata WHERE schema_name = \'public\'" ).empty() )
{
tx.exec( "DROP SCHEMA public CASCADE" );
}
else
{
spdlog::debug( "Public schema does not exist. Skipping drop." );
}
tx.exec( "CREATE SCHEMA public" );
}
#endif
} // namespace idhan::db

View File

@@ -3,7 +3,6 @@ project(IDHANServer LANGUAGES CXX C)
AddFGLExecutable(IDHANServer ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_sources(IDHANServer PRIVATE ${MIGRATION_SOURCE})
# Gui is needed for QImage for whatever reason
@@ -22,10 +21,6 @@ target_link_libraries(IDHANServer PRIVATE IDHAN)
target_link_libraries(IDHANServer PUBLIC IDHANModules IDHANMigration)
target_compile_definitions(IDHANServer PUBLIC IDHAN_USE_STD_FORMAT)
if (DEFINED ALLOW_TABLE_DESTRUCTION AND ALLOW_TABLE_DESTRUCTION)
target_compile_definitions(IDHANServer PUBLIC ALLOW_TABLE_DESTRUCTION=1)
endif ()
# Copy page info to the output directory
file(GLOB_RECURSE PAGE_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/static/*")

View File

@@ -16,21 +16,28 @@ drogon::Task< Json::Value >
const auto wrapped_search_value { '%' + search_value + '%' };
constexpr std::size_t max_limit { 32 };
if ( limit > max_limit )
{
log::warn( "Tag search came in with absurdly high limit (was {}, clamped to {})", limit, max_limit );
}
const auto result { co_await db->execSqlCoro(
R"(
SELECT *,
similarity(tag_text, $2) AS similarity,
tag_text = $2 AS exact,
similarity(tag_text, $2) * coalesce(display_count, 1) AS score,
tag_counts.display_count AS count
COALESCE(tc.display_count, 0) AS count
FROM tags_combined
JOIN tag_counts USING (tag_id)
LEFT JOIN total_tag_counts tc USING (tag_id)
WHERE tag_text LIKE $1
ORDER BY exact DESC, score DESC, similarity DESC
limit $3)",
wrapped_search_value,
search_value,
limit ) };
std::min( limit, max_limit ) ) };
Json::Value root {};

View File

@@ -12,13 +12,6 @@
namespace idhan
{
void ManagementConnection::initalSetup( pqxx::nontransaction& tx )
{
log::info( "Starting inital table setup" );
tx.commit();
}
ManagementConnection::ManagementConnection( const ConnectionArguments& arguments ) : connection( arguments.format() )
{
log::info( "Postgres connection made: {}", connection.dbname() );
@@ -37,7 +30,6 @@ ManagementConnection::ManagementConnection( const ConnectionArguments& arguments
{
tx.exec( "CREATE SCHEMA IF NOT EXISTS public" );
constexpr std::string_view schema { "public" };
db::destroyTables( tx );
db::updateMigrations( tx, schema );
}

View File

@@ -20,8 +20,6 @@ class ManagementConnection
{
pqxx::connection connection;
void initalSetup( pqxx::nontransaction& nontransaction );
public:
inline pqxx::connection& conn() { return connection; }

View File

@@ -4,29 +4,29 @@
#include "MappingFixture.hpp"
void MappingFixture::createMapping( TagID tag_id )
void MappingFixture::createMapping( TagID tag_id, const RecordID record_id )
{
pqxx::work tx { *conn };
tx.exec_params( "INSERT INTO tag_mappings (tag_id, record_id, tag_domain_id) VALUES ($1, $2, $3)", pqxx::params { tag_id, default_record_id, default_domain_id } );
tx.exec_params( "INSERT INTO tag_mappings (tag_id, record_id, tag_domain_id) VALUES ($1, $2, $3)", pqxx::params { tag_id, record_id, default_domain_id } );
tx.commit();
}
void MappingFixture::deleteMapping( TagID tag_id )
void MappingFixture::deleteMapping( TagID tag_id, const RecordID record_id )
{
pqxx::work tx { *conn };
tx.exec_params( "DELETE FROM tag_mappings WHERE tag_id = $1 AND record_id = $2 AND tag_domain_id = $3", pqxx::params { tag_id, default_record_id, default_domain_id } );
tx.exec_params( "DELETE FROM tag_mappings WHERE tag_id = $1 AND record_id = $2 AND tag_domain_id = $3", pqxx::params { tag_id, record_id, default_domain_id } );
tx.commit();
}
bool MappingFixture::mappingExists( TagID tag_id )
bool MappingFixture::mappingExists( TagID tag_id, const RecordID record_id )
{
pqxx::work tx { *conn };
const auto result { tx.exec_params( "SELECT EXISTS(SELECT 1 FROM tag_mappings WHERE tag_id = $1 AND record_id = $2 AND tag_domain_id = $3)", pqxx::params { tag_id, default_record_id, default_domain_id } ) };
const auto result { tx.exec_params( "SELECT EXISTS(SELECT 1 FROM tag_mappings WHERE tag_id = $1 AND record_id = $2 AND tag_domain_id = $3)", pqxx::params { tag_id, record_id, default_domain_id } ) };
tx.commit();
@@ -39,16 +39,16 @@ RecordID MappingFixture::createRecord( const std::string_view data )
const auto result { tx.exec_params( "INSERT INTO records (sha256) VALUES (digest($1, 'sha256')) RETURNING record_id", pqxx::params { data } ) };
tx.commit();
if ( result.empty() ) throw std::runtime_error( "Failed to create record" );
tx.exec_params( "INSERT INTO file_info (size, record_id) VALUES ($1, $2)", 1, result[ 0 ][ 0 ].as< RecordID >() );
tx.commit();
return result[ 0 ][ 0 ].as< RecordID >();
}
void MappingFixture::SetUp()
{
ServerTagFixture::SetUp();
default_record_id = createRecord( "test" );
}

View File

@@ -10,9 +10,9 @@ class MappingFixture : public ServerTagFixture
protected:
void createMapping( TagID tag_id );
void deleteMapping( TagID tag_id );
bool mappingExists( TagID tag_id );
void createMapping( TagID tag_id, RecordID record_id );
void deleteMapping( TagID tag_id, RecordID record_id );
bool mappingExists( TagID tag_id, RecordID record_id );
RecordID createRecord( const std::string_view data );
void SetUp() override;

View File

@@ -4,7 +4,9 @@
#include "ServerTagFixture.hpp"
#include "MappingFixture.hpp"
#include "ServerDBFixture.hpp"
#include "logging/format_ns.hpp"
#include "migrations.hpp"
#include "splitTag.hpp"
@@ -73,4 +75,129 @@ bool ServerTagFixture::aliasExists( const TagID aliased_id, const TagID alias_id
tx.commit();
return result[ 0 ][ 0 ].as< bool >();
}
void ServerTagFixture::createParent( TagID parent_id, TagID child_id )
{
if ( !conn ) throw std::runtime_error( "Connection was nullptr" );
pqxx::work tx { *conn };
tx.exec_params( "INSERT INTO tag_parents (parent_id, child_id, tag_domain_id) VALUES ($1, $2, $3)", pqxx::params { parent_id, child_id, default_domain_id } );
tx.commit();
return;
}
bool ServerTagFixture::parentExists( TagID parent_id, TagID child_id )
{
if ( !conn ) throw std::runtime_error( "Connection was nullptr" );
pqxx::work tx { *conn };
const auto result { tx.exec_params( "SELECT EXISTS(SELECT 1 FROM tag_parents WHERE parent_id = $1 AND child_id = $2 AND tag_domain_id = $3)", pqxx::params { parent_id, child_id, default_domain_id } ) };
return result[ 0 ][ 0 ].as< bool >();
}
testing::AssertionResult dumpParents( testing::AssertionResult result, pqxx::work& tx )
{
const auto table_printout { tx.exec( "SELECT * FROM tag_parents" ) };
result << "Parents (tag_parents):\n";
result << "(parent_id, child_id, ideal_parent_id, ideal_child_id)\n";
for ( const auto& row : table_printout )
{
const auto parent_id { row[ "parent_id" ].as< TagID >() };
const auto child_id { row[ "child_id" ].as< TagID >() };
const auto is_parent_ideal { !row[ "ideal_parent_id" ].is_null() };
const auto is_child_ideal { !row[ "ideal_child_id" ].is_null() };
if ( is_parent_ideal && is_child_ideal )
{
result << format_ns::format( "({}, {}, {}, {})\n", parent_id, child_id, row[ "ideal_parent_id" ].as< TagID >(), row[ "ideal_child_id" ].as< TagID >() );
}
else if ( is_parent_ideal )
{
result << format_ns::format( "({}, {}, {}, NULL)\n", parent_id, child_id, row[ "ideal_parent_id" ].as< TagID >() );
}
else if ( is_child_ideal )
{
result << format_ns::format( "({}, {}, NULL, {})\n", parent_id, child_id, row[ "ideal_child_id" ].as< TagID >() );
}
else
{
result << format_ns::format( "({}, {}, NULL, NULL)\n", parent_id, child_id );
}
}
return result;
}
testing::AssertionResult dumpMappings( testing::AssertionResult result, pqxx::work& tx )
{
const auto table_printout { tx.exec( "SELECT * FROM active_tag_mappings" ) };
result << "Mappings (active_tag_mappings):\n";
result << "(record_id, tag_id, tag_domain_id, ideal_tag_id)\n";
for ( const auto& row : table_printout )
{
const auto record_id { row[ "record_id" ].as< RecordID >() };
const auto tag_id { row[ "tag_id" ].as< TagID >() };
const auto tag_domain_id { row[ "tag_domain_id" ].as< TagDomainID >() };
const bool is_ideal { !row[ "ideal_tag_id" ].is_null() };
if ( is_ideal )
result << format_ns::format( "({}, {}, {}, {})\n", record_id, tag_id, tag_domain_id, row[ "ideal_tag_id" ].as< TagID >() );
else
result << format_ns::format( "({}, {}, {}, NULL)\n", record_id, tag_id, tag_domain_id );
}
if ( table_printout.size() == 0 )
result << "\t\t\tNo rows found\n";
return result;
}
testing::AssertionResult dumpParentMappings( testing::AssertionResult result, pqxx::work& tx )
{
result = dumpMappings( result, tx );
result = dumpParents( result, tx );
const auto table_printout { tx.exec( "SELECT * FROM active_tag_mappings_parents" ) };
result << "Parent mappings (active_tag_mappings_parents):\n";
result << "(record_id, tag_id, origin_id, tag_domain_id, internal, internal_count)\n";
for ( const auto& row : table_printout )
{
const auto record_id { row[ "record_id" ].as< RecordID >() };
const auto tag_id { row[ "tag_id" ].as< TagID >() };
const auto origin_id { row[ "origin_id" ].as< TagID >() };
const auto tag_domain_id { row[ "tag_domain_id" ].as< TagDomainID >() };
const bool internal { row[ "internal" ].as< bool >() };
const auto internal_count { row[ "internal_count" ].as< std::uint32_t >() };
result << format_ns::format( "({}, {}, {}, {}, {}, {})\n", record_id, tag_id, origin_id, tag_domain_id, internal, internal_count );
}
if ( table_printout.size() == 0 )
result << "\t\t\tNo rows found\n";
return result;
}
testing::AssertionResult ServerTagFixture::parentInternalExists( const RecordID record_id, TagID parent_id, TagID child_id, std::uint32_t count )
{
if ( !conn ) throw std::runtime_error( "Connection was nullptr" );
pqxx::work tx { *conn };
const auto result { tx.exec_params( "SELECT EXISTS(SELECT 1 FROM active_tag_mappings_parents WHERE record_id = $5 AND tag_id = $1 AND origin_id = $2 AND tag_domain_id = $3 AND internal_count = $4)", pqxx::params { parent_id, child_id, default_domain_id, count, record_id } ) };
if ( result[ 0 ][ 0 ].as< bool >() == false )
{
return dumpParentMappings( testing::AssertionFailure(), tx );
}
return testing::AssertionSuccess();
}

View File

@@ -25,5 +25,11 @@ class ServerTagFixture : public ServerDBFixture
bool aliasExists( TagID aliased_id, TagID alias_id );
void createParent( TagID parent_id, TagID child_id );
bool parentExists( TagID parent_id, TagID child_id );
testing::AssertionResult parentInternalExists( RecordID record_id, TagID parent_id, TagID child_id, std::uint32_t count = 1 );
TagDomainID default_domain_id { 0 };
};

View File

@@ -9,11 +9,13 @@ TEST_F( MappingFixture, StorageMapping )
const auto tag_1 { createTag( "tag:1" ) };
const auto tag_2 { createTag( "tag:2" ) };
createMapping( tag_1 );
createMapping( tag_2 );
const auto record { createRecord( "record" ) };
ASSERT_TRUE( mappingExists( tag_1 ) );
ASSERT_TRUE( mappingExists( tag_2 ) );
createMapping( tag_1, record );
createMapping( tag_2, record );
ASSERT_TRUE( mappingExists( tag_1, record ) );
ASSERT_TRUE( mappingExists( tag_2, record ) );
SUCCEED();
}

View File

@@ -0,0 +1,35 @@
#include "MappingFixture.hpp"
#include "ServerTagFixture.hpp"
TEST_F( MappingFixture, TagParentCreation )
{
const auto tag_ahri { createTag( "ahri (league of legends)" ) };
const auto tag_kogmaw { createTag( "kogmaw (league of legends)" ) };
const auto tag_league { createTag( "series:league of legends" ) };
const auto tag_riot_games { createTag( "copyright:riot games" ) };
// test adding to existing
const auto record_1 { createRecord( "record_1" ) };
createMapping( tag_ahri, record_1 );
createMapping( tag_kogmaw, record_1 );
createParent( tag_league, tag_ahri );
createParent( tag_league, tag_kogmaw );
createParent( tag_riot_games, tag_league );
ASSERT_TRUE( parentExists( tag_league, tag_ahri ) );
ASSERT_TRUE( parentExists( tag_league, tag_kogmaw ) );
// test adding to new mappings
const auto record_2 { createRecord( "record_2" ) };
createMapping( tag_ahri, record_2 );
createMapping( tag_kogmaw, record_2 );
ASSERT_TRUE( parentInternalExists( record_1, tag_riot_games, tag_league, 2 ) );
ASSERT_TRUE( parentInternalExists( record_2, tag_riot_games, tag_league, 2 ) );
}