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