2016-04-20 12:12:38 +00:00
# include "nar-info-disk-cache.hh"
# include "sync.hh"
# include "sqlite.hh"
2018-04-02 11:45:18 +00:00
# include "globals.hh"
2016-04-20 12:12:38 +00:00
# include <sqlite3.h>
2021-05-06 14:45:09 +00:00
# include <nlohmann/json.hpp>
2016-04-20 12:12:38 +00:00
namespace nix {
static const char * schema = R " sql(
create table if not exists BinaryCaches (
id integer primary key autoincrement not null ,
url text unique not null ,
timestamp integer not null ,
storeDir text not null ,
wantMassQuery integer not null ,
priority integer not null
) ;
create table if not exists NARs (
cache integer not null ,
2016-04-21 15:53:47 +00:00
hashPart text not null ,
2016-06-20 15:39:05 +00:00
namePart text ,
2016-04-20 12:12:38 +00:00
url text ,
compression text ,
fileHash text ,
fileSize integer ,
narHash text ,
narSize integer ,
refs text ,
deriver text ,
sigs text ,
2018-12-12 00:04:34 +00:00
ca text ,
2016-04-20 12:12:38 +00:00
timestamp integer not null ,
2016-06-20 15:39:05 +00:00
present integer not null ,
2016-04-21 15:53:47 +00:00
primary key ( cache , hashPart ) ,
2016-04-20 12:12:38 +00:00
foreign key ( cache ) references BinaryCaches ( id ) on delete cascade
) ;
2021-05-06 14:45:09 +00:00
create table if not exists Realisations (
cache integer not null ,
outputId text not null ,
2021-05-10 15:45:53 +00:00
content blob , - - Json serialisation of the realisation , or null if the realisation is absent
2021-05-06 14:45:09 +00:00
timestamp integer not null ,
primary key ( cache , outputId ) ,
foreign key ( cache ) references BinaryCaches ( id ) on delete cascade
) ;
2017-01-27 14:19:33 +00:00
create table if not exists LastPurge (
dummy text primary key ,
value integer
) ;
2016-04-20 12:12:38 +00:00
) sql " ;
class NarInfoDiskCacheImpl : public NarInfoDiskCache
{
public :
2017-01-27 14:19:33 +00:00
/* How often to purge expired entries from the cache. */
const int purgeInterval = 24 * 3600 ;
2022-06-22 14:49:18 +00:00
/* How long to cache binary cache info (i.e. /nix-cache-info) */
const int cacheInfoTtl = 7 * 24 * 3600 ;
2016-06-01 12:49:12 +00:00
struct Cache
{
int id ;
Path storeDir ;
2016-06-01 13:15:21 +00:00
bool wantMassQuery ;
int priority ;
2016-06-01 12:49:12 +00:00
} ;
2016-04-20 12:12:38 +00:00
struct State
{
SQLite db ;
2021-05-06 14:45:09 +00:00
SQLiteStmt insertCache , queryCache , insertNAR , insertMissingNAR ,
queryNAR , insertRealisation , insertMissingRealisation ,
queryRealisation , purgeCache ;
2016-06-01 12:49:12 +00:00
std : : map < std : : string , Cache > caches ;
2016-04-20 12:12:38 +00:00
} ;
Sync < State > _state ;
NarInfoDiskCacheImpl ( )
{
auto state ( _state . lock ( ) ) ;
2018-12-12 00:04:34 +00:00
Path dbPath = getCacheDir ( ) + " /nix/binary-cache-v6.sqlite " ;
2016-04-20 12:12:38 +00:00
createDirs ( dirOf ( dbPath ) ) ;
2016-08-09 12:27:30 +00:00
state - > db = SQLite ( dbPath ) ;
2016-04-20 12:12:38 +00:00
2020-03-24 13:26:13 +00:00
state - > db . isCache ( ) ;
2016-04-20 12:12:38 +00:00
2016-08-09 12:27:30 +00:00
state - > db . exec ( schema ) ;
2016-04-20 12:12:38 +00:00
state - > insertCache . create ( state - > db ,
" insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?) " ) ;
state - > queryCache . create ( state - > db ,
2022-06-23 18:52:16 +00:00
" select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ? and timestamp > ? " ) ;
2016-04-20 12:12:38 +00:00
state - > insertNAR . create ( state - > db ,
2016-04-21 15:53:47 +00:00
" insert or replace into NARs(cache, hashPart, namePart, url, compression, fileHash, fileSize, narHash, "
2018-12-12 00:04:34 +00:00
" narSize, refs, deriver, sigs, ca, timestamp, present) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1) " ) ;
2016-06-20 15:39:05 +00:00
state - > insertMissingNAR . create ( state - > db ,
" insert or replace into NARs(cache, hashPart, timestamp, present) values (?, ?, ?, 0) " ) ;
2016-04-20 12:12:38 +00:00
state - > queryNAR . create ( state - > db ,
2018-12-12 16:12:41 +00:00
" select present, namePart, url, compression, fileHash, fileSize, narHash, narSize, refs, deriver, sigs, ca from NARs where cache = ? and hashPart = ? and ((present = 0 and timestamp > ?) or (present = 1 and timestamp > ?)) " ) ;
2017-01-27 14:19:33 +00:00
2021-05-06 14:45:09 +00:00
state - > insertRealisation . create ( state - > db ,
R " (
2021-05-10 15:45:53 +00:00
insert or replace into Realisations ( cache , outputId , content , timestamp )
values ( ? , ? , ? , ? )
2021-05-06 14:45:09 +00:00
) " );
state - > insertMissingRealisation . create ( state - > db ,
R " (
2021-05-10 15:45:53 +00:00
insert or replace into Realisations ( cache , outputId , timestamp )
values ( ? , ? , ? )
2021-05-06 14:45:09 +00:00
) " );
state - > queryRealisation . create ( state - > db ,
R " (
2021-05-10 15:45:53 +00:00
select content from Realisations
2021-05-06 14:45:09 +00:00
where cache = ? and outputId = ? and
2021-05-10 15:45:53 +00:00
( ( content is null and timestamp > ? ) or
( content is not null and timestamp > ? ) )
2021-05-06 14:45:09 +00:00
) " );
2017-01-27 14:19:33 +00:00
/* Periodically purge expired entries from the database. */
2017-02-28 12:44:11 +00:00
retrySQLite < void > ( [ & ] ( ) {
auto now = time ( 0 ) ;
SQLiteStmt queryLastPurge ( state - > db , " select value from LastPurge " ) ;
auto queryLastPurge_ ( queryLastPurge . use ( ) ) ;
if ( ! queryLastPurge_ . next ( ) | | queryLastPurge_ . getInt ( 0 ) < now - purgeInterval ) {
SQLiteStmt ( state - > db ,
" delete from NARs where ((present = 0 and timestamp < ?) or (present = 1 and timestamp < ?)) " )
. use ( )
2021-01-18 13:38:31 +00:00
// Use a minimum TTL to prevent --refresh from
// nuking the entire disk cache.
( now - std : : max ( settings . ttlNegativeNarInfoCache . get ( ) , 3600U ) )
( now - std : : max ( settings . ttlPositiveNarInfoCache . get ( ) , 30 * 24 * 3600U ) )
2017-02-28 12:44:11 +00:00
. exec ( ) ;
debug ( " deleted %d entries from the NAR info disk cache " , sqlite3_changes ( state - > db ) ) ;
SQLiteStmt ( state - > db ,
" insert or replace into LastPurge(dummy, value) values ('', ?) " )
. use ( ) ( now ) . exec ( ) ;
}
} ) ;
2016-04-20 12:12:38 +00:00
}
2016-06-01 12:49:12 +00:00
Cache & getCache ( State & state , const std : : string & uri )
2016-04-20 12:12:38 +00:00
{
auto i = state . caches . find ( uri ) ;
if ( i = = state . caches . end ( ) ) abort ( ) ;
return i - > second ;
}
2022-12-21 09:50:40 +00:00
std : : optional < Cache > queryCacheRaw ( State & state , const std : : string & uri )
{
auto i = state . caches . find ( uri ) ;
if ( i = = state . caches . end ( ) ) {
auto queryCache ( state . queryCache . use ( ) ( uri ) ( time ( 0 ) - cacheInfoTtl ) ) ;
if ( ! queryCache . next ( ) )
return std : : nullopt ;
state . caches . emplace ( uri ,
Cache { ( int ) queryCache . getInt ( 0 ) , queryCache . getStr ( 1 ) , queryCache . getInt ( 2 ) ! = 0 , ( int ) queryCache . getInt ( 3 ) } ) ;
}
return getCache ( state , uri ) ;
}
2016-06-01 12:49:12 +00:00
void createCache ( const std : : string & uri , const Path & storeDir , bool wantMassQuery , int priority ) override
2016-04-20 12:12:38 +00:00
{
2017-02-28 12:44:11 +00:00
retrySQLite < void > ( [ & ] ( ) {
auto state ( _state . lock ( ) ) ;
2022-12-21 09:50:40 +00:00
SQLiteTxn txn ( state - > db ) ;
// To avoid the race, we have to check if maybe someone hasn't yet created
// the cache for this URI in the meantime.
auto cache ( queryCacheRaw ( * state , uri ) ) ;
2016-04-20 12:12:38 +00:00
2022-12-21 09:50:40 +00:00
if ( cache )
return ;
2016-04-20 12:12:38 +00:00
2017-02-28 12:44:11 +00:00
state - > insertCache . use ( ) ( uri ) ( time ( 0 ) ) ( storeDir ) ( wantMassQuery ) ( priority ) . exec ( ) ;
assert ( sqlite3_changes ( state - > db ) = = 1 ) ;
state - > caches [ uri ] = Cache { ( int ) sqlite3_last_insert_rowid ( state - > db ) , storeDir , wantMassQuery , priority } ;
2022-12-21 09:50:40 +00:00
txn . commit ( ) ;
2017-02-28 12:44:11 +00:00
} ) ;
2016-04-20 12:12:38 +00:00
}
2019-12-17 16:17:53 +00:00
std : : optional < CacheInfo > cacheExists ( const std : : string & uri ) override
2016-04-20 12:12:38 +00:00
{
2019-12-17 16:17:53 +00:00
return retrySQLite < std : : optional < CacheInfo > > ( [ & ] ( ) - > std : : optional < CacheInfo > {
2017-02-28 12:44:11 +00:00
auto state ( _state . lock ( ) ) ;
2022-12-21 09:50:40 +00:00
auto cache ( queryCacheRaw ( * state , uri ) ) ;
if ( ! cache )
return std : : nullopt ;
2019-12-17 16:17:53 +00:00
return CacheInfo {
2022-12-21 09:50:40 +00:00
. wantMassQuery = cache - > wantMassQuery ,
. priority = cache - > priority
2019-12-17 16:17:53 +00:00
} ;
2017-02-28 12:44:11 +00:00
} ) ;
2016-04-20 12:12:38 +00:00
}
std : : pair < Outcome , std : : shared_ptr < NarInfo > > lookupNarInfo (
2016-04-21 15:53:47 +00:00
const std : : string & uri , const std : : string & hashPart ) override
2016-04-20 12:12:38 +00:00
{
2017-02-28 12:44:11 +00:00
return retrySQLite < std : : pair < Outcome , std : : shared_ptr < NarInfo > > > (
[ & ] ( ) - > std : : pair < Outcome , std : : shared_ptr < NarInfo > > {
auto state ( _state . lock ( ) ) ;
auto & cache ( getCache ( * state , uri ) ) ;
2016-04-20 12:12:38 +00:00
2017-02-28 12:44:11 +00:00
auto now = time ( 0 ) ;
auto queryNAR ( state - > queryNAR . use ( )
( cache . id )
( hashPart )
2018-04-06 10:05:15 +00:00
( now - settings . ttlNegativeNarInfoCache )
( now - settings . ttlPositiveNarInfoCache ) ) ;
2017-02-28 12:44:11 +00:00
if ( ! queryNAR . next ( ) )
return { oUnknown , 0 } ;
2018-12-12 16:12:41 +00:00
if ( ! queryNAR . getInt ( 0 ) )
2017-02-28 12:44:11 +00:00
return { oInvalid , 0 } ;
2018-12-12 16:12:41 +00:00
auto namePart = queryNAR . getStr ( 1 ) ;
2020-08-06 18:31:48 +00:00
auto narInfo = make_ref < NarInfo > (
StorePath ( hashPart + " - " + namePart ) ,
Hash : : parseAnyPrefixed ( queryNAR . getStr ( 6 ) ) ) ;
2018-12-12 16:12:41 +00:00
narInfo - > url = queryNAR . getStr ( 2 ) ;
narInfo - > compression = queryNAR . getStr ( 3 ) ;
if ( ! queryNAR . isNull ( 4 ) )
2020-07-01 22:34:18 +00:00
narInfo - > fileHash = Hash : : parseAnyPrefixed ( queryNAR . getStr ( 4 ) ) ;
2018-12-12 16:12:41 +00:00
narInfo - > fileSize = queryNAR . getInt ( 5 ) ;
narInfo - > narSize = queryNAR . getInt ( 7 ) ;
for ( auto & r : tokenizeString < Strings > ( queryNAR . getStr ( 8 ) , " " ) )
2020-06-16 12:16:39 +00:00
narInfo - > references . insert ( StorePath ( r ) ) ;
2018-12-12 16:12:41 +00:00
if ( ! queryNAR . isNull ( 9 ) )
2020-06-16 12:16:39 +00:00
narInfo - > deriver = StorePath ( queryNAR . getStr ( 9 ) ) ;
2018-12-12 16:12:41 +00:00
for ( auto & sig : tokenizeString < Strings > ( queryNAR . getStr ( 10 ) , " " ) )
2017-02-28 12:44:11 +00:00
narInfo - > sigs . insert ( sig ) ;
2020-06-02 15:00:10 +00:00
narInfo - > ca = parseContentAddressOpt ( queryNAR . getStr ( 11 ) ) ;
2017-02-28 12:44:11 +00:00
return { oValid , narInfo } ;
} ) ;
2016-04-20 12:12:38 +00:00
}
2021-05-06 14:45:09 +00:00
std : : pair < Outcome , std : : shared_ptr < Realisation > > lookupRealisation (
const std : : string & uri , const DrvOutput & id ) override
{
return retrySQLite < std : : pair < Outcome , std : : shared_ptr < Realisation > > > (
[ & ] ( ) - > std : : pair < Outcome , std : : shared_ptr < Realisation > > {
auto state ( _state . lock ( ) ) ;
auto & cache ( getCache ( * state , uri ) ) ;
auto now = time ( 0 ) ;
auto queryRealisation ( state - > queryRealisation . use ( )
( cache . id )
( id . to_string ( ) )
( now - settings . ttlNegativeNarInfoCache )
( now - settings . ttlPositiveNarInfoCache ) ) ;
if ( ! queryRealisation . next ( ) )
return { oUnknown , 0 } ;
2021-05-10 15:45:53 +00:00
if ( queryRealisation . isNull ( 0 ) )
2021-05-06 14:45:09 +00:00
return { oInvalid , 0 } ;
auto realisation =
std : : make_shared < Realisation > ( Realisation : : fromJSON (
2021-05-10 15:45:53 +00:00
nlohmann : : json : : parse ( queryRealisation . getStr ( 0 ) ) ,
2021-05-06 14:45:09 +00:00
" Local disk cache " ) ) ;
return { oValid , realisation } ;
} ) ;
}
2016-04-20 12:12:38 +00:00
void upsertNarInfo (
2016-04-21 15:53:47 +00:00
const std : : string & uri , const std : : string & hashPart ,
2018-09-25 16:54:16 +00:00
std : : shared_ptr < const ValidPathInfo > info ) override
2016-04-20 12:12:38 +00:00
{
2017-02-28 12:44:11 +00:00
retrySQLite < void > ( [ & ] ( ) {
auto state ( _state . lock ( ) ) ;
auto & cache ( getCache ( * state , uri ) ) ;
if ( info ) {
2018-09-25 16:54:16 +00:00
auto narInfo = std : : dynamic_pointer_cast < const NarInfo > ( info ) ;
2017-02-28 12:44:11 +00:00
2019-12-05 18:11:09 +00:00
//assert(hashPart == storePathToHash(info->path));
2017-02-28 12:44:11 +00:00
state - > insertNAR . use ( )
( cache . id )
( hashPart )
2019-12-05 18:11:09 +00:00
( std : : string ( info - > path . name ( ) ) )
2017-02-28 12:44:11 +00:00
( narInfo ? narInfo - > url : " " , narInfo ! = 0 )
( narInfo ? narInfo - > compression : " " , narInfo ! = 0 )
2020-06-19 18:41:33 +00:00
( narInfo & & narInfo - > fileHash ? narInfo - > fileHash - > to_string ( Base32 , true ) : " " , narInfo & & narInfo - > fileHash )
2017-02-28 12:44:11 +00:00
( narInfo ? narInfo - > fileSize : 0 , narInfo ! = 0 & & narInfo - > fileSize )
2020-08-05 18:42:48 +00:00
( info - > narHash . to_string ( Base32 , true ) )
2017-02-28 12:44:11 +00:00
( info - > narSize )
( concatStringsSep ( " " , info - > shortRefs ( ) ) )
2019-12-05 18:11:09 +00:00
( info - > deriver ? std : : string ( info - > deriver - > to_string ( ) ) : " " , ( bool ) info - > deriver )
2017-02-28 12:44:11 +00:00
( concatStringsSep ( " " , info - > sigs ) )
2020-06-02 00:37:43 +00:00
( renderContentAddress ( info - > ca ) )
2017-02-28 12:44:11 +00:00
( time ( 0 ) ) . exec ( ) ;
} else {
state - > insertMissingNAR . use ( )
( cache . id )
( hashPart )
( time ( 0 ) ) . exec ( ) ;
}
} ) ;
2016-04-20 12:12:38 +00:00
}
2021-05-06 14:45:09 +00:00
void upsertRealisation (
const std : : string & uri ,
const Realisation & realisation ) override
{
retrySQLite < void > ( [ & ] ( ) {
auto state ( _state . lock ( ) ) ;
auto & cache ( getCache ( * state , uri ) ) ;
state - > insertRealisation . use ( )
( cache . id )
( realisation . id . to_string ( ) )
( realisation . toJSON ( ) . dump ( ) )
( time ( 0 ) ) . exec ( ) ;
} ) ;
}
virtual void upsertAbsentRealisation (
const std : : string & uri ,
const DrvOutput & id ) override
{
retrySQLite < void > ( [ & ] ( ) {
auto state ( _state . lock ( ) ) ;
auto & cache ( getCache ( * state , uri ) ) ;
state - > insertMissingRealisation . use ( )
( cache . id )
( id . to_string ( ) )
( time ( 0 ) ) . exec ( ) ;
} ) ;
}
2016-04-20 12:12:38 +00:00
} ;
ref < NarInfoDiskCache > getNarInfoDiskCache ( )
{
2018-03-20 15:32:59 +00:00
static ref < NarInfoDiskCache > cache = make_ref < NarInfoDiskCacheImpl > ( ) ;
return cache ;
2016-04-20 12:12:38 +00:00
}
}