X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FNavaids%2FNavDataCache.cxx;h=f6756613bd6c19130d5c308b44c36282add9852f;hb=5c659b3970e9a4542519e0d528016c08f7ecb6c1;hp=60d6aab44e85df0d7ba2422e92c9f8800292fe77;hpb=6365ad5dae50e66be86b8240c72fa16ec8de526d;p=flightgear.git diff --git a/src/Navaids/NavDataCache.cxx b/src/Navaids/NavDataCache.cxx index 60d6aab44..f6756613b 100644 --- a/src/Navaids/NavDataCache.cxx +++ b/src/Navaids/NavDataCache.cxx @@ -23,11 +23,6 @@ # include "config.h" #endif -// to ensure compatability between sqlite3_int64 and PositionedID, -// force the type used by sqlite to match PositionedID explicitly -#define SQLITE_INT64_TYPE int64_t -#define SQLITE_UINT64_TYPE uint64_t - #include "NavDataCache.hxx" // std @@ -37,9 +32,23 @@ // boost #include -#include "sqlite3.h" + +#ifdef SYSTEM_SQLITE +// the standard sqlite3.h doesn't give a way to set SQLITE_UINT64_TYPE, +// so we have to hope sizeof(int64_t) matches sizeof(sqlite3_int64). +// otherwise things will go bad quickly. + #include "sqlite3.h" +#else +// to ensure compatability between sqlite3_int64 and PositionedID, +// force the type used by sqlite to match PositionedID explicitly +#define SQLITE_INT64_TYPE int64_t +#define SQLITE_UINT64_TYPE uint64_t + + #include "fg_sqlite3.h" +#endif // SimGear +#include #include #include #include @@ -49,9 +58,11 @@ #include #include
+#include
+#include
#include "markerbeacon.hxx" #include "navrecord.hxx" -#include +#include #include #include #include "fix.hxx" @@ -60,8 +71,10 @@ #include "PositionedOctree.hxx" #include #include +#include "poidb.hxx" #include #include +#include "CacheSchema.h" using std::string; @@ -70,8 +83,10 @@ using std::string; namespace { -const int SCHEMA_VERSION = 6; - +const int MAX_RETRIES = 10; + +const int CACHE_SIZE_KBYTES= 32 * 1024; + // bind a std::string to a sqlite statement. The std::string must live the // entire duration of the statement execution - do not pass a temporary // std::string, or the compiler may delete it, freeing the C-string storage, @@ -195,27 +210,31 @@ public: outer(o), db(NULL), path(p), + readOnly(false), cacheHits(0), - cacheMisses(0) + cacheMisses(0), + transactionLevel(0), + transactionAborted(false) { } ~NavDataCachePrivate() { - BOOST_FOREACH(sqlite3_stmt_ptr stmt, prepared) { - sqlite3_finalize(stmt); - } - prepared.clear(); - - sqlite3_close(db); + close(); } void init() { SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache at:" << path); - sqlite3_open_v2(path.c_str(), &db, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); - + + readOnly = fgGetBool("/sim/fghome-readonly", false); + + int openFlags = readOnly ? SQLITE_OPEN_READONLY : + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + // see http://code.google.com/p/flightgear-bugs/issues/detail?id=1055 + // for the UTF8 / path logic here + std::string pathUtf8 = simgear::strutils::convertWindowsLocal8BitToUtf8(path.str()); + sqlite3_open_v2(pathUtf8.c_str(), &db, openFlags, NULL); sqlite3_stmt_ptr checkTables = prepare("SELECT count(*) FROM sqlite_master WHERE name='properties'"); @@ -225,7 +244,7 @@ public: execSelect(checkTables); bool didCreate = false; - if (sqlite3_column_int(checkTables, 0) == 0) { + if (!readOnly && (sqlite3_column_int(checkTables, 0) == 0)) { SG_LOG(SG_NAVCACHE, SG_INFO, "will create tables"); initTables(); didCreate = true; @@ -245,16 +264,30 @@ public: } } + // see http://www.sqlite.org/pragma.html#pragma_cache_size + // for the details, small cache would cause thrashing. + std::ostringstream q; + q << "PRAGMA cache_size=-" << CACHE_SIZE_KBYTES << ";"; + runSQL(q.str()); prepareQueries(); } + void close() + { + BOOST_FOREACH(sqlite3_stmt_ptr stmt, prepared) { + sqlite3_finalize(stmt); + } + prepared.clear(); + sqlite3_close(db); + } + void checkCacheFile() { SG_LOG(SG_NAVCACHE, SG_INFO, "running DB integrity check"); SGTimeStamp st; st.stamp(); - sqlite3_stmt_ptr stmt = prepare("PRAGMA integrity_check(1)"); + sqlite3_stmt_ptr stmt = prepare("PRAGMA quick_check(1)"); if (!execSelect(stmt)) { throw sg_exception("DB integrity check failed to run"); } @@ -267,6 +300,8 @@ public: SG_LOG(SG_NAVCACHE, SG_INFO, "NavDataCache integrity check took:" << st.elapsedMSec()); finalize(stmt); } + + bool isCachedFileModified(const SGPath& path, bool verbose); void callSqlite(int result, const string& sql) { @@ -334,8 +369,6 @@ public: return stepSelect(stmt); } - const int MAX_RETRIES = 10; - bool stepSelect(sqlite3_stmt_ptr stmt) { int retries = 0; @@ -350,15 +383,16 @@ public: return false; // no result rows } - if (result != SQLITE_LOCKED) { + if (result != SQLITE_BUSY) { break; } - ++retries; + SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache contention on select, will retry:" << retries); + SGTimeStamp::sleepForMSec(++retries * 10); } // of retry loop for DB locked if (retries >= MAX_RETRIES) { - SG_LOG(SG_NAVCACHE, SG_ALERT, "exceeded maximum number of SQLITE_LOCKED retries"); + SG_LOG(SG_NAVCACHE, SG_ALERT, "exceeded maximum number of SQLITE_BUSY retries"); return false; } @@ -368,8 +402,8 @@ public: SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite API abuse"); } else { errMsg = sqlite3_errmsg(db); - SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg - << " while running:\n\t" << sqlite3_sql(stmt)); + SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " (" << result + << ") while running:\n\t" << sqlite3_sql(stmt)); } throw sg_exception("Sqlite error:" + errMsg, sqlite3_sql(stmt)); @@ -386,129 +420,38 @@ public: sqlite3_int64 execInsert(sqlite3_stmt_ptr stmt) { execSelect(stmt); - return sqlite3_last_insert_rowid(db); + sqlite3_int64 rowid = sqlite3_last_insert_rowid(db); + reset(stmt); + return rowid; } void execUpdate(sqlite3_stmt_ptr stmt) { execSelect(stmt); + reset(stmt); } - + void initTables() { - runSQL("CREATE TABLE properties (" - "key VARCHAR," - "value VARCHAR" - ")"); - - runSQL("CREATE TABLE stat_cache (" - "path VARCHAR unique," - "stamp INT" - ")"); - - runSQL("CREATE TABLE positioned (" - "type INT," - "ident VARCHAR collate nocase," - "name VARCHAR collate nocase," - "airport INT64," - "lon FLOAT," - "lat FLOAT," - "elev_m FLOAT," - "octree_node INT," - "cart_x FLOAT," - "cart_y FLOAT," - "cart_z FLOAT" - ")"); - - runSQL("CREATE INDEX pos_octree ON positioned(octree_node)"); - runSQL("CREATE INDEX pos_ident ON positioned(ident collate nocase)"); - runSQL("CREATE INDEX pos_name ON positioned(name collate nocase)"); - // allow efficient querying of 'all ATIS at this airport' or - // 'all towers at this airport' - runSQL("CREATE INDEX pos_apt_type ON positioned(airport, type)"); - - runSQL("CREATE TABLE airport (" - "has_metar BOOL" - ")" - ); - - runSQL("CREATE TABLE comm (" - "freq_khz INT," - "range_nm INT" - ")" - ); - - runSQL("CREATE INDEX comm_freq ON comm(freq_khz)"); - - runSQL("CREATE TABLE runway (" - "heading FLOAT," - "length_ft FLOAT," - "width_m FLOAT," - "surface INT," - "displaced_threshold FLOAT," - "stopway FLOAT," - "reciprocal INT64," - "ils INT64" - ")" - ); - - runSQL("CREATE TABLE navaid (" - "freq INT," - "range_nm INT," - "multiuse FLOAT," - "runway INT64," - "colocated INT64" - ")" - ); - - runSQL("CREATE INDEX navaid_freq ON navaid(freq)"); - - runSQL("CREATE TABLE octree (children INT)"); - - runSQL("CREATE TABLE airway (" - "ident VARCHAR collate nocase," - "network INT" // high-level or low-level - ")"); - - runSQL("CREATE INDEX airway_ident ON airway(ident)"); - - runSQL("CREATE TABLE airway_edge (" - "network INT," - "airway INT64," - "a INT64," - "b INT64" - ")"); - - runSQL("CREATE INDEX airway_edge_from ON airway_edge(a)"); - - runSQL("CREATE TABLE taxi_node (" - "hold_type INT," - "on_runway BOOL," - "pushback BOOL" - ")"); - - runSQL("CREATE TABLE parking (" - "heading FLOAT," - "radius INT," - "gate_type VARCHAR," - "airlines VARCHAR," - "pushback INT64" - ")"); - - runSQL("CREATE TABLE groundnet_edge (" - "airport INT64," - "a INT64," - "b INT64" - ")"); - - runSQL("CREATE INDEX groundnet_edge_airport ON groundnet_edge(airport)"); - runSQL("CREATE INDEX groundnet_edge_from ON groundnet_edge(a)"); + string_list commands = simgear::strutils::split(SCHEMA_SQL, ";"); + BOOST_FOREACH(std::string sql, commands) { + if (sql.empty()) { + continue; + } + + runSQL(sql); + } // of commands in scheme loop } void prepareQueries() { writePropertyMulti = prepare("INSERT INTO properties (key, value) VALUES(?1,?2)"); + beginTransactionStmt = prepare("BEGIN"); + commitTransactionStmt = prepare("COMMIT"); + rollbackTransactionStmt = prepare("ROLLBACK"); + + #define POSITIONED_COLS "rowid, type, ident, name, airport, lon, lat, elev_m, octree_node" #define AND_TYPED "AND type>=?2 AND type <=?3" statCacheCheck = prepare("SELECT stamp FROM stat_cache WHERE path=?"); @@ -532,7 +475,7 @@ public: setRunwayReciprocal = prepare("UPDATE runway SET reciprocal=?2 WHERE rowid=?1"); setRunwayILS = prepare("UPDATE runway SET ils=?2 WHERE rowid=?1"); - updateRunwayThreshold = prepare("UPDATE runway SET heading=?2, displaced_threshold=?3, stopway=?4 WHERE rowid=?1"); + setNavaidColocated = prepare("UPDATE navaid SET colocated=?2 WHERE rowid=?1"); insertPositionedQuery = prepare("INSERT INTO positioned " "(type, ident, name, airport, lon, lat, elev_m, octree_node, " @@ -544,7 +487,6 @@ public: insertAirport = prepare("INSERT INTO airport (rowid, has_metar) VALUES (?, ?)"); insertNavaid = prepare("INSERT INTO navaid (rowid, freq, range_nm, multiuse, runway, colocated)" " VALUES (?1, ?2, ?3, ?4, ?5, ?6)"); - updateILS = prepare("UPDATE navaid SET multiuse=?2 WHERE rowid=?1"); insertCommStation = prepare("INSERT INTO comm (rowid, freq_khz, range_nm)" " VALUES (?, ?, ?)"); @@ -553,6 +495,8 @@ public: " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)"); runwayLengthFtQuery = prepare("SELECT length_ft FROM runway WHERE rowid=?1"); + removePOIQuery = prepare("DELETE FROM positioned WHERE type=?1 AND ident=?2"); + // query statement findClosestWithIdent = prepare("SELECT rowid FROM positioned WHERE ident=?1 " AND_TYPED " ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?4, ?5, ?6)"); @@ -592,6 +536,11 @@ public: sqlite3_bind_int(searchAirports, 2, FGPositioned::AIRPORT); sqlite3_bind_int(searchAirports, 3, FGPositioned::SEAPORT); + getAllAirports = prepare("SELECT ident, name FROM positioned WHERE type>=?1 AND type <=?2"); + sqlite3_bind_int(getAllAirports, 1, FGPositioned::AIRPORT); + sqlite3_bind_int(getAllAirports, 2, FGPositioned::SEAPORT); + + getAirportItemByIdent = prepare("SELECT rowid FROM positioned WHERE airport=?1 AND ident=?2 AND type=?3"); findAirportRunway = prepare("SELECT airport, rowid FROM positioned WHERE ident=?2 AND type=?3 AND airport=" @@ -667,33 +616,32 @@ public: void writeIntProperty(const string& key, int value) { - reset(clearProperty); sqlite_bind_stdstring(clearProperty, 1, key); execUpdate(clearProperty); sqlite_bind_stdstring(writePropertyQuery, 1, key); sqlite3_bind_int(writePropertyQuery, 2, value); - execSelect(writePropertyQuery); + execUpdate(writePropertyQuery); } - FGPositioned* loadFromStmt(sqlite3_stmt_ptr query); + FGPositioned* loadById(sqlite_int64 rowId, sqlite3_int64& aptId); FGAirport* loadAirport(sqlite_int64 rowId, FGPositioned::Type ty, const string& id, const string& name, const SGGeod& pos) { - reset(loadAirportStmt); sqlite3_bind_int64(loadAirportStmt, 1, rowId); execSelect1(loadAirportStmt); bool hasMetar = (sqlite3_column_int(loadAirportStmt, 0) > 0); + reset(loadAirportStmt); + return new FGAirport(rowId, id, pos, name, hasMetar, ty); } FGRunwayBase* loadRunway(sqlite3_int64 rowId, FGPositioned::Type ty, const string& id, const SGGeod& pos, PositionedID apt) { - reset(loadRunwayStmt); sqlite3_bind_int(loadRunwayStmt, 1, rowId); execSelect1(loadRunwayStmt); @@ -703,14 +651,18 @@ public: int surface = sqlite3_column_int(loadRunwayStmt, 3); if (ty == FGPositioned::TAXIWAY) { + reset(loadRunwayStmt); return new FGTaxiway(rowId, id, pos, heading, lengthM, widthM, surface); + } else if (ty == FGPositioned::HELIPAD) { + reset(loadRunwayStmt); + return new FGHelipad(rowId, apt, id, pos, heading, lengthM, widthM, surface); } else { double displacedThreshold = sqlite3_column_double(loadRunwayStmt, 4); double stopway = sqlite3_column_double(loadRunwayStmt, 5); PositionedID reciprocal = sqlite3_column_int64(loadRunwayStmt, 6); PositionedID ils = sqlite3_column_int64(loadRunwayStmt, 7); FGRunway* r = new FGRunway(rowId, apt, id, pos, heading, lengthM, widthM, - displacedThreshold, stopway, surface, false); + displacedThreshold, stopway, surface); if (reciprocal > 0) { r->setReciprocalRunway(reciprocal); @@ -720,6 +672,7 @@ public: r->setILS(ils); } + reset(loadRunwayStmt); return r; } } @@ -729,12 +682,12 @@ public: const SGGeod& pos, PositionedID airport) { - reset(loadCommStation); sqlite3_bind_int64(loadCommStation, 1, rowId); execSelect1(loadCommStation); int range = sqlite3_column_int(loadCommStation, 0); int freqKhz = sqlite3_column_int(loadCommStation, 1); + reset(loadCommStation); CommStation* c = new CommStation(rowId, name, ty, pos, freqKhz, range); c->setAirport(airport); @@ -745,7 +698,6 @@ public: FGPositioned::Type ty, const string& id, const string& name, const SGGeod& pos) { - reset(loadNavaid); sqlite3_bind_int64(loadNavaid, 1, rowId); execSelect1(loadNavaid); @@ -754,22 +706,33 @@ public: if ((ty == FGPositioned::OM) || (ty == FGPositioned::IM) || (ty == FGPositioned::MM)) { + reset(loadNavaid); return new FGMarkerBeaconRecord(rowId, ty, runway, pos); } int rangeNm = sqlite3_column_int(loadNavaid, 0), - freq = sqlite3_column_int(loadNavaid, 1); + freq = sqlite3_column_int(loadNavaid, 1); double mulituse = sqlite3_column_double(loadNavaid, 2); - //sqlite3_int64 colocated = sqlite3_column_int64(loadNavaid, 4); - - return new FGNavRecord(rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway); + PositionedID colocated = sqlite3_column_int64(loadNavaid, 4); + reset(loadNavaid); + + FGNavRecord* n = + (ty == FGPositioned::MOBILE_TACAN) + ? new FGMobileNavRecord + (rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway) + : new FGNavRecord + (rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway); + + if (colocated) + n->setColocatedDME(colocated); + + return n; } FGPositioned* loadParking(sqlite3_int64 rowId, const string& name, const SGGeod& pos, PositionedID airport) { - reset(loadParkingPos); sqlite3_bind_int64(loadParkingPos, 1, rowId); execSelect1(loadParkingPos); @@ -778,6 +741,7 @@ public: string aircraftType((char*) sqlite3_column_text(loadParkingPos, 2)); string airlines((char*) sqlite3_column_text(loadParkingPos, 3)); PositionedID pushBack = sqlite3_column_int64(loadParkingPos, 4); + reset(loadParkingPos); return new FGParking(rowId, pos, heading, radius, name, aircraftType, airlines, pushBack); } @@ -785,12 +749,13 @@ public: FGPositioned* loadTaxiNode(sqlite3_int64 rowId, const SGGeod& pos, PositionedID airport) { - reset(loadTaxiNodeStmt); sqlite3_bind_int64(loadTaxiNodeStmt, 1, rowId); execSelect1(loadTaxiNodeStmt); int hold_type = sqlite3_column_int(loadTaxiNodeStmt, 0); bool onRunway = sqlite3_column_int(loadTaxiNodeStmt, 1); + reset(loadTaxiNodeStmt); + return new FGTaxiNode(rowId, pos, onRunway, hold_type); } @@ -800,7 +765,6 @@ public: { SGVec3d cartPos(SGVec3d::fromGeod(pos)); - reset(insertPositionedQuery); sqlite3_bind_int(insertPositionedQuery, 1, ty); sqlite_bind_stdstring(insertPositionedQuery, 2, ident); sqlite_bind_stdstring(insertPositionedQuery, 3, name); @@ -821,11 +785,11 @@ public: sqlite3_bind_double(insertPositionedQuery, 10, cartPos.y()); sqlite3_bind_double(insertPositionedQuery, 11, cartPos.z()); - PositionedID r = execInsert(insertPositionedQuery); + PositionedID r = execInsert(insertPositionedQuery); return r; } - FGPositioned::List findAllByString(const string& s, const string& column, + FGPositionedList findAllByString(const string& s, const string& column, FGPositioned::Filter* filter, bool exact) { string query = s; @@ -845,14 +809,13 @@ public: findByStringDict[sql] = stmt; } - reset(stmt); sqlite_bind_stdstring(stmt, 1, query); if (filter) { sqlite3_bind_int(stmt, 2, filter->minType()); sqlite3_bind_int(stmt, 3, filter->maxType()); } - FGPositioned::List result; + FGPositionedList result; // run the prepared SQL while (stepSelect(stmt)) { @@ -864,6 +827,7 @@ public: result.push_back(pos); } + reset(stmt); return result; } @@ -873,21 +837,22 @@ public: while (stepSelect(query)) { result.push_back(sqlite3_column_int64(query, 0)); } + reset(query); return result; } double runwayLengthFt(PositionedID rwy) { - reset(runwayLengthFtQuery); sqlite3_bind_int64(runwayLengthFtQuery, 1, rwy); execSelect1(runwayLengthFtQuery); - return sqlite3_column_double(runwayLengthFtQuery, 0); + double length = sqlite3_column_double(runwayLengthFtQuery, 0); + reset(runwayLengthFtQuery); + return length; } void flushDeferredOctreeUpdates() { BOOST_FOREACH(Octree::Branch* nd, deferredOctreeUpdates) { - reset(updateOctreeChildren); sqlite3_bind_int64(updateOctreeChildren, 1, nd->guid()); sqlite3_bind_int(updateOctreeChildren, 2, nd->childMask()); execUpdate(updateOctreeChildren); @@ -895,18 +860,34 @@ public: deferredOctreeUpdates.clear(); } + + void removePositionedWithIdent(FGPositioned::Type ty, const std::string& aIdent) + { + sqlite3_bind_int(removePOIQuery, 1, ty); + sqlite_bind_stdstring(removePOIQuery, 2, aIdent); + execUpdate(removePOIQuery); + reset(removePOIQuery); + } NavDataCache* outer; sqlite3* db; SGPath path; - + bool readOnly; + /// the actual cache of ID -> instances. This holds an owning reference, /// so once items are in the cache they will never be deleted until /// the cache drops its reference PositionedCache cache; unsigned int cacheHits, cacheMisses; + + /** + * record the levels of open transaction objects we have + */ + unsigned int transactionLevel; + bool transactionAborted; + sqlite3_stmt_ptr beginTransactionStmt, commitTransactionStmt, rollbackTransactionStmt; - SGPath aptDatPath, metarDatPath, navDatPath, fixDatPath, + SGPath aptDatPath, metarDatPath, navDatPath, fixDatPath, poiDatPath, carrierDatPath, airwayDatPath; sqlite3_stmt_ptr readPropertyQuery, writePropertyQuery, @@ -917,15 +898,16 @@ public: sqlite3_stmt_ptr insertPositionedQuery, insertAirport, insertTower, insertRunway, insertCommStation, insertNavaid; - sqlite3_stmt_ptr setAirportMetar, setRunwayReciprocal, setRunwayILS, - setAirportPos, updateRunwayThreshold, updateILS; + sqlite3_stmt_ptr setAirportMetar, setRunwayReciprocal, setRunwayILS, setNavaidColocated, + setAirportPos; + sqlite3_stmt_ptr removePOIQuery; sqlite3_stmt_ptr findClosestWithIdent; // octree (spatial index) related queries sqlite3_stmt_ptr getOctreeChildren, insertOctree, updateOctreeChildren, getOctreeLeafChildren; - sqlite3_stmt_ptr searchAirports; + sqlite3_stmt_ptr searchAirports, getAllAirports; sqlite3_stmt_ptr findCommByFreq, findNavsByFreq, findNavsByFreqNoPos, findNavaidForRunway; sqlite3_stmt_ptr getAirportItems, getAirportItemByIdent; @@ -959,21 +941,28 @@ public: std::auto_ptr rebuilder; }; - ////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// -FGPositioned* NavDataCache::NavDataCachePrivate::loadFromStmt(sqlite3_stmt_ptr query) +FGPositioned* NavDataCache::NavDataCachePrivate::loadById(sqlite3_int64 rowid, + sqlite3_int64& aptId) { - execSelect1(query); - sqlite3_int64 rowid = sqlite3_column_int64(query, 0); - FGPositioned::Type ty = (FGPositioned::Type) sqlite3_column_int(query, 1); - string ident = (char*) sqlite3_column_text(query, 2); - string name = (char*) sqlite3_column_text(query, 3); - sqlite3_int64 aptId = sqlite3_column_int64(query, 4); - double lon = sqlite3_column_double(query, 5); - double lat = sqlite3_column_double(query, 6); - double elev = sqlite3_column_double(query, 7); + sqlite3_bind_int64(loadPositioned, 1, rowid); + execSelect1(loadPositioned); + + assert(rowid == sqlite3_column_int64(loadPositioned, 0)); + FGPositioned::Type ty = (FGPositioned::Type) sqlite3_column_int(loadPositioned, 1); + + PositionedID prowid = static_cast(rowid); + string ident = (char*) sqlite3_column_text(loadPositioned, 2); + string name = (char*) sqlite3_column_text(loadPositioned, 3); + aptId = sqlite3_column_int64(loadPositioned, 4); + double lon = sqlite3_column_double(loadPositioned, 5); + double lat = sqlite3_column_double(loadPositioned, 6); + double elev = sqlite3_column_double(loadPositioned, 7); SGGeod pos = SGGeod::fromDegM(lon, lat, elev); + + reset(loadPositioned); switch (ty) { case FGPositioned::AIRPORT: @@ -982,9 +971,10 @@ FGPositioned* NavDataCache::NavDataCachePrivate::loadFromStmt(sqlite3_stmt_ptr q return loadAirport(rowid, ty, ident, name, pos); case FGPositioned::TOWER: - return new AirportTower(rowid, aptId, ident, pos); + return new AirportTower(prowid, aptId, ident, pos); case FGPositioned::RUNWAY: + case FGPositioned::HELIPAD: case FGPositioned::TAXIWAY: return loadRunway(rowid, ty, ident, pos, aptId); @@ -999,26 +989,18 @@ FGPositioned* NavDataCache::NavDataCachePrivate::loadFromStmt(sqlite3_stmt_ptr q case FGPositioned::DME: case FGPositioned::TACAN: case FGPositioned::MOBILE_TACAN: - { - if (aptId > 0) { - FGAirport* apt = (FGAirport*) outer->loadById(aptId); - if (apt->validateILSData()) { - SG_LOG(SG_NAVCACHE, SG_INFO, "re-loaded ILS data for " << apt->ident()); - // queried data above is probably invalid, force us to go around again - // (the next time through, validateILSData will return false) - return outer->loadById(rowid); - } - } - return loadNav(rowid, ty, ident, name, pos); - } case FGPositioned::FIX: return new FGFix(rowid, ident, pos); case FGPositioned::WAYPOINT: + case FGPositioned::COUNTRY: + case FGPositioned::CITY: + case FGPositioned::TOWN: + case FGPositioned::VILLAGE: { - FGPositioned* wpt = new FGPositioned(rowid, FGPositioned::WAYPOINT, ident, pos); + FGPositioned* wpt = new FGPositioned(rowid, ty, ident, pos); return wpt; } @@ -1042,7 +1024,37 @@ FGPositioned* NavDataCache::NavDataCachePrivate::loadFromStmt(sqlite3_stmt_ptr q return NULL; } } - + +bool NavDataCache::NavDataCachePrivate::isCachedFileModified(const SGPath& path, bool verbose) +{ + if (!path.exists()) { + throw sg_io_exception("isCachedFileModified: Missing file:" + path.str()); + } + + sqlite_bind_temp_stdstring(statCacheCheck, 1, path.str()); + bool isModified = true; + sgDebugPriority logLevel = verbose ? SG_WARN : SG_DEBUG; + if (execSelect(statCacheCheck)) { + time_t modtime = sqlite3_column_int64(statCacheCheck, 0); + time_t delta = std::labs(modtime - path.modTime()); + if (delta != 0) + { + SG_LOG(SG_NAVCACHE, logLevel, "NavCache: rebuild required for " << path << + ". Timestamps: " << modtime << " != " << path.modTime()); + } + else + { + SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: no rebuild required for " << path); + } + + isModified = (delta != 0); + } else { + SG_LOG(SG_NAVCACHE, logLevel, "NavCache: initial build required for " << path); + } + + reset(statCacheCheck); + return isModified; +} static NavDataCache* static_instance = NULL; @@ -1050,7 +1062,16 @@ NavDataCache::NavDataCache() { const int MAX_TRIES = 3; SGPath homePath(globals->get_fg_home()); - homePath.append("navdata.cache"); + + std::ostringstream os; + string_list versionParts = simgear::strutils::split(VERSION, "."); + if (versionParts.size() < 2) { + os << "navdata.cache"; + } else { + os << "navdata_" << versionParts[0] << "_" << versionParts[1] << ".cache"; + } + + homePath.append(os.str()); for (int t=0; t < MAX_TRIES; ++t) { try { @@ -1063,7 +1084,11 @@ NavDataCache::NavDataCache() SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what() << " (attempt " << t << ")"); d.reset(); - homePath.remove(); + + // only wipe the existing if not readonly + if (!fgGetBool("/sim/fghome-readonly", false)) { + homePath.remove(); + } } } // of retry loop @@ -1083,6 +1108,9 @@ NavDataCache::NavDataCache() d->fixDatPath = SGPath(globals->get_fg_root()); d->fixDatPath.append("Navaids/fix.dat.gz"); + + d->poiDatPath = SGPath(globals->get_fg_root()); + d->poiDatPath.append("Navaids/poi.dat.gz"); d->carrierDatPath = SGPath(globals->get_fg_root()); d->carrierDatPath.append("Navaids/carrier_nav.dat.gz"); @@ -1095,7 +1123,6 @@ NavDataCache::~NavDataCache() { assert(static_instance == this); static_instance = NULL; - SG_LOG(SG_NAVCACHE, SG_INFO, "closing the navcache"); d.reset(); } @@ -1110,25 +1137,49 @@ NavDataCache* NavDataCache::instance() bool NavDataCache::isRebuildRequired() { - if (isCachedFileModified(d->aptDatPath) || - isCachedFileModified(d->metarDatPath) || - isCachedFileModified(d->navDatPath) || - isCachedFileModified(d->fixDatPath) || - isCachedFileModified(d->airwayDatPath)) + if (d->readOnly) { + return false; + } + + if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) { + SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: restore-defaults requested, will rebuild cache"); + return true; + } + + if (d->isCachedFileModified(d->aptDatPath, true) || + d->isCachedFileModified(d->metarDatPath, true) || + d->isCachedFileModified(d->navDatPath, true) || + d->isCachedFileModified(d->fixDatPath, true) || + d->isCachedFileModified(d->carrierDatPath, true) || +// since POI loading is disabled on Windows, don't check for it +// this caused: https://code.google.com/p/flightgear-bugs/issues/detail?id=1227 +#ifndef SG_WINDOWS + d->isCachedFileModified(d->poiDatPath, true) || +#endif + d->isCachedFileModified(d->airwayDatPath, true)) { SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: main cache rebuild required"); return true; } - string sceneryPaths = simgear::strutils::join(globals->get_fg_scenery(), ";"); - if (readStringProperty("scenery_paths") != sceneryPaths) { - SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: scenery paths changed, main cache rebuild required"); - return true; - } - + dropGroundnetsIfRequired(); + SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: no main cache rebuild required"); return false; } + +bool NavDataCache::dropGroundnetsIfRequired() +{ + string sceneryPaths = simgear::strutils::join(globals->get_fg_scenery(), ";"); + if (readStringProperty("scenery_paths") != sceneryPaths) { + SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: scenery paths changed, dropping ground nets"); + dropAllGroundnets(); + writeStringProperty("scenery_paths", sceneryPaths); + return true; + } + + return false; +} bool NavDataCache::rebuild() { @@ -1148,99 +1199,124 @@ bool NavDataCache::rebuild() void NavDataCache::doRebuild() { try { - d->runSQL("BEGIN"); - d->runSQL("DELETE FROM positioned"); - d->runSQL("DELETE FROM airport"); - d->runSQL("DELETE FROM runway"); - d->runSQL("DELETE FROM navaid"); - d->runSQL("DELETE FROM comm"); - d->runSQL("DELETE FROM octree"); - d->runSQL("DELETE FROM airway"); - d->runSQL("DELETE FROM airway_edge"); - d->runSQL("DELETE FROM taxi_node"); - d->runSQL("DELETE FROM parking"); - d->runSQL("DELETE FROM groundnet_edge"); - d->runSQL("DELETE FROM stat_cache"); - - // initialise the root octree node - d->runSQL("INSERT INTO octree (rowid, children) VALUES (1, 0)"); + d->close(); // completely close the sqlite object + d->path.remove(); // remove the file on disk + d->init(); // start again from scratch + // initialise the root octree node + d->runSQL("INSERT INTO octree (rowid, children) VALUES (1, 0)"); + SGTimeStamp st; - st.stamp(); - - airportDBLoad(d->aptDatPath); - SG_LOG(SG_NAVCACHE, SG_INFO, "apt.dat load took:" << st.elapsedMSec()); - - metarDataLoad(d->metarDatPath); - stampCacheFile(d->aptDatPath); - stampCacheFile(d->metarDatPath); - - st.stamp(); - loadFixes(d->fixDatPath); - stampCacheFile(d->fixDatPath); - SG_LOG(SG_NAVCACHE, SG_INFO, "fix.dat load took:" << st.elapsedMSec()); - - st.stamp(); - navDBInit(d->navDatPath); - stampCacheFile(d->navDatPath); - SG_LOG(SG_NAVCACHE, SG_INFO, "nav.dat load took:" << st.elapsedMSec()); - - loadCarrierNav(d->carrierDatPath); - stampCacheFile(d->carrierDatPath); - - st.stamp(); - Airway::load(d->airwayDatPath); - stampCacheFile(d->airwayDatPath); - SG_LOG(SG_NAVCACHE, SG_INFO, "awy.dat load took:" << st.elapsedMSec()); - - d->flushDeferredOctreeUpdates(); - - string sceneryPaths = simgear::strutils::join(globals->get_fg_scenery(), ";"); - writeStringProperty("scenery_paths", sceneryPaths); - - d->runSQL("COMMIT"); + { + Transaction txn(this); + + st.stamp(); + airportDBLoad(d->aptDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "apt.dat load took:" << st.elapsedMSec()); + + metarDataLoad(d->metarDatPath); + stampCacheFile(d->aptDatPath); + stampCacheFile(d->metarDatPath); + + st.stamp(); + loadFixes(d->fixDatPath); + stampCacheFile(d->fixDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "fix.dat load took:" << st.elapsedMSec()); + + st.stamp(); + navDBInit(d->navDatPath); + stampCacheFile(d->navDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "nav.dat load took:" << st.elapsedMSec()); + + st.stamp(); + txn.commit(); + SG_LOG(SG_NAVCACHE, SG_INFO, "stage 1 commit took:" << st.elapsedMSec()); + } + +#ifdef SG_WINDOWS + SG_LOG(SG_NAVCACHE, SG_ALERT, "SKIPPING POI load on Windows"); +#else + { + Transaction txn(this); + + st.stamp(); + poiDBInit(d->poiDatPath); + stampCacheFile(d->poiDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "poi.dat load took:" << st.elapsedMSec()); + + st.stamp(); + txn.commit(); + SG_LOG(SG_NAVCACHE, SG_INFO, "POI commit took:" << st.elapsedMSec()); + } +#endif + + { + Transaction txn(this); + loadCarrierNav(d->carrierDatPath); + stampCacheFile(d->carrierDatPath); + + st.stamp(); + Airway::load(d->airwayDatPath); + stampCacheFile(d->airwayDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "awy.dat load took:" << st.elapsedMSec()); + + d->flushDeferredOctreeUpdates(); + + string sceneryPaths = simgear::strutils::join(globals->get_fg_scenery(), ";"); + writeStringProperty("scenery_paths", sceneryPaths); + + st.stamp(); + txn.commit(); + SG_LOG(SG_NAVCACHE, SG_INFO, "final commit took:" << st.elapsedMSec()); + + } + } catch (sg_exception& e) { SG_LOG(SG_NAVCACHE, SG_ALERT, "caught exception rebuilding navCache:" << e.what()); - // abandon the DB transation completely - d->runSQL("ROLLBACK"); } } int NavDataCache::readIntProperty(const string& key) { - d->reset(d->readPropertyQuery); sqlite_bind_stdstring(d->readPropertyQuery, 1, key); + int result = 0; if (d->execSelect(d->readPropertyQuery)) { - return sqlite3_column_int(d->readPropertyQuery, 0); + result = sqlite3_column_int(d->readPropertyQuery, 0); } else { SG_LOG(SG_NAVCACHE, SG_WARN, "readIntProperty: unknown:" << key); - return 0; // no such property } + + d->reset(d->readPropertyQuery); + return result; } double NavDataCache::readDoubleProperty(const string& key) { - d->reset(d->readPropertyQuery); sqlite_bind_stdstring(d->readPropertyQuery, 1, key); + double result = 0.0; if (d->execSelect(d->readPropertyQuery)) { - return sqlite3_column_double(d->readPropertyQuery, 0); + result = sqlite3_column_double(d->readPropertyQuery, 0); } else { SG_LOG(SG_NAVCACHE, SG_WARN, "readDoubleProperty: unknown:" << key); - return 0.0; // no such property } + + d->reset(d->readPropertyQuery); + return result; } string NavDataCache::readStringProperty(const string& key) { - d->reset(d->readPropertyQuery); sqlite_bind_stdstring(d->readPropertyQuery, 1, key); + string result; if (d->execSelect(d->readPropertyQuery)) { - return (char*) sqlite3_column_text(d->readPropertyQuery, 0); + result = (char*) sqlite3_column_text(d->readPropertyQuery, 0); } else { SG_LOG(SG_NAVCACHE, SG_WARN, "readStringProperty: unknown:" << key); - return string(); // no such property } + + d->reset(d->readPropertyQuery); + return result; } void NavDataCache::writeIntProperty(const string& key, int value) @@ -1250,48 +1326,42 @@ void NavDataCache::writeIntProperty(const string& key, int value) void NavDataCache::writeStringProperty(const string& key, const string& value) { - d->reset(d->clearProperty); sqlite_bind_stdstring(d->clearProperty, 1, key); d->execUpdate(d->clearProperty); - d->reset(d->writePropertyQuery); sqlite_bind_stdstring(d->writePropertyQuery, 1, key); sqlite_bind_stdstring(d->writePropertyQuery, 2, value); - d->execSelect(d->writePropertyQuery); + d->execUpdate(d->writePropertyQuery); } void NavDataCache::writeDoubleProperty(const string& key, const double& value) { - d->reset(d->clearProperty); sqlite_bind_stdstring(d->clearProperty, 1, key); d->execUpdate(d->clearProperty); - d->reset(d->writePropertyQuery); sqlite_bind_stdstring(d->writePropertyQuery, 1, key); sqlite3_bind_double(d->writePropertyQuery, 2, value); - d->execSelect(d->writePropertyQuery); + d->execUpdate(d->writePropertyQuery); } string_list NavDataCache::readStringListProperty(const string& key) { - d->reset(d->readPropertyQuery); sqlite_bind_stdstring(d->readPropertyQuery, 1, key); string_list result; while (d->stepSelect(d->readPropertyQuery)) { result.push_back((char*) sqlite3_column_text(d->readPropertyQuery, 0)); } + d->reset(d->readPropertyQuery); return result; } void NavDataCache::writeStringListProperty(const string& key, const string_list& values) { - d->reset(d->clearProperty); sqlite_bind_stdstring(d->clearProperty, 1, key); d->execUpdate(d->clearProperty); BOOST_FOREACH(string value, values) { - d->reset(d->writePropertyMulti); sqlite_bind_stdstring(d->writePropertyMulti, 1, key); sqlite_bind_stdstring(d->writePropertyMulti, 2, value); d->execInsert(d->writePropertyMulti); @@ -1300,40 +1370,11 @@ void NavDataCache::writeStringListProperty(const string& key, const string_list& bool NavDataCache::isCachedFileModified(const SGPath& path) const { - if (!path.exists()) { - throw sg_io_exception("isCachedFileModified: Missing file:" + path.str()); - } - - d->reset(d->statCacheCheck); - sqlite_bind_temp_stdstring(d->statCacheCheck, 1, path.str()); - if (d->execSelect(d->statCacheCheck)) { - time_t modtime = sqlite3_column_int64(d->statCacheCheck, 0); - time_t delta = std::labs(modtime - path.modTime()); - if (delta != 0) - { - SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: rebuild required for " << path << ". Timestamps: " << modtime << " != " << path.modTime()); - if (delta < 30) - { - // File system time stamp has slightly changed. It's unlikely that the user has managed to change a file, start fgfs, - // and then changed file again within x seconds - so it's suspicious... - SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache: suspicious file timestamp change. Please report this to FlightGear. " - << "Delta: " << delta << ", Timestamps: " << modtime << ", " << path.modTime() << ", path: " << path); - } - } - else - { - SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: no rebuild required for " << path); - } - return (delta != 0); - } else { - SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: initial build required for " << path); - return true; - } + return d->isCachedFileModified(path, false); } void NavDataCache::stampCacheFile(const SGPath& path) { - d->reset(d->stampFileCache); sqlite_bind_temp_stdstring(d->stampFileCache, 1, path.str()); sqlite3_bind_int64(d->stampFileCache, 2, path.modTime()); d->execInsert(d->stampFileCache); @@ -1341,20 +1382,74 @@ void NavDataCache::stampCacheFile(const SGPath& path) void NavDataCache::beginTransaction() { - d->runSQL("BEGIN"); + if (d->transactionLevel == 0) { + d->transactionAborted = false; + d->stepSelect(d->beginTransactionStmt); + sqlite3_reset(d->beginTransactionStmt); + } + + ++d->transactionLevel; } void NavDataCache::commitTransaction() { - d->runSQL("COMMIT"); + assert(d->transactionLevel > 0); + if (--d->transactionLevel == 0) { + // if a nested transaction aborted, we might end up here, but must + // still abort the entire transaction. That's bad, but safer than + // committing. + sqlite3_stmt_ptr q = d->transactionAborted ? d->rollbackTransactionStmt : d->commitTransactionStmt; + + int retries = 0; + int result; + while (retries < MAX_RETRIES) { + result = sqlite3_step(q); + if (result == SQLITE_DONE) { + break; + } + + // see http://www.sqlite.org/c3ref/get_autocommit.html for a hint + // what's going on here: autocommit in inactive inside BEGIN, so if + // it's active, the DB was rolled-back + if (sqlite3_get_autocommit(d->db)) { + SG_LOG(SG_NAVCACHE, SG_ALERT, "commit: was rolled back!" << retries); + d->transactionAborted = true; + break; + } + + if (result != SQLITE_BUSY) { + break; + } + + SGTimeStamp::sleepForMSec(++retries * 10); + SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache contention on commit, will retry:" << retries); + } // of retry loop for DB busy + + string errMsg; + if (result != SQLITE_DONE) { + errMsg = sqlite3_errmsg(d->db); + SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " for " << result + << " while running:\n\t" << sqlite3_sql(q)); + } + + sqlite3_reset(q); + } } void NavDataCache::abortTransaction() { - d->runSQL("ROLLBACK"); + SG_LOG(SG_NAVCACHE, SG_WARN, "NavCache: aborting transaction"); + + assert(d->transactionLevel > 0); + if (--d->transactionLevel == 0) { + d->stepSelect(d->rollbackTransactionStmt); + sqlite3_reset(d->rollbackTransactionStmt); + } + + d->transactionAborted = true; } -FGPositioned* NavDataCache::loadById(PositionedID rowid) +FGPositionedRef NavDataCache::loadById(PositionedID rowid) { if (rowid == 0) { return NULL; @@ -1366,12 +1461,16 @@ FGPositioned* NavDataCache::loadById(PositionedID rowid) return it->second; // cache it } - d->reset(d->loadPositioned); - sqlite3_bind_int64(d->loadPositioned, 1, rowid); - FGPositioned* pos = d->loadFromStmt(d->loadPositioned); - + sqlite3_int64 aptId; + FGPositioned* pos = d->loadById(rowid, aptId); d->cache.insert(it, PositionedCache::value_type(rowid, pos)); d->cacheMisses++; + + // when we loaded an ILS, we must apply per-airport changes + if ((pos->type() == FGPositioned::ILS) && (aptId > 0)) { + FGAirport* apt = FGPositioned::loadById(aptId); + apt->validateILSData(); + } return pos; } @@ -1386,7 +1485,6 @@ PositionedID NavDataCache::insertAirport(FGPositioned::Type ty, const string& id 0 /* airport */, false /* spatial index */); - d->reset(d->insertAirport); sqlite3_bind_int64(d->insertAirport, 1, rowId); d->execInsert(d->insertAirport); @@ -1402,7 +1500,6 @@ void NavDataCache::updatePosition(PositionedID item, const SGGeod &pos) SGVec3d cartPos(SGVec3d::fromGeod(pos)); - d->reset(d->setAirportPos); sqlite3_bind_int(d->setAirportPos, 1, item); sqlite3_bind_double(d->setAirportPos, 2, pos.getLongitudeDeg()); sqlite3_bind_double(d->setAirportPos, 3, pos.getLatitudeDeg()); @@ -1441,11 +1538,10 @@ NavDataCache::insertRunway(FGPositioned::Type ty, const string& ident, { // only runways are spatially indexed; don't bother indexing taxiways // or pavements - bool spatialIndex = (ty == FGPositioned::RUNWAY); + bool spatialIndex = ( ty == FGPositioned::RUNWAY || ty == FGPositioned::HELIPAD); sqlite3_int64 rowId = d->insertPositioned(ty, cleanRunwayNo(ident), "", pos, apt, spatialIndex); - d->reset(d->insertRunway); sqlite3_bind_int64(d->insertRunway, 1, rowId); sqlite3_bind_double(d->insertRunway, 2, heading); sqlite3_bind_double(d->insertRunway, 3, length); @@ -1459,13 +1555,11 @@ NavDataCache::insertRunway(FGPositioned::Type ty, const string& ident, void NavDataCache::setRunwayReciprocal(PositionedID runway, PositionedID recip) { - d->reset(d->setRunwayReciprocal); sqlite3_bind_int64(d->setRunwayReciprocal, 1, runway); sqlite3_bind_int64(d->setRunwayReciprocal, 2, recip); d->execUpdate(d->setRunwayReciprocal); // and the opposite direction too! - d->reset(d->setRunwayReciprocal); sqlite3_bind_int64(d->setRunwayReciprocal, 2, runway); sqlite3_bind_int64(d->setRunwayReciprocal, 1, recip); d->execUpdate(d->setRunwayReciprocal); @@ -1473,32 +1567,15 @@ void NavDataCache::setRunwayReciprocal(PositionedID runway, PositionedID recip) void NavDataCache::setRunwayILS(PositionedID runway, PositionedID ils) { - d->reset(d->setRunwayILS); sqlite3_bind_int64(d->setRunwayILS, 1, runway); sqlite3_bind_int64(d->setRunwayILS, 2, ils); d->execUpdate(d->setRunwayILS); -} - -void NavDataCache::updateRunwayThreshold(PositionedID runwayID, const SGGeod &aThreshold, - double aHeading, double aDisplacedThreshold, - double aStopway) -{ -// update the runway information - d->reset(d->updateRunwayThreshold); - sqlite3_bind_int64(d->updateRunwayThreshold, 1, runwayID); - sqlite3_bind_double(d->updateRunwayThreshold, 2, aHeading); - sqlite3_bind_double(d->updateRunwayThreshold, 3, aDisplacedThreshold); - sqlite3_bind_double(d->updateRunwayThreshold, 4, aStopway); - d->execUpdate(d->updateRunwayThreshold); - -// compute the new runway center, based on the threshold lat/lon and length, - double offsetFt = (0.5 * d->runwayLengthFt(runwayID)); - SGGeod newCenter; - double dummy; - SGGeodesy::direct(aThreshold, aHeading, offsetFt * SG_FEET_TO_METER, newCenter, dummy); -// now update the positional data - updatePosition(runwayID, newCenter); + // and the in-memory one + if (d->cache.find(runway) != d->cache.end()) { + FGRunway* instance = (FGRunway*) d->cache[runway].ptr(); + instance->setILS(ils); + } } PositionedID @@ -1514,22 +1591,27 @@ NavDataCache::insertNavaid(FGPositioned::Type ty, const string& ident, sqlite3_int64 rowId = d->insertPositioned(ty, ident, name, pos, apt, spatialIndex); - d->reset(d->insertNavaid); sqlite3_bind_int64(d->insertNavaid, 1, rowId); sqlite3_bind_int(d->insertNavaid, 2, freq); sqlite3_bind_int(d->insertNavaid, 3, range); sqlite3_bind_double(d->insertNavaid, 4, multiuse); sqlite3_bind_int64(d->insertNavaid, 5, runway); + sqlite3_bind_int64(d->insertNavaid, 6, 0); return d->execInsert(d->insertNavaid); } -void NavDataCache::updateILS(PositionedID ils, const SGGeod& newPos, double aHdg) +void NavDataCache::setNavaidColocated(PositionedID navaid, PositionedID colocatedDME) { - d->reset(d->updateILS); - sqlite3_bind_int64(d->updateILS, 1, ils); - sqlite3_bind_double(d->updateILS, 2, aHdg); - d->execUpdate(d->updateILS); - updatePosition(ils, newPos); + // Update DB entries... + sqlite3_bind_int64(d->setNavaidColocated, 1, navaid); + sqlite3_bind_int64(d->setNavaidColocated, 2, colocatedDME); + d->execUpdate(d->setNavaidColocated); + + // ...and the in-memory copy of the navrecord + if (d->cache.find(navaid) != d->cache.end()) { + FGNavRecord* rec = (FGNavRecord*) d->cache[navaid].get(); + rec->setColocatedDME(colocatedDME); + } } PositionedID NavDataCache::insertCommStation(FGPositioned::Type ty, @@ -1537,7 +1619,6 @@ PositionedID NavDataCache::insertCommStation(FGPositioned::Type ty, PositionedID apt) { sqlite3_int64 rowId = d->insertPositioned(ty, "", name, pos, apt, true); - d->reset(d->insertCommStation); sqlite3_bind_int64(d->insertCommStation, 1, rowId); sqlite3_bind_int(d->insertCommStation, 2, freq); sqlite3_bind_int(d->insertCommStation, 3, range); @@ -1549,36 +1630,48 @@ PositionedID NavDataCache::insertFix(const std::string& ident, const SGGeod& aPo return d->insertPositioned(FGPositioned::FIX, ident, string(), aPos, 0, true); } -PositionedID NavDataCache::createUserWaypoint(const std::string& ident, const SGGeod& aPos) +PositionedID NavDataCache::createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos) { - return d->insertPositioned(FGPositioned::WAYPOINT, ident, string(), aPos, 0, + return d->insertPositioned(ty, ident, string(), aPos, 0, true /* spatial index */); } + +bool NavDataCache::removePOI(FGPositioned::Type ty, const std::string& aIdent) +{ + d->removePositionedWithIdent(ty, aIdent); + // should remove from the live cache too? + + return true; +} void NavDataCache::setAirportMetar(const string& icao, bool hasMetar) { - d->reset(d->setAirportMetar); sqlite_bind_stdstring(d->setAirportMetar, 1, icao); sqlite3_bind_int(d->setAirportMetar, 2, hasMetar); d->execUpdate(d->setAirportMetar); } -FGPositioned::List NavDataCache::findAllWithIdent(const string& s, - FGPositioned::Filter* filter, bool exact) +//------------------------------------------------------------------------------ +FGPositionedList NavDataCache::findAllWithIdent( const string& s, + FGPositioned::Filter* filter, + bool exact ) { return d->findAllByString(s, "ident", filter, exact); } -FGPositioned::List NavDataCache::findAllWithName(const string& s, - FGPositioned::Filter* filter, bool exact) +//------------------------------------------------------------------------------ +FGPositionedList NavDataCache::findAllWithName( const string& s, + FGPositioned::Filter* filter, + bool exact ) { return d->findAllByString(s, "name", filter, exact); } - -FGPositionedRef NavDataCache::findClosestWithIdent(const string& aIdent, - const SGGeod& aPos, FGPositioned::Filter* aFilter) + +//------------------------------------------------------------------------------ +FGPositionedRef NavDataCache::findClosestWithIdent( const string& aIdent, + const SGGeod& aPos, + FGPositioned::Filter* aFilter ) { - d->reset(d->findClosestWithIdent); sqlite_bind_stdstring(d->findClosestWithIdent, 1, aIdent); if (aFilter) { sqlite3_bind_int(d->findClosestWithIdent, 2, aFilter->minType()); @@ -1593,30 +1686,34 @@ FGPositionedRef NavDataCache::findClosestWithIdent(const string& aIdent, sqlite3_bind_double(d->findClosestWithIdent, 5, cartPos.y()); sqlite3_bind_double(d->findClosestWithIdent, 6, cartPos.z()); + FGPositionedRef result; + while (d->stepSelect(d->findClosestWithIdent)) { FGPositioned* pos = loadById(sqlite3_column_int64(d->findClosestWithIdent, 0)); if (aFilter && !aFilter->pass(pos)) { continue; } - return pos; + result = pos; + break; } - return NULL; // no matches at all + d->reset(d->findClosestWithIdent); + return result; } int NavDataCache::getOctreeBranchChildren(int64_t octreeNodeId) { - d->reset(d->getOctreeChildren); sqlite3_bind_int64(d->getOctreeChildren, 1, octreeNodeId); d->execSelect1(d->getOctreeChildren); - return sqlite3_column_int(d->getOctreeChildren, 0); + int children = sqlite3_column_int(d->getOctreeChildren, 0); + d->reset(d->getOctreeChildren); + return children; } void NavDataCache::defineOctreeNode(Octree::Branch* pr, Octree::Node* nd) { - d->reset(d->insertOctree); sqlite3_bind_int64(d->insertOctree, 1, nd->guid()); d->execInsert(d->insertOctree); @@ -1626,7 +1723,6 @@ void NavDataCache::defineOctreeNode(Octree::Branch* pr, Octree::Node* nd) // lowest three bits of node ID are 0..7 index of the child in the parent int childIndex = nd->guid() & 0x07; - d->reset(d->updateOctreeChildren); sqlite3_bind_int64(d->updateOctreeChildren, 1, pr->guid()); // mask has bit N set where child N exists int childMask = 1 << childIndex; @@ -1638,7 +1734,6 @@ void NavDataCache::defineOctreeNode(Octree::Branch* pr, Octree::Node* nd) TypedPositionedVec NavDataCache::getOctreeLeafChildren(int64_t octreeNodeId) { - d->reset(d->getOctreeLeafChildren); sqlite3_bind_int64(d->getOctreeLeafChildren, 1, octreeNodeId); TypedPositionedVec r; @@ -1649,6 +1744,7 @@ NavDataCache::getOctreeLeafChildren(int64_t octreeNodeId) sqlite3_column_int64(d->getOctreeLeafChildren, 0))); } + d->reset(d->getOctreeLeafChildren); return r; } @@ -1660,14 +1756,19 @@ NavDataCache::getOctreeLeafChildren(int64_t octreeNodeId) */ char** NavDataCache::searchAirportNamesAndIdents(const std::string& aFilter) { - d->reset(d->searchAirports); - string s = "%" + aFilter + "%"; - sqlite_bind_stdstring(d->searchAirports, 1, s); - + sqlite3_stmt_ptr stmt; unsigned int numMatches = 0, numAllocated = 16; - char** result = (char**) malloc(sizeof(char*) * numAllocated); + string searchTerm("%" + aFilter + "%"); + if (aFilter.empty()) { + stmt = d->getAllAirports; + numAllocated = 4096; // start much larger for all airports + } else { + stmt = d->searchAirports; + sqlite_bind_stdstring(stmt, 1, searchTerm); + } - while (d->stepSelect(d->searchAirports)) { + char** result = (char**) malloc(sizeof(char*) * numAllocated); + while (d->stepSelect(stmt)) { if ((numMatches + 1) >= numAllocated) { numAllocated <<= 1; // double in size! // reallocate results array @@ -1685,18 +1786,18 @@ char** NavDataCache::searchAirportNamesAndIdents(const std::string& aFilter) // which gives a grand total of 7 + name-length + icao-length. // note the ident can be three letters (non-ICAO local strip), four // (default ICAO) or more (extended format ICAO) - int nameLength = sqlite3_column_bytes(d->searchAirports, 1); - int icaoLength = sqlite3_column_bytes(d->searchAirports, 0); + int nameLength = sqlite3_column_bytes(stmt, 1); + int icaoLength = sqlite3_column_bytes(stmt, 0); char* entry = (char*) malloc(7 + nameLength + icaoLength); char* dst = entry; *dst++ = ' '; - memcpy(dst, sqlite3_column_text(d->searchAirports, 1), nameLength); + memcpy(dst, sqlite3_column_text(stmt, 1), nameLength); dst += nameLength; *dst++ = ' '; *dst++ = ' '; *dst++ = ' '; *dst++ = '('; - memcpy(dst, sqlite3_column_text(d->searchAirports, 0), icaoLength); + memcpy(dst, sqlite3_column_text(stmt, 0), icaoLength); dst += icaoLength; *dst++ = ')'; *dst++ = 0; @@ -1705,13 +1806,13 @@ char** NavDataCache::searchAirportNamesAndIdents(const std::string& aFilter) } result[numMatches] = NULL; // end of list marker + d->reset(stmt); return result; } FGPositionedRef NavDataCache::findCommByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filter* aFilter) { - d->reset(d->findCommByFreq); sqlite3_bind_int(d->findCommByFreq, 1, freqKhz); if (aFilter) { sqlite3_bind_int(d->findCommByFreq, 2, aFilter->minType()); @@ -1725,6 +1826,7 @@ NavDataCache::findCommByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filt sqlite3_bind_double(d->findCommByFreq, 4, cartPos.x()); sqlite3_bind_double(d->findCommByFreq, 5, cartPos.y()); sqlite3_bind_double(d->findCommByFreq, 6, cartPos.z()); + FGPositionedRef result; while (d->execSelect(d->findCommByFreq)) { FGPositioned* p = loadById(sqlite3_column_int64(d->findCommByFreq, 0)); @@ -1732,16 +1834,17 @@ NavDataCache::findCommByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filt continue; } - return p; + result = p; + break; } - return NULL; + d->reset(d->findCommByFreq); + return result; } PositionedIDVec NavDataCache::findNavaidsByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filter* aFilter) { - d->reset(d->findNavsByFreq); sqlite3_bind_int(d->findNavsByFreq, 1, freqKhz); if (aFilter) { sqlite3_bind_int(d->findNavsByFreq, 2, aFilter->minType()); @@ -1762,7 +1865,6 @@ NavDataCache::findNavaidsByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::F PositionedIDVec NavDataCache::findNavaidsByFreq(int freqKhz, FGPositioned::Filter* aFilter) { - d->reset(d->findNavsByFreqNoPos); sqlite3_bind_int(d->findNavsByFreqNoPos, 1, freqKhz); if (aFilter) { sqlite3_bind_int(d->findNavsByFreqNoPos, 2, aFilter->minType()); @@ -1783,7 +1885,6 @@ NavDataCache::airportItemsOfType(PositionedID apt,FGPositioned::Type ty, maxTy = ty; // single-type range } - d->reset(d->getAirportItems); sqlite3_bind_int64(d->getAirportItems, 1, apt); sqlite3_bind_int(d->getAirportItems, 2, ty); sqlite3_bind_int(d->getAirportItems, 3, maxTy); @@ -1795,16 +1896,17 @@ PositionedID NavDataCache::airportItemWithIdent(PositionedID apt, FGPositioned::Type ty, const std::string& ident) { - d->reset(d->getAirportItemByIdent); sqlite3_bind_int64(d->getAirportItemByIdent, 1, apt); sqlite_bind_stdstring(d->getAirportItemByIdent, 2, ident); sqlite3_bind_int(d->getAirportItemByIdent, 3, ty); + PositionedID result = 0; - if (!d->execSelect(d->getAirportItemByIdent)) { - return 0; + if (d->execSelect(d->getAirportItemByIdent)) { + result = sqlite3_column_int64(d->getAirportItemByIdent, 0); } - return sqlite3_column_int64(d->getAirportItemByIdent, 0); + d->reset(d->getAirportItemByIdent); + return result; } AirportRunwayPair @@ -1820,55 +1922,62 @@ NavDataCache::findAirportRunway(const std::string& aName) return AirportRunwayPair(); } - d->reset(d->findAirportRunway); + AirportRunwayPair result; sqlite_bind_stdstring(d->findAirportRunway, 1, parts[0]); - sqlite_bind_stdstring(d->findAirportRunway, 2, parts[1]); - if (!d->execSelect(d->findAirportRunway)) { + sqlite_bind_stdstring(d->findAirportRunway, 2, cleanRunwayNo(parts[1])); + + if (d->execSelect(d->findAirportRunway)) { + result = AirportRunwayPair(sqlite3_column_int64(d->findAirportRunway, 0), + sqlite3_column_int64(d->findAirportRunway, 1)); + + } else { SG_LOG(SG_NAVCACHE, SG_WARN, "findAirportRunway: unknown airport/runway:" << aName); - return AirportRunwayPair(); } - // success, extract the IDs and continue - return AirportRunwayPair(sqlite3_column_int64(d->findAirportRunway, 0), - sqlite3_column_int64(d->findAirportRunway, 1)); + d->reset(d->findAirportRunway); + return result; } PositionedID -NavDataCache::findILS(PositionedID airport, const string& runway, const string& navIdent) +NavDataCache::findILS(PositionedID airport, const string& aRunway, const string& navIdent) { - d->reset(d->findILS); + string runway(cleanRunwayNo(aRunway)); + sqlite_bind_stdstring(d->findILS, 1, navIdent); sqlite3_bind_int64(d->findILS, 2, airport); sqlite_bind_stdstring(d->findILS, 3, runway); - - if (!d->execSelect(d->findILS)) { - return 0; + PositionedID result = 0; + if (d->execSelect(d->findILS)) { + result = sqlite3_column_int64(d->findILS, 0); } - return sqlite3_column_int64(d->findILS, 0); + d->reset(d->findILS); + return result; } int NavDataCache::findAirway(int network, const string& aName) { - d->reset(d->findAirway); sqlite3_bind_int(d->findAirway, 1, network); sqlite_bind_stdstring(d->findAirway, 2, aName); + + int airway = 0; if (d->execSelect(d->findAirway)) { // already exists - return sqlite3_column_int(d->findAirway, 0); + airway = sqlite3_column_int(d->findAirway, 0); + } else { + sqlite_bind_stdstring(d->insertAirway, 1, aName); + sqlite3_bind_int(d->insertAirway, 2, network); + airway = d->execInsert(d->insertAirway); } - d->reset(d->insertAirway); - sqlite_bind_stdstring(d->insertAirway, 1, aName); - sqlite3_bind_int(d->insertAirway, 2, network); - return d->execInsert(d->insertAirway); + d->reset(d->findAirway); + return airway; } void NavDataCache::insertEdge(int network, int airwayID, PositionedID from, PositionedID to) { // assume all edges are bidirectional for the moment for (int i=0; i<2; ++i) { - d->reset(d->insertAirwayEdge); sqlite3_bind_int(d->insertAirwayEdge, 1, network); sqlite3_bind_int(d->insertAirwayEdge, 2, airwayID); sqlite3_bind_int64(d->insertAirwayEdge, 3, from); @@ -1881,16 +1990,16 @@ void NavDataCache::insertEdge(int network, int airwayID, PositionedID from, Posi bool NavDataCache::isInAirwayNetwork(int network, PositionedID pos) { - d->reset(d->isPosInAirway); sqlite3_bind_int(d->isPosInAirway, 1, network); sqlite3_bind_int64(d->isPosInAirway, 2, pos); bool ok = d->execSelect(d->isPosInAirway); + d->reset(d->isPosInAirway); + return ok; } AirwayEdgeVec NavDataCache::airwayEdgesFrom(int network, PositionedID pos) { - d->reset(d->airwayEdgesFrom); sqlite3_bind_int(d->airwayEdgesFrom, 1, network); sqlite3_bind_int64(d->airwayEdgesFrom, 2, pos); @@ -1901,19 +2010,23 @@ AirwayEdgeVec NavDataCache::airwayEdgesFrom(int network, PositionedID pos) sqlite3_column_int64(d->airwayEdgesFrom, 1) )); } + + d->reset(d->airwayEdgesFrom); return result; } PositionedID NavDataCache::findNavaidForRunway(PositionedID runway, FGPositioned::Type ty) { - d->reset(d->findNavaidForRunway); sqlite3_bind_int64(d->findNavaidForRunway, 1, runway); sqlite3_bind_int(d->findNavaidForRunway, 2, ty); - if (!d->execSelect(d->findNavaidForRunway)) { - return 0; + + PositionedID result = 0; + if (d->execSelect(d->findNavaidForRunway)) { + result = sqlite3_column_int64(d->findNavaidForRunway, 0); } - return sqlite3_column_int64(d->findNavaidForRunway, 0); + d->reset(d->findNavaidForRunway); + return result; } PositionedID @@ -1926,13 +2039,11 @@ NavDataCache::insertParking(const std::string& name, const SGGeod& aPos, // we need to insert a row into the taxi_node table, otherwise we can't maintain // the appropriate pushback flag. - d->reset(d->insertTaxiNode); sqlite3_bind_int64(d->insertTaxiNode, 1, rowId); sqlite3_bind_int(d->insertTaxiNode, 2, 0); sqlite3_bind_int(d->insertTaxiNode, 3, 0); d->execInsert(d->insertTaxiNode); - d->reset(d->insertParkingPos); sqlite3_bind_int64(d->insertParkingPos, 1, rowId); sqlite3_bind_double(d->insertParkingPos, 2, aHeading); sqlite3_bind_int(d->insertParkingPos, 3, aRadius); @@ -1943,7 +2054,6 @@ NavDataCache::insertParking(const std::string& name, const SGGeod& aPos, void NavDataCache::setParkingPushBackRoute(PositionedID parking, PositionedID pushBackNode) { - d->reset(d->setParkingPushBack); sqlite3_bind_int64(d->setParkingPushBack, 1, parking); sqlite3_bind_int64(d->setParkingPushBack, 2, pushBackNode); d->execUpdate(d->setParkingPushBack); @@ -1953,7 +2063,6 @@ PositionedID NavDataCache::insertTaxiNode(const SGGeod& aPos, PositionedID aAirport, int aHoldType, bool aOnRunway) { sqlite3_int64 rowId = d->insertPositioned(FGPositioned::TAXI_NODE, string(), string(), aPos, aAirport, false); - d->reset(d->insertTaxiNode); sqlite3_bind_int64(d->insertTaxiNode, 1, rowId); sqlite3_bind_int(d->insertTaxiNode, 2, aHoldType); sqlite3_bind_int(d->insertTaxiNode, 3, aOnRunway); @@ -1962,7 +2071,6 @@ NavDataCache::insertTaxiNode(const SGGeod& aPos, PositionedID aAirport, int aHol void NavDataCache::insertGroundnetEdge(PositionedID aAirport, PositionedID from, PositionedID to) { - d->reset(d->insertTaxiEdge); sqlite3_bind_int64(d->insertTaxiEdge, 1, aAirport); sqlite3_bind_int64(d->insertTaxiEdge, 2, from); sqlite3_bind_int64(d->insertTaxiEdge, 3, to); @@ -1972,14 +2080,12 @@ void NavDataCache::insertGroundnetEdge(PositionedID aAirport, PositionedID from, PositionedIDVec NavDataCache::groundNetNodes(PositionedID aAirport, bool onlyPushback) { sqlite3_stmt_ptr q = onlyPushback ? d->airportPushbackNodes : d->airportTaxiNodes; - d->reset(q); sqlite3_bind_int64(q, 1, aAirport); return d->selectIds(q); } void NavDataCache::markGroundnetAsPushback(PositionedID nodeId) { - d->reset(d->markTaxiNodeAsPushback); sqlite3_bind_int64(d->markTaxiNodeAsPushback, 1, nodeId); d->execUpdate(d->markTaxiNodeAsPushback); } @@ -1995,7 +2101,6 @@ PositionedID NavDataCache::findGroundNetNode(PositionedID airport, const SGGeod& bool onRunway, FGRunway* aRunway) { sqlite3_stmt_ptr q = onRunway ? d->findNearestRunwayTaxiNode : d->findNearestTaxiNode; - d->reset(q); sqlite3_bind_int64(q, 1, airport); SGVec3d cartPos(SGVec3d::fromGeod(aPos)); @@ -2003,27 +2108,30 @@ PositionedID NavDataCache::findGroundNetNode(PositionedID airport, const SGGeod& sqlite3_bind_double(q, 3, cartPos.y()); sqlite3_bind_double(q, 4, cartPos.z()); + PositionedID result = 0; while (d->execSelect(q)) { PositionedID id = sqlite3_column_int64(q, 0); if (!aRunway) { - return id; + result = id; + break; } // ensure found node lies on the runway FGPositionedRef node = loadById(id); double course = SGGeodesy::courseDeg(node->geod(), aRunway->end()); if (fabs(headingDifferenceDeg(course, aRunway->headingDeg())) < 3.0 ) { - return id; + result = id; + break; } } - return 0; + d->reset(q); + return result; } PositionedIDVec NavDataCache::groundNetEdgesFrom(PositionedID pos, bool onlyPushback) { sqlite3_stmt_ptr q = onlyPushback ? d->pushbackEdgesFrom : d->taxiEdgesFrom; - d->reset(q); sqlite3_bind_int64(q, 1, pos); return d->selectIds(q); } @@ -2031,7 +2139,6 @@ PositionedIDVec NavDataCache::groundNetEdgesFrom(PositionedID pos, bool onlyPush PositionedIDVec NavDataCache::findAirportParking(PositionedID airport, const std::string& flightType, int radius) { - d->reset(d->findAirportParking); sqlite3_bind_int64(d->findAirportParking, 1, airport); sqlite3_bind_int(d->findAirportParking, 2, radius); sqlite_bind_stdstring(d->findAirportParking, 3, flightType); @@ -2046,9 +2153,10 @@ void NavDataCache::dropGroundnetFor(PositionedID aAirport) sqlite3_bind_int64(q, 2, aAirport); d->execUpdate(q); - q = d->prepare("DELETE FROM taxi_node WHERE rowid IN (SELECT rowid FROM positioned WHERE type=?1 AND airport=?2)"); + q = d->prepare("DELETE FROM taxi_node WHERE rowid IN (SELECT rowid FROM positioned WHERE (type=?1 OR type=?2) AND airport=?3)"); sqlite3_bind_int(q, 1, FGPositioned::TAXI_NODE); - sqlite3_bind_int64(q, 2, aAirport); + sqlite3_bind_int(q, 2, FGPositioned::PARKING); + sqlite3_bind_int64(q, 3, aAirport); d->execUpdate(q); q = d->prepare("DELETE FROM positioned WHERE (type=?1 OR type=?2) AND airport=?3"); @@ -2062,5 +2170,51 @@ void NavDataCache::dropGroundnetFor(PositionedID aAirport) d->execUpdate(q); } +void NavDataCache::dropAllGroundnets() +{ + SG_LOG(SG_NAVCACHE, SG_INFO, "dropping ground-net data"); + beginTransaction(); + d->runSQL("DELETE FROM groundnet_edge"); + d->runSQL("DELETE FROM parking"); + d->runSQL("DELETE FROM taxi_node"); + + sqlite3_stmt_ptr q = d->prepare("DELETE FROM positioned WHERE (type=?1 OR type=?2)"); + sqlite3_bind_int(q, 1, FGPositioned::TAXI_NODE); + sqlite3_bind_int(q, 2, FGPositioned::PARKING); + d->execUpdate(q); + commitTransaction(); +} + +bool NavDataCache::isReadOnly() const +{ + return d->readOnly; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Transaction RAII object + +NavDataCache::Transaction::Transaction(NavDataCache* cache) : + _instance(cache), + _committed(false) +{ + assert(cache); + _instance->beginTransaction(); +} + +NavDataCache::Transaction::~Transaction() +{ + if (!_committed) { + SG_LOG(SG_NAVCACHE, SG_INFO, "aborting cache transaction!"); + _instance->abortTransaction(); + } +} + +void NavDataCache::Transaction::commit() +{ + assert(!_committed); + _committed = true; + _instance->commitTransaction(); +} + } // of namespace flightgear