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