diff --git a/src/index/IndexBuilderMain.cpp b/src/index/IndexBuilderMain.cpp index 5b27bf3d72..25def72bdb 100644 --- a/src/index/IndexBuilderMain.cpp +++ b/src/index/IndexBuilderMain.cpp @@ -20,6 +20,7 @@ #include "libqlever/Qlever.h" #include "util/ProgramOptionsHelpers.h" #include "util/ReadableNumberFacet.h" +#include "util/json.h" using std::string; @@ -142,6 +143,36 @@ auto getFileSpecifications = [](const auto& filetype, auto& inputFile, return fileSpecs; }; +// Helper to convert the JSON given for writing materialized views to a proper +// `WriteMaterializedViews` vector. +qlever::IndexBuilderConfig::WriteMaterializedViews parseMaterializedViewsJson( + std::string materializedViewsJson) { + qlever::IndexBuilderConfig::WriteMaterializedViews views; + if (!materializedViewsJson.empty()) { + AD_LOG_INFO << "Writing materialized views..." << std::endl; + try { + auto viewsJson = nlohmann::json::parse(materializedViewsJson); + if (!viewsJson.is_object()) { + throw std::runtime_error( + "The --write-materialized-views option must be a JSON object " + "mapping view names to SPARQL queries."); + } + for (auto& [viewName, query] : viewsJson.items()) { + if (!query.is_string()) { + throw std::runtime_error(absl::StrCat( + "Query for materialized view '", viewName, + "' must be a string, but got type: ", jsonToTypeString(query))); + } + views.push_back({std::move(viewName), query.get()}); + } + } catch (const nlohmann::json::exception& e) { + throw std::runtime_error( + absl::StrCat("Failed to parse materialized views JSON: ", e.what())); + } + } + return views; +} + // Main function. int main(int argc, char** argv) { // Copy the git hash and datetime of compilation (which require relinking) @@ -159,6 +190,7 @@ int main(int argc, char** argv) { std::vector inputFile; std::vector defaultGraphs; std::vector parseParallel; + std::string materializedViewsJson; boost::program_options::options_description boostOptions( "Options for qlever-index"); @@ -251,6 +283,10 @@ int main(int argc, char** argv) { "large enough to hold a single input triple. Default: 10 MB."); add("keep-temporary-files,k", po::bool_switch(&config.keepTemporaryFiles_), "Do not delete temporary files from index creation for debugging."); + add("write-materialized-views", po::value(&materializedViewsJson), + "Write materialized views after index building. Takes a JSON object " + "mapping view names to SELECT queries for writing the view, for example: " + R"({"view1": "SELECT ...", "view2": "SELECT ..."})"); // Process command line arguments. po::variables_map optionsMap; @@ -280,9 +316,10 @@ int main(int argc, char** argv) { try { config.inputFiles_ = getFileSpecifications(filetype, inputFile, defaultGraphs, parseParallel); + config.writeMaterializedViews_ = + parseMaterializedViewsJson(std::move(materializedViewsJson)); config.validate(); qlever::Qlever::buildIndex(config); - } catch (std::exception& e) { AD_LOG_ERROR << "Creating the index for QLever failed with the following " "exception: " diff --git a/src/libqlever/Qlever.cpp b/src/libqlever/Qlever.cpp index a29fb9bcfa..7cf2b21eea 100644 --- a/src/libqlever/Qlever.cpp +++ b/src/libqlever/Qlever.cpp @@ -105,6 +105,18 @@ void Qlever::buildIndex(IndexBuilderConfig config) { "version of QLever"); #endif } + + // Build materialized views if requested. + if (!config.writeMaterializedViews_.empty()) { + AD_LOG_INFO + << "Loading the new index to execute materialized view write queries..." + << std::endl; + Qlever engine{EngineConfig{config}}; + for (auto& [viewName, query] : config.writeMaterializedViews_) { + engine.writeMaterializedView(viewName, query); + } + AD_LOG_INFO << "All materialized views written successfully." << std::endl; + } } // ___________________________________________________________________________ diff --git a/src/libqlever/Qlever.h b/src/libqlever/Qlever.h index 91207a97ca..3a2b63cb2c 100644 --- a/src/libqlever/Qlever.h +++ b/src/libqlever/Qlever.h @@ -137,6 +137,11 @@ struct IndexBuilderConfig : CommonConfig { float bScoringParam_ = 0.75; float kScoringParam_ = 1.75; + // Materialized views to be written after normal index build is complete. + using WriteMaterializedViews = + std::vector>; + WriteMaterializedViews writeMaterializedViews_; + // Assert that the given configuration is valid. void validate() const; diff --git a/test/libqlever/QleverTest.cpp b/test/libqlever/QleverTest.cpp index 57fa4f797a..4f2b2a8ece 100644 --- a/test/libqlever/QleverTest.cpp +++ b/test/libqlever/QleverTest.cpp @@ -36,7 +36,12 @@ TEST(LibQlever, buildIndexAndRunQuery) { ::testing::HasSubstr("buffer size")); c.parserBufferSize_ = std::nullopt; + + // Test materialized views to be written at index build time. + c.writeMaterializedViews_ = {{"demoView", "SELECT ?s { ?s

}"}}; + EXPECT_NO_THROW(Qlever::buildIndex(c)); + { EngineConfig ec{c}; Qlever engine{ec}; @@ -78,6 +83,9 @@ TEST(LibQlever, buildIndexAndRunQuery) { engine.clearNamedResultCache(); AD_EXPECT_THROW_WITH_MESSAGE(engine.query(serviceQuery), notPinned); AD_EXPECT_THROW_WITH_MESSAGE(engine.query(serviceQuery2), notPinned); + + // Test that the requested materialized view exists. + EXPECT_NO_THROW(engine.loadMaterializedView("demoView")); } #ifndef QLEVER_REDUCED_FEATURE_SET_FOR_CPP17