File indexing completed on 2024-05-19 05:42:12

0001 // ct_lvtmdb_soci_writer.h                                      -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtmdb_objectstore.h>
0021 #include <ct_lvtmdb_soci_helper.h>
0022 #include <ct_lvtmdb_soci_writer.h>
0023 
0024 #include <soci/soci-backend.h>
0025 #include <soci/sqlite3/soci-sqlite3.h>
0026 
0027 #include <ct_lvtmdb_componentobject.h>
0028 #include <ct_lvtmdb_errorobject.h>
0029 #include <ct_lvtmdb_fieldobject.h>
0030 #include <ct_lvtmdb_fileobject.h>
0031 #include <ct_lvtmdb_functionobject.h>
0032 #include <ct_lvtmdb_methodobject.h>
0033 #include <ct_lvtmdb_namespaceobject.h>
0034 #include <ct_lvtmdb_packageobject.h>
0035 #include <ct_lvtmdb_repositoryobject.h>
0036 #include <ct_lvtmdb_typeobject.h>
0037 #include <ct_lvtmdb_variableobject.h>
0038 
0039 #include <filesystem>
0040 #include <fstream>
0041 #include <iostream>
0042 #include <optional>
0043 
0044 #include <QCoreApplication> // for applicationDirPath
0045 #include <QElapsedTimer>
0046 #include <QStandardPaths>
0047 
0048 #include <QDebug>
0049 #include <QFile>
0050 
0051 using namespace Codethink::lvtmdb;
0052 
0053 // must be called on the global namespace.
0054 void initialize_resources()
0055 {
0056     static bool initialized_resources = false;
0057     if (initialized_resources) {
0058         return;
0059     }
0060     Q_INIT_RESOURCE(databases);
0061     initialized_resources = true;
0062 }
0063 
0064 namespace {
0065 
0066 bool make_sure_file_exist(const std::filesystem::path& db_schema_path, const std::string& db_schema_id)
0067 {
0068     std::filesystem::path resulting_file = db_schema_path / db_schema_id;
0069 
0070 #if 0
0071 // Temporarely disable this check, as it causes problems such as not updating the
0072 // database spec when we need.
0073 // We need a new way so that users can say that they want to override the file.
0074     if (std::filesystem::exists(resulting_file)) {
0075         return true;
0076     }
0077 #endif
0078 
0079     QFile thisFile = QString::fromStdString(":/db/" + db_schema_id);
0080     if (!thisFile.exists()) {
0081         qDebug() << "FATAL Error, can't access resource file from Qt - use Gammaray to verify if names are correct";
0082         return false;
0083     }
0084 
0085     if (!std::filesystem::exists(db_schema_path)) {
0086         bool res = std::filesystem::create_directories(db_schema_path);
0087         if (!res) {
0088             qDebug() << "error, could not create data folder" << QString::fromStdString(db_schema_path.string());
0089             return false;
0090         }
0091     }
0092 
0093     if (std::filesystem::exists(resulting_file)) {
0094         try {
0095             std::filesystem::remove(resulting_file);
0096         } catch (std::exception& e) {
0097             // This only happens in windows. something keeps those files open and windows
0098             // can't delete them. but it's safe to assume that at least, we have a database to use.
0099             // so even if it's an exception, we can safely return true.
0100             std::cout << "Exception: " << e.what() << "\n";
0101             return true;
0102         }
0103     }
0104 
0105     bool res = thisFile.copy(QString::fromStdString(resulting_file.string())); // to the filesystem.
0106     if (!res) {
0107         qDebug() << "error, could not copy file to our internal filesystem" << thisFile.errorString();
0108         return false;
0109     }
0110     return true;
0111 }
0112 
0113 bool run_migration(soci::session& db, const std::string& db_schema_id)
0114 {
0115     initialize_resources();
0116     QStringList dataLocations;
0117     if (const char *env_p = std::getenv("DBSPEC_PATH")) {
0118         dataLocations << QString::fromStdString(env_p);
0119     }
0120     dataLocations << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0121 
0122     auto db_schema_path = [&dataLocations, &db_schema_id]() -> std::filesystem::path {
0123         for (const auto& currPath : qAsConst(dataLocations)) {
0124             auto db_schema_path = std::filesystem::path(currPath.toStdString());
0125             if (!make_sure_file_exist(db_schema_path, db_schema_id)) {
0126                 continue;
0127             }
0128             return db_schema_path / db_schema_id;
0129         }
0130 
0131         std::cerr << "Could not find db schema in all the searched paths.\n";
0132         return {};
0133     }();
0134     if (db_schema_path.empty()) {
0135         return false;
0136     }
0137 
0138     std::ifstream codebase_schema(db_schema_path);
0139     std::stringstream buffer;
0140     buffer << codebase_schema.rdbuf();
0141     QStringList result_schema = QString::fromStdString(buffer.str())
0142                                     .split(";",
0143 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
0144                                            Qt::SkipEmptyParts
0145 #else
0146                                            QString::SkipEmptyParts
0147 #endif
0148                                     );
0149 
0150     for (const QString& res : result_schema) {
0151         if (res.simplified().isEmpty()) {
0152             continue;
0153         }
0154 
0155         try {
0156             db << res.toStdString();
0157         } catch (const std::exception& e) {
0158             std::cout << e.what() << "\n";
0159             return false;
0160         }
0161     }
0162 
0163     return true;
0164 }
0165 
0166 std::optional<std::pair<int, soci::indicator>>
0167 query_id_from_qual_name(soci::session& db, const std::string& table_name, const std::string& qual_name)
0168 {
0169     assert(!table_name.empty());
0170 
0171     if (qual_name.empty()) {
0172         return std::nullopt;
0173     }
0174 
0175     int id = 0;
0176     soci::indicator ind = soci::indicator::i_null;
0177     std::string query = "select id from " + table_name + " where qualified_name = :name";
0178 
0179     db << query, soci::into(id, ind), soci::use(qual_name, "name");
0180 
0181     if (!db.got_data()) {
0182         return std::nullopt;
0183     }
0184 
0185     return std::make_pair(id, ind);
0186 }
0187 
0188 // Quick and dirty print macro - to be removed when we feel that this
0189 // code is stable enough.
0190 template<typename Obj>
0191 void print(int id, Obj *obj)
0192 {
0193     std::cout << "Writing" << id << " " << obj->name() << " " << obj->qualifiedName() << "\n";
0194 }
0195 
0196 template<typename DbObject, typename callable>
0197 std::pair<int, soci::indicator>
0198 get_or_add_thing(DbObject *dbobj, soci::session& db, const std::string& table_name, callable fn)
0199 {
0200     int id = 0;
0201     soci::indicator indicator = soci::indicator::i_null;
0202     if (!dbobj) {
0203         return {id, indicator};
0204     }
0205 
0206     auto lock = dbobj->readOnlyLock();
0207     (void) lock;
0208 
0209     // TODO:
0210     // This should never happen but we have a problem with repositories
0211     // that might be empty, currently.
0212     // so let's this for a while untill we fix that.'
0213     if (dbobj->name().empty() || dbobj->qualifiedName().empty()) {
0214         std::cout << "We are looking for something without name and qualified name.\n";
0215         std::cout << "returning an empty indicator / null item.\n";
0216         return {id, indicator};
0217     }
0218 
0219     std::optional<std::pair<int, soci::indicator>> res =
0220         query_id_from_qual_name(db, table_name, dbobj->qualifiedName());
0221 
0222     if (!res.has_value()) {
0223         fn(dbobj, db);
0224         res = query_id_from_qual_name(db, table_name, dbobj->qualifiedName());
0225     }
0226 
0227     // here it *needs* to have value.
0228     id = res.value().first;
0229     indicator = res.value().second;
0230 
0231     return std::make_pair(id, indicator);
0232 }
0233 
0234 void exportRepository(RepositoryObject *obj, soci::session& db)
0235 {
0236     auto lock = obj->readOnlyLock();
0237     if (obj->qualifiedName().empty()) {
0238         return;
0239     }
0240 
0241     if (query_id_from_qual_name(db, "source_repository", obj->qualifiedName()).has_value()) {
0242         return;
0243     }
0244 
0245     db << "insert into source_repository(version, name, qualified_name, disk_path) values (0, :name, :qual_name, "
0246           ":disk_path)",
0247         soci::use(obj->name()), soci::use(obj->qualifiedName()), soci::use(obj->diskPath());
0248 }
0249 
0250 void exportPackage(PackageObject *pkg, soci::session& db)
0251 {
0252     auto lock = pkg->readOnlyLock();
0253     (void) lock;
0254 
0255     if (query_id_from_qual_name(db, "source_package", pkg->qualifiedName()).has_value()) {
0256         return;
0257     }
0258 
0259     auto [parent_id, parent_ind] = get_or_add_thing(pkg->parent(), db, "source_package", exportPackage);
0260     auto [repository_id, repository_ind] =
0261         get_or_add_thing(pkg->repository(), db, "source_repository", exportRepository);
0262 
0263     db << "insert into source_package(version, parent_id, source_repository_id, name, qualified_name, disk_path)"
0264           " values (0, :parent_id, :repo_id, :name, :qual_name, :disk_path)",
0265         soci::use(parent_id, parent_ind), soci::use(repository_id, repository_ind), soci::use(pkg->name()),
0266         soci::use(pkg->qualifiedName()), soci::use(pkg->diskPath());
0267 }
0268 
0269 void exportComponent(ComponentObject *comp, soci::session& db)
0270 {
0271     auto lock = comp->readOnlyLock();
0272     (void) lock;
0273 
0274     if (query_id_from_qual_name(db, "source_component", comp->qualifiedName()).has_value()) {
0275         return;
0276     }
0277 
0278     auto [pkg_id, pkg_ind] = get_or_add_thing(comp->package(), db, "source_package", exportPackage);
0279 
0280     db << "insert into source_component(version, qualified_name, name, package_id)"
0281           " values (0, :qual_name, :name, :pkg_id)",
0282         soci::use(comp->qualifiedName()), soci::use(comp->name()), soci::use(pkg_id, pkg_ind);
0283 }
0284 
0285 void exportFile(FileObject *file, soci::session& db)
0286 {
0287     auto lock = file->readOnlyLock();
0288     (void) lock;
0289 
0290     if (query_id_from_qual_name(db, "source_file", file->qualifiedName()).has_value()) {
0291         return;
0292     }
0293 
0294     auto [pkg_id, pkg_ind] = get_or_add_thing(file->package(), db, "source_package", exportPackage);
0295     auto [component_id, component_ind] = get_or_add_thing(file->component(), db, "source_component", exportComponent);
0296     const int isHeader = file->isHeader() ? 1 : 0;
0297 
0298     db << "insert into source_file(version, package_id, component_id, name, qualified_name, is_header, hash)"
0299           "values(0, :pkg_id, :comp_id, :name, :qual_name, :is_header, :hash)",
0300         soci::use(pkg_id, pkg_ind), soci::use(component_id, component_ind), soci::use(file->name()),
0301         soci::use(file->qualifiedName()), soci::use(isHeader), soci::use(file->hash());
0302 }
0303 
0304 void exportError(ErrorObject *error, soci::session& db)
0305 {
0306     auto lock = error->readOnlyLock();
0307     (void) lock;
0308 
0309     // TODO: this is the *only* table that the `qualified_name` field is named `fully_qualified_name`.
0310     // if we can reword that, we can simplify this code.
0311     db << "select id from error_messages where fully_qualified_name = :name", soci::use(error->qualifiedName());
0312     if (db.got_data()) {
0313         return;
0314     }
0315 
0316     const int kind = static_cast<int>(error->errorKind());
0317     db << "insert into error_messages(version, error_kind, fully_qualified_name, error_message, file_name)"
0318           "values(0, :error_kind, :qual_name, :error_msg, :file)",
0319         soci::use(kind), soci::use(error->qualifiedName()), soci::use(error->errorMessage()),
0320         soci::use(error->fileName());
0321 }
0322 
0323 void exportNamespace(NamespaceObject *nmspc, soci::session& db)
0324 {
0325     auto lock = nmspc->readOnlyLock();
0326     (void) lock;
0327 
0328     if (query_id_from_qual_name(db, "namespace_declaration", nmspc->qualifiedName()).has_value()) {
0329         return;
0330     }
0331 
0332     auto [nspc_parent_id, nspc_parent_ind] =
0333         get_or_add_thing(nmspc->parent(), db, "namespace_declaration", exportNamespace);
0334 
0335     db << "insert into namespace_declaration(version, parent_id, name, qualified_name)"
0336           "values(0, :parent, :name, :qual_name)",
0337         soci::use(nspc_parent_id, nspc_parent_ind), soci::use(nmspc->name()), soci::use(nmspc->qualifiedName());
0338 }
0339 
0340 void exportVariable(VariableObject *var, soci::session& db)
0341 {
0342     auto lock = var->readOnlyLock();
0343     (void) lock;
0344 
0345     if (query_id_from_qual_name(db, "variable_declaration", var->qualifiedName()).has_value()) {
0346         return;
0347     }
0348 
0349     auto [nspc_id, nspc_ind] = get_or_add_thing(var->parent(), db, "namespace_declaration", exportNamespace);
0350     const int isGlobal = var->isGlobal() ? 1 : 0;
0351     db << "insert into variable_declaration(version, namespace_id, name, qualified_name, signature, is_global)"
0352           "values(0, :namespace_id, :name, :qual_name, :sig, :global)",
0353         soci::use(nspc_id, nspc_ind), soci::use(var->name()), soci::use(var->qualifiedName()),
0354         soci::use(var->signature()), soci::use(isGlobal);
0355 }
0356 
0357 void exportFunction(FunctionObject *fn, soci::session& db)
0358 {
0359     auto lock = fn->readOnlyLock();
0360     (void) lock;
0361 
0362     if (query_id_from_qual_name(db, "function_declaration", fn->qualifiedName()).has_value()) {
0363         return;
0364     }
0365     auto [nspc_id, nspc_ind] = get_or_add_thing(fn->parent(), db, "namespace_declaration", exportNamespace);
0366 
0367     db << "insert into function_declaration(version, namespace_id, name, "
0368           "qualified_name, signature, return_type, template_parameters)"
0369           "values(0, :namespace_id, :name, :qual_name, :signature, :return_type, :template_parameters)",
0370         soci::use(nspc_id, nspc_ind), soci::use(fn->name()), soci::use(fn->qualifiedName()), soci::use(fn->signature()),
0371         soci::use(fn->returnType()), soci::use(fn->templateParameters());
0372 }
0373 
0374 // Weird. the class_namespace_id is not a namespace at all, but this is
0375 // what we have on the database chema. it actually points as a declaration.
0376 // a better name would be parent_class or parent_udt.
0377 void exportUserDefinedType(TypeObject *type, soci::session& db)
0378 {
0379     auto lock = type->readOnlyLock();
0380     (void) lock;
0381 
0382     if (query_id_from_qual_name(db, "class_declaration", type->qualifiedName()).has_value()) {
0383         return;
0384     }
0385 
0386     auto [parent_namespace_id, parent_namespace_ind] =
0387         get_or_add_thing(type->parentNamespace(), db, "namespace_declaration", exportNamespace);
0388     auto [class_namespace_id, class_namespace_ind] =
0389         get_or_add_thing(type->parent(), db, "class_declaration", exportUserDefinedType);
0390     auto [parent_package_id, parent_package_ind] =
0391         get_or_add_thing(type->package(), db, "source_package", exportPackage);
0392 
0393     const int kind = static_cast<int>(type->kind());
0394     const int access = static_cast<int>(type->access());
0395 
0396     db << "insert into class_declaration(version, parent_namespace_id, class_namespace_id, parent_package_id, "
0397           " name, qualified_name, kind, access)"
0398           "values(0, :parent_namespace_id, :class_namespace_id, :parent_package_id, :name, :qual_name, :kind, :access)",
0399         soci::use(parent_namespace_id, parent_namespace_ind), soci::use(class_namespace_id, class_namespace_ind),
0400         soci::use(parent_package_id, parent_package_ind), soci::use(type->name()), soci::use(type->qualifiedName()),
0401         soci::use(kind), soci::use(access);
0402 }
0403 
0404 void exportField(FieldObject *field, soci::session& db)
0405 {
0406     auto lock = field->readOnlyLock();
0407     (void) lock;
0408 
0409     if (query_id_from_qual_name(db, "field_declaration", field->qualifiedName()).has_value()) {
0410         return;
0411     }
0412 
0413     auto [class_id, class_ind] = get_or_add_thing(field->parent(), db, "class_declaration", exportUserDefinedType);
0414     const int access = static_cast<int>(field->access());
0415     const int isStatic = field->isStatic() ? 1 : 0;
0416 
0417     db << "insert into field_declaration(version, class_id, name, qualified_name, signature, access, is_static)"
0418           "values(0, :class_id, :name, :qual_name, :signature, :access, :is_static)",
0419         soci::use(class_id, class_ind), soci::use(field->name()), soci::use(field->qualifiedName()),
0420         soci::use(field->signature()), soci::use(access), soci::use(isStatic);
0421 }
0422 
0423 void exportMethod(MethodObject *method, soci::session& db)
0424 {
0425     auto lock = method->readOnlyLock();
0426     (void) lock;
0427 
0428     if (query_id_from_qual_name(db, "method_declaration", method->qualifiedName()).has_value()) {
0429         return;
0430     }
0431 
0432     auto [class_id, class_ind] = get_or_add_thing(method->parent(), db, "class_declaration", exportUserDefinedType);
0433     const int access = static_cast<int>(method->access());
0434 
0435     const int isVirtual = method->isVirtual() ? 1 : 0;
0436     const int isPure = method->isPure() ? 1 : 0;
0437     const int isStatic = method->isStatic() ? 1 : 0;
0438     const int isConst = method->isConst() ? 1 : 0;
0439 
0440     db << "insert into method_declaration(version, class_id, name, qualified_name, "
0441           "signature, return_type, template_parameters, access, "
0442           "is_virtual, is_pure, is_static, is_const) values (0, "
0443           ":class_id, :name, :qual_name, :signature, :return_type, :template_params, :access, :is_virtual, :is_pure, "
0444           ":is_static, :is_const)",
0445         soci::use(class_id, class_ind), soci::use(method->name()), soci::use(method->qualifiedName()),
0446         soci::use(method->signature()), soci::use(method->returnType()), soci::use(method->templateParameters()),
0447         soci::use(access), soci::use(isVirtual), soci::use(isPure), soci::use(isStatic), soci::use(isConst);
0448 }
0449 
0450 void exportPkgRelations(PackageObject *pkg, soci::session& db)
0451 {
0452     auto lock = pkg->readOnlyLock();
0453     (void) lock;
0454 
0455     // dependencies. Here we assume that all package queries are correct and that there's no need
0456     // to check if the sql failed.
0457     const int this_id = query_id_from_qual_name(db, "source_package", pkg->qualifiedName()).value().first;
0458     int dep_id = 0;
0459 
0460     soci::statement insert_st =
0461         (db.prepare << "insert into dependencies(source_id, target_id) values (:s, :t) on conflict do nothing",
0462          soci::use(this_id),
0463          soci::use(dep_id));
0464 
0465     for (PackageObject *dep : pkg->forwardDependencies()) {
0466         auto _lock = dep->readOnlyLock();
0467         (void) _lock;
0468 
0469         dep_id = query_id_from_qual_name(db, "source_package", dep->qualifiedName()).value().first;
0470         insert_st.execute(true);
0471     }
0472 }
0473 
0474 void exportCompRelations(ComponentObject *comp, soci::session& db)
0475 {
0476     auto lock = comp->readOnlyLock();
0477     (void) lock;
0478 
0479     // types handled in exportUserDefinedTypeRelations
0480     // dependencies
0481     const int this_id = query_id_from_qual_name(db, "source_component", comp->qualifiedName()).value().first;
0482     int dep_id = 0;
0483 
0484     soci::statement insert_st =
0485         (db.prepare << "insert into component_relation(source_id, target_id) values (:s, :t) on conflict do nothing",
0486          soci::use(this_id),
0487          soci::use(dep_id));
0488 
0489     for (ComponentObject *dep : comp->forwardDependencies()) {
0490         auto _lock = dep->readOnlyLock();
0491         (void) _lock;
0492 
0493         dep_id = query_id_from_qual_name(db, "source_component", dep->qualifiedName()).value().first;
0494         insert_st.execute(true);
0495     }
0496 }
0497 
0498 void exportFileRelations(FileObject *file, soci::session& db)
0499 {
0500     auto lock = file->readOnlyLock();
0501     (void) lock;
0502 
0503     const int this_id = query_id_from_qual_name(db, "source_file", file->qualifiedName()).value().first;
0504     int other_source_id = 0;
0505     int namespace_id = 0;
0506 
0507     soci::statement other_source_insert =
0508         (db.prepare << "insert into includes(source_id, target_id) values (:s, :t) on conflict do nothing",
0509          soci::use(this_id),
0510          soci::use(other_source_id));
0511 
0512     soci::statement namespace_insert = (db.prepare << "insert into namespace_source_file(source_file_id, namespace_id) "
0513                                                       "values (:s, :t) on conflict do nothing",
0514                                         soci::use(this_id),
0515                                         soci::use(namespace_id));
0516 
0517     // includes
0518     for (FileObject *include : file->forwardIncludes()) {
0519         auto _lock = include->readOnlyLock();
0520         (void) _lock;
0521         other_source_id = query_id_from_qual_name(db, "source_file", include->qualifiedName()).value().first;
0522         other_source_insert.execute(true);
0523     }
0524 
0525     // namespaces
0526     for (NamespaceObject *nmspc : file->namespaces()) {
0527         auto _lock = nmspc->readOnlyLock();
0528         (void) _lock;
0529         namespace_id = query_id_from_qual_name(db, "namespace_declaration", nmspc->qualifiedName()).value().first;
0530         namespace_insert.execute(true);
0531     }
0532 
0533     // free functions
0534     for (FunctionObject *fnc : file->globalFunctions()) {
0535         auto _lock = fnc->readOnlyLock();
0536         (void) _lock;
0537 
0538         int dep_id = query_id_from_qual_name(db, "function_declaration", fnc->qualifiedName()).value().first;
0539         db << "insert into global_function_source_file(source_file_id, function_id) values (:s, :t) on conflict do "
0540               "nothing",
0541             soci::use(this_id), soci::use(dep_id);
0542     }
0543 }
0544 
0545 void exportUserDefinedTypeRelations(TypeObject *type, soci::session& db)
0546 {
0547     auto lock = type->readOnlyLock();
0548     (void) lock;
0549 
0550     const int this_id = query_id_from_qual_name(db, "class_declaration", type->qualifiedName()).value().first;
0551     int subclass_id = 0;
0552     int uses_in_interface_id = 0;
0553     int uses_in_impl_id = 0;
0554     int component_id = 0;
0555     int file_id = 0;
0556 
0557     soci::statement subclass_insert_st =
0558         (db.prepare << "insert into class_hierarchy(source_id, target_id) values (:s, :t) on conflict do nothing",
0559          soci::use(this_id),
0560          soci::use(subclass_id));
0561 
0562     soci::statement uses_in_interface_insert_st =
0563         (db.prepare << "insert into uses_in_the_interface(source_id, target_id) values (:s, :t) on conflict do nothing",
0564          soci::use(this_id),
0565          soci::use(uses_in_interface_id));
0566 
0567     soci::statement uses_in_impl_insert_st =
0568         (db.prepare
0569              << "insert into uses_in_the_implementation(source_id, target_id) values (:s, :t) on conflict do nothing",
0570          soci::use(this_id),
0571          soci::use(uses_in_impl_id));
0572 
0573     soci::statement component_insert_st =
0574         (db.prepare << "insert into udt_component(udt_id, component_id) values (:s, :t) on conflict do nothing",
0575          soci::use(this_id),
0576          soci::use(component_id));
0577 
0578     soci::statement file_insert_st =
0579         (db.prepare << "insert into class_source_file(class_id, source_file_id) values (:s, :t) on conflict do nothing",
0580          soci::use(this_id),
0581          soci::use(file_id));
0582     // isA
0583     for (TypeObject *subclass : type->subclasses()) {
0584         auto _lock = subclass->readOnlyLock();
0585         (void) _lock;
0586 
0587         subclass_id = query_id_from_qual_name(db, "class_declaration", subclass->qualifiedName()).value().first;
0588         subclass_insert_st.execute(true);
0589     }
0590 
0591     // usesInTheInterface
0592     for (TypeObject *dep : type->usesInTheInterface()) {
0593         auto _lock = dep->readOnlyLock();
0594         (void) _lock;
0595 
0596         uses_in_interface_id = query_id_from_qual_name(db, "class_declaration", dep->qualifiedName()).value().first;
0597         uses_in_interface_insert_st.execute(true);
0598     }
0599 
0600     // usesInTheImplementation
0601     for (TypeObject *dep : type->usesInTheImplementation()) {
0602         auto _lock = dep->readOnlyLock();
0603         (void) _lock;
0604 
0605         uses_in_impl_id = query_id_from_qual_name(db, "class_declaration", dep->qualifiedName()).value().first;
0606         uses_in_impl_insert_st.execute(true);
0607     }
0608 
0609     for (ComponentObject *comp : type->components()) {
0610         auto _lock = comp->readOnlyLock();
0611         (void) _lock;
0612 
0613         component_id = query_id_from_qual_name(db, "source_component", comp->qualifiedName()).value().first;
0614         component_insert_st.execute(true);
0615     }
0616 
0617     for (FileObject *file : type->files()) {
0618         auto _lock = file->readOnlyLock();
0619         (void) _lock;
0620 
0621         file_id = query_id_from_qual_name(db, "source_file", file->qualifiedName()).value().first;
0622         file_insert_st.execute(true);
0623     }
0624 }
0625 
0626 void exportFieldRelations(FieldObject *field, soci::session& db)
0627 {
0628     auto lock = field->readOnlyLock();
0629     (void) lock;
0630 
0631     int this_id = query_id_from_qual_name(db, "field_declaration", field->qualifiedName()).value().first;
0632     int dep_id = 0;
0633 
0634     soci::statement insert_st =
0635         (db.prepare << "insert into field_type(field_id, type_class_id) values (:s, :t) on conflict do nothing",
0636          soci::use(this_id),
0637          soci::use(dep_id));
0638 
0639     // variable types
0640     for (TypeObject *type : field->variableTypes()) {
0641         auto _lock = type->readOnlyLock();
0642         (void) _lock;
0643 
0644         dep_id = query_id_from_qual_name(db, "class_declaration", type->qualifiedName()).value().first;
0645         insert_st.execute(true);
0646     }
0647 }
0648 
0649 void exportMethodRelations(MethodObject *method, soci::session& db)
0650 {
0651     auto lock = method->readOnlyLock();
0652     (void) lock;
0653 
0654     int this_id = query_id_from_qual_name(db, "method_declaration", method->qualifiedName()).value().first;
0655     int dep_id = 0;
0656 
0657     soci::statement insert_st =
0658         (db.prepare
0659              << "insert into method_argument_class(method_id, type_class_id) values (:s, :t) on conflict do nothing",
0660          soci::use(this_id),
0661          soci::use(dep_id));
0662 
0663     // variable types
0664     for (TypeObject *type : method->argumentTypes()) {
0665         auto _lock = type->readOnlyLock();
0666         (void) _lock;
0667 
0668         dep_id = query_id_from_qual_name(db, "class_declaration", type->qualifiedName()).value().first;
0669         insert_st.execute(true);
0670     }
0671 }
0672 
0673 void exportFunctionCallgraph(FunctionObject *fn, soci::session& db)
0674 {
0675     auto lock = fn->readOnlyLock();
0676     (void) lock;
0677 
0678     int this_id = query_id_from_qual_name(db, "function_declaration", fn->qualifiedName()).value().first;
0679     int callee_id = 0;
0680 
0681     soci::statement insert_st =
0682         (db.prepare << "insert into function_calls(caller_id, callee_id) values (:s, :t) on conflict do nothing",
0683          soci::use(this_id),
0684          soci::use(callee_id));
0685 
0686     // variable types
0687     for (FunctionObject *callee : fn->callees()) {
0688         auto _lock = callee->readOnlyLock();
0689         (void) _lock;
0690 
0691         callee_id = query_id_from_qual_name(db, "function_declaration", callee->qualifiedName()).value().first;
0692         insert_st.execute(true);
0693     }
0694 }
0695 
0696 } // namespace
0697 namespace Codethink::lvtmdb {
0698 
0699 SociWriter::SociWriter() = default;
0700 
0701 bool SociWriter::updateDbSchema(const std::string& path, const std::string& schemaPath)
0702 {
0703     if (!std::filesystem::exists(path)) {
0704         return createOrOpen(path, schemaPath);
0705     }
0706 
0707     // not a problem if called multiple times.
0708     initialize_resources();
0709     d_db.open(*soci::factory_sqlite3(), path);
0710     bool res = run_migration(d_db, schemaPath);
0711     return res;
0712 }
0713 
0714 bool SociWriter::createOrOpen(const std::string& path, const std::string& schemaPath)
0715 {
0716     const bool create_db = path == ":memory:" || !std::filesystem::exists(path);
0717 
0718     std::cout << "Trying to open database at " << path << "\n";
0719 
0720     d_db.open(*soci::factory_sqlite3(), path);
0721     if (create_db) {
0722         const bool res = run_migration(d_db, schemaPath);
0723         if (!res) {
0724             return false;
0725         }
0726         std::cout << "Database created correctly\n";
0727     } else {
0728         std::cout << "Using a pre-existing database\n";
0729     }
0730 
0731     d_path = path;
0732     return true;
0733 }
0734 
0735 void SociWriter::writeFrom(const ObjectStore& store)
0736 {
0737     QElapsedTimer timer;
0738     timer.start();
0739     assert(!d_path.empty());
0740     assert(d_db.is_connected());
0741 
0742     std::cout << "Starting to write to the database at " << d_path << "\n";
0743     soci::transaction tr(d_db);
0744 
0745     // TODO: To optimize this call, we need to create the queries outside of the
0746     //  loops, as each query takes a time to be created and validated.
0747     //  best case scenario is to create all queries in this method, and just
0748     //  pass the statements to the functions.
0749     //  this function is taking 17 seconds to run on a small source code.
0750     for (const auto& [_, repo] : store.repositories()) {
0751         (void) _;
0752         exportRepository(repo.get(), d_db);
0753     }
0754 
0755     for (const auto& [_, pkg] : store.packages()) {
0756         (void) _;
0757         exportPackage(pkg.get(), d_db);
0758     }
0759 
0760     for (const auto& [_, comp] : store.components()) {
0761         (void) _;
0762         exportComponent(comp.get(), d_db);
0763     }
0764 
0765     for (const auto& [_, file] : store.files()) {
0766         (void) _;
0767         exportFile(file.get(), d_db);
0768     }
0769 
0770     for (const auto& [_, error] : store.errors()) {
0771         (void) _;
0772         exportError(error.get(), d_db);
0773     }
0774     for (const auto& [_, nmspc] : store.namespaces()) {
0775         (void) _;
0776         exportNamespace(nmspc.get(), d_db);
0777     }
0778     for (const auto& [_, var] : store.variables()) {
0779         (void) _;
0780         exportVariable(var.get(), d_db);
0781     }
0782     for (const auto& [_, fn] : store.functions()) {
0783         (void) _;
0784         exportFunction(fn.get(), d_db);
0785     }
0786     for (const auto& [_, type] : store.types()) {
0787         (void) _;
0788         exportUserDefinedType(type.get(), d_db);
0789     }
0790     for (const auto& [_, field] : store.fields()) {
0791         (void) _;
0792         exportField(field.get(), d_db);
0793     }
0794     for (const auto& [_, method] : store.methods()) {
0795         (void) _;
0796         exportMethod(method.get(), d_db);
0797     }
0798     for (const auto& [_, pkg] : store.packages()) {
0799         (void) _;
0800         exportPkgRelations(pkg.get(), d_db);
0801     }
0802 
0803     for (const auto& [_, comp] : store.components()) {
0804         (void) _;
0805         exportCompRelations(comp.get(), d_db);
0806     }
0807 
0808     for (const auto& [_, file] : store.files()) {
0809         (void) _;
0810         exportFileRelations(file.get(), d_db);
0811     }
0812 
0813     for (const auto& [_, type] : store.types()) {
0814         (void) _;
0815         exportUserDefinedTypeRelations(type.get(), d_db);
0816     }
0817 
0818     for (const auto& [_, field] : store.fields()) {
0819         (void) _;
0820         exportFieldRelations(field.get(), d_db);
0821     }
0822 
0823     for (const auto& [_, method] : store.methods()) {
0824         (void) _;
0825         exportMethodRelations(method.get(), d_db);
0826     }
0827 
0828     for (const auto& [_, fn] : store.functions()) {
0829         (void) _;
0830         exportFunctionCallgraph(fn.get(), d_db);
0831     }
0832 
0833     int n = 0;
0834     d_db << "select count(*) from db_option", soci::into(n);
0835     if (n == 0) {
0836         const std::vector<std::pair<int, int>> db_options = {
0837             {static_cast<int>(SociHelper::Key::Version), SociHelper::CURRENT_VERSION},
0838             {static_cast<int>(SociHelper::Key::DatabaseState), static_cast<int>(store.state())}};
0839 
0840         for (const auto& [key, val] : db_options) {
0841             d_db << "insert into db_option(key, value) values(:k, :v)", soci::use(key), soci::use(val);
0842         }
0843     }
0844 
0845     tr.commit();
0846     std::cout << "Ending to write to the database: " << timer.elapsed() << "\n";
0847 }
0848 
0849 } // namespace Codethink::lvtmdb