/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt
SPDX-License-Identifier: BSD-2-Clause-FreeBSD
SPDX-FileCopyrightText: 2021 The Libfossil Authors
SPDX-ArtifactOfProjectName: Libfossil
SPDX-FileType: Code
Heavily indebted to the Fossil SCM project (https://fossil-scm.org).
*/
/************************************************************************
This file houses some of the "leaf"-related APIs.
*/
#include <assert.h>
#include "fossil-scm/internal.h"
/* Only for debugging */
#define MARKER(pfexp) \
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
printf pfexp; \
} while(0)
int fsl_repo_leaves_rebuild(fsl_cx * const f){
fsl_db * const db = fsl_cx_db_repo(f);
int rc = fsl_db_exec_multi(db,
"DELETE FROM leaf;"
"INSERT OR IGNORE INTO leaf"
" SELECT cid FROM plink"
" EXCEPT"
" SELECT pid FROM plink"
" WHERE coalesce((SELECT value FROM tagxref"
" WHERE tagid=%d AND rid=plink.pid),'trunk')"
" == coalesce((SELECT value FROM tagxref"
" WHERE tagid=%d AND rid=plink.cid),'trunk')",
FSL_TAGID_BRANCH, FSL_TAGID_BRANCH
);
return rc ? fsl_cx_uplift_db_error2(f, db, rc) : 0;
}
fsl_int_t fsl_count_nonbranch_children(fsl_cx * const f, fsl_id_t rid){
int32_t rv = 0;
int rc;
fsl_db * const db = fsl_cx_db_repo(f);
if(!db || !db->dbh || (rid<=0)) return -1;
rc = fsl_db_get_int32(db, &rv,
"SELECT count(*) FROM plink "
"WHERE pid=%"FSL_ID_T_PFMT" "
"AND isprim "
"AND coalesce((SELECT value FROM tagxref "
"WHERE tagid=%d AND rid=plink.pid), 'trunk')"
"=coalesce((SELECT value FROM tagxref "
"WHERE tagid=%d AND rid=plink.cid), 'trunk')",
rid, FSL_TAGID_BRANCH, FSL_TAGID_BRANCH);
return rc ? -2 : rv;
}
bool fsl_rid_is_leaf(fsl_cx * const f, fsl_id_t rid){
int rv = -1;
int rc;
fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
fsl_stmt * st = NULL;
if(!db || !db->dbh || (rid<=0)) return 0;
rc = fsl_db_prepare_cached(db, &st,
"SELECT 1 FROM plink "
"WHERE pid=?1 "
"AND coalesce("
"(SELECT value FROM tagxref "
"WHERE tagid=%d AND rid=?1), "
//"(SELECT value FROM config WHERE name='main-branch'), "
"'trunk')"
"=coalesce((SELECT value FROM tagxref "
"WHERE tagid=%d "
"AND rid=plink.cid), "
//"(SELECT value FROM config WHERE name='main-branch'), "
"'trunk')"
"/*%s()*/",
FSL_TAGID_BRANCH, FSL_TAGID_BRANCH, __func__);
if(!rc){
rc = fsl_stmt_bind_step(st, "R", rid);
switch(rc){
case FSL_RC_STEP_ROW:
rv = 0;
rc = 0;
break;
case 0:
rv = 1;
rc = 0;
break;
default:
break;
}
fsl_stmt_cached_yield(st);
assert(0==rv || 1==rv);
}
return rc ? 0 : (rv==1);
}
bool fsl_rid_is_version(fsl_cx * const f, fsl_id_t rid){
fsl_db * const db = fsl_cx_db_repo(f);
if(!db) return false;
return 1==fsl_db_g_int32(db, 0,
"SELECT 1 FROM event "
"WHERE objid=%" FSL_ID_T_PFMT
" AND type='ci'", rid);
}
int fsl__repo_leafcheck(fsl_cx * const f, fsl_id_t rid){
fsl_db * const db = f ? fsl_cx_db_repo(f) : NULL;
if(!db || !db->dbh) return FSL_RC_MISUSE;
else if(rid<=0) return FSL_RC_RANGE;
else {
int rc = 0;
bool isLeaf;
fsl_cx_err_reset(f);
isLeaf = fsl_rid_is_leaf(f, rid);
rc = fsl_cx_err_get(f, NULL, NULL);
if(!rc){
fsl_stmt * st = NULL;
if( isLeaf ){
rc = fsl_db_prepare_cached(db, &st,
"INSERT OR IGNORE INTO leaf VALUES"
"(?) /*%s()*/",__func__);
}else{
rc = fsl_db_prepare_cached(db, &st,
"DELETE FROM leaf WHERE rid=?"
"/*%s()*/",__func__);
}
if(!rc && st){
rc = fsl_stmt_bind_step(st, "R", rid);
fsl_stmt_cached_yield(st);
if(rc) rc = fsl_cx_uplift_db_error2(f, db, rc);
}
}
return rc;
}
}
int fsl__repo_leafeventually_check( fsl_cx * const f, fsl_id_t rid){
fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
if(!f) return FSL_RC_MISUSE;
else if(rid<=0) return FSL_RC_RANGE;
else if(!db) return FSL_RC_NOT_A_REPO;
else {
fsl_stmt * parentsOf = NULL;
int rc = fsl_db_prepare_cached(db, &parentsOf,
"SELECT pid FROM plink WHERE "
"cid=? AND pid>0"
"/*%s()*/",__func__);
if(rc) return rc;
rc = fsl_stmt_bind_id(parentsOf, 1, rid);
if(!rc){
rc = fsl_id_bag_insert(&f->cache.leafCheck, rid);
while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(parentsOf)) ){
rc = fsl_id_bag_insert(&f->cache.leafCheck,
fsl_stmt_g_id(parentsOf, 0));
}
}
fsl_stmt_cached_yield(parentsOf);
return rc;
}
}
int fsl__repo_leafdo_pending_checks(fsl_cx * const f){
fsl_id_t rid;
int rc = 0;
for(rid=fsl_id_bag_first(&f->cache.leafCheck);
!rc && rid; rid=fsl_id_bag_next(&f->cache.leafCheck,rid)){
rc = fsl__repo_leafcheck(f, rid);
}
fsl_id_bag_reset(&f->cache.leafCheck);
return rc;
}
int fsl_leaves_compute(fsl_cx * const f, fsl_id_t vid,
fsl_leaves_compute_e closeMode){
fsl_db * const db = fsl_needs_repo(f);
if(!db) return FSL_RC_NOT_A_REPO;
int rc = 0;
/* Create the LEAVES table if it does not already exist. Make sure
** it is empty.
*/
rc = fsl_db_exec_multi(db,
"CREATE TEMP TABLE IF NOT EXISTS leaves("
" rid INTEGER PRIMARY KEY"
");"
"DELETE FROM leaves;"
);
if(rc) goto dberr;
if( vid <= 0 ){
rc = fsl_db_exec_multi(db,
"INSERT INTO leaves SELECT leaf.rid FROM leaf"
);
if(rc) goto dberr;
}
if( vid>0 ){
fsl_id_bag seen = fsl_id_bag_empty; /* Descendants seen */
fsl_id_bag pending = fsl_id_bag_empty; /* Unpropagated descendants */
fsl_stmt q1 = fsl_stmt_empty; /* Query to find children of a check-in */
fsl_stmt isBr = fsl_stmt_empty; /* Query to check to see if a check-in starts a new branch */
fsl_stmt ins = fsl_stmt_empty; /* INSERT statement for a new record */
/* Initialize the bags. */
rc = fsl_id_bag_insert(&pending, vid);
if(rc) goto cleanup;
/* This query returns all non-branch-merge children of check-in
** RID (?1).
**
** If a child is a merge of a fork within the same branch, it is
** returned. Only merge children in different branches are excluded.
*/
rc = fsl_db_prepare(db, &q1,
"SELECT cid FROM plink"
" WHERE pid=?1"
" AND (isprim"
" OR coalesce((SELECT value FROM tagxref"
" WHERE tagid=%d AND rid=plink.pid), 'trunk')"
/* FIXME? main-branch? */
"=coalesce((SELECT value FROM tagxref"
" WHERE tagid=%d AND rid=plink.cid), 'trunk'))"
/* FIXME? main-branch? */
,
FSL_TAGID_BRANCH, FSL_TAGID_BRANCH
);
if(rc) goto cleanup;
/* This query returns a single row if check-in RID (?1) is the
** first check-in of a new branch. */
rc = fsl_db_prepare(db, &isBr,
"SELECT 1 FROM tagxref"
" WHERE rid=?1 AND tagid=%d AND tagtype=2"
" AND srcid>0",
FSL_TAGID_BRANCH
);
if(rc) goto cleanup;
/* This statement inserts check-in RID (?1) into the LEAVES table.*/
rc = fsl_db_prepare(db, &ins,
"INSERT OR IGNORE INTO leaves VALUES(?1)");
if(rc) goto cleanup;
while( fsl_id_bag_count(&pending) ){
fsl_id_t const rid = fsl_id_bag_first(&pending);
unsigned cnt = 0;
fsl_id_bag_remove(&pending, rid);
fsl_stmt_bind_id(&q1, 1, rid);
while( FSL_RC_STEP_ROW==(rc = fsl_stmt_step(&q1)) ){
int const cid = fsl_stmt_g_id(&q1, 0);
rc = fsl_id_bag_insert(&seen, cid);
if(rc) break;
rc = fsl_id_bag_insert(&pending, cid);
if(rc) break;
fsl_stmt_bind_id(&isBr, 1, cid);
if( FSL_RC_STEP_DONE==fsl_stmt_step(&isBr) ){
++cnt;
}
fsl_stmt_reset(&isBr);
}
if(FSL_RC_STEP_DONE==rc) rc = 0;
else if(rc) break;
fsl_stmt_reset(&q1);
if( cnt==0 && !fsl_rid_is_leaf(f, rid) ){
++cnt;
}
if( cnt==0 ){
fsl_stmt_bind_id(&ins, 1, rid);
rc = fsl_stmt_step(&ins);
if(FSL_RC_STEP_DONE!=rc) break;
rc = 0;
fsl_stmt_reset(&ins);
}
}
cleanup:
fsl_stmt_finalize(&ins);
fsl_stmt_finalize(&isBr);
fsl_stmt_finalize(&q1);
fsl_id_bag_clear(&pending);
fsl_id_bag_clear(&seen);
if(rc) goto dberr;
}
assert(!rc);
switch(closeMode){
case FSL_LEAVES_COMPUTE_OPEN:
rc =
fsl_db_exec_multi(db,
"DELETE FROM leaves WHERE rid IN"
" (SELECT leaves.rid FROM leaves, tagxref"
" WHERE tagxref.rid=leaves.rid "
" AND tagxref.tagid=%d"
" AND tagxref.tagtype>0)",
FSL_TAGID_CLOSED);
if(rc) goto dberr;
break;
case FSL_LEAVES_COMPUTE_CLOSED:
rc =
fsl_db_exec_multi(db,
"DELETE FROM leaves WHERE rid NOT IN"
" (SELECT leaves.rid FROM leaves, tagxref"
" WHERE tagxref.rid=leaves.rid "
" AND tagxref.tagid=%d"
" AND tagxref.tagtype>0)",
FSL_TAGID_CLOSED);
if(rc) goto dberr;
break;
default: break;
}
end:
return rc;
dberr:
assert(rc);
rc = fsl_cx_uplift_db_error2(f, db, rc);
goto end;
}
bool fsl_leaves_computed_has(fsl_cx * const f){
return fsl_db_exists(fsl_cx_db_repo(f),
"SELECT 1 FROM leaves");
}
fsl_int_t fsl_leaves_computed_count(fsl_cx * const f){
int32_t rv = -1;
fsl_db * const db = fsl_cx_db_repo(f);
int const rc = fsl_db_get_int32(db, &rv,
"SELECT COUNT(*) FROM leaves");
if(rc){
fsl_cx_uplift_db_error2(f, db, rc);
assert(-1==rv);
}else{
assert(rv>=0);
}
return rv;
}
fsl_id_t fsl_leaves_computed_latest(fsl_cx * const f){
fsl_id_t rv = 0;
fsl_db * const db = fsl_cx_db_repo(f);
int const rc =
fsl_db_get_id(db, &rv,
"SELECT rid FROM leaves, event"
" WHERE event.objid=leaves.rid"
" ORDER BY event.mtime DESC");
if(rc){
fsl_cx_uplift_db_error2(f, db, rc);
assert(!rv);
}else{
assert(rv>=0);
}
return rv;
}
void fsl_leaves_computed_cleanup(fsl_cx * const f){
fsl_cx_exec(f, "DROP TABLE IF EXISTS temp.leaves");
/* Potential TODO: if we run into locking problems in
dropping this, switch to DELETE ... */
}
#undef MARKER