Rework migrations system and tag relationships
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/*")
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ class ManagementConnection
|
||||
{
|
||||
pqxx::connection connection;
|
||||
|
||||
void initalSetup( pqxx::nontransaction& nontransaction );
|
||||
|
||||
public:
|
||||
|
||||
inline pqxx::connection& conn() { return connection; }
|
||||
|
||||
@@ -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" );
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
35
tests/src/server/tags/parents.cpp
Normal file
35
tests/src/server/tags/parents.cpp
Normal 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 ) );
|
||||
}
|
||||
Reference in New Issue
Block a user