/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2013 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
** This file houses most of main the v2 API routines.
*/
#include "fossil/fossil.h"
#include "fsl_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <assert.h>
const fsl_buffer fsl_buffer_empty = fsl_buffer_empty_m;
const fsl_cx fsl_cx_empty = fsl_cx_empty_m;
const fsl_cx_config fsl_cx_config_empty = fsl_cx_config_empty_m;
const fsl_db fsl_db_empty = fsl_db_empty_m;
const fsl_deck fsl_cardset_empty = fsl_deck_empty_m;
const fsl_deck fsl_deck_empty = fsl_deck_empty_m;
const fsl_error fsl_error_empty = fsl_error_empty_m;
const fsl_fstat fsl_fstat_empty = fsl_fstat_empty_m;
const fsl_init_param fsl_init_param_default = fsl_init_param_default_m;
const fsl_init_param fsl_init_param_empty = fsl_init_param_empty_m;
const fsl_list fsl_list_empty = fsl_list_empty_m;
const fsl_mf fsl_mf_empty = fsl_mf_empty_m;
const fsl_mf_file fsl_mf_file_empty = fsl_mf_file_empty_m;
const fsl_mf_tag fsl_mf_tag_empty = fsl_mf_tag_empty_m;
const fsl_outputer fsl_outputer_FILE = fsl_outputer_FILE_m;
const fsl_pair_buf fsl_pair_buf_empty = fsl_pair_buf_empty_m;
const fsl_stmt fsl_stmt_empty = {
NULL/*db*/,
NULL/*stmt*/,
fsl_buffer_empty_m/*sql*/,
0/*colCount*/,
0/*paramCount*/,
NULL/*allocStamp*/
};
#define fsl_db_record_empty_m { \
FSL_TYPE_INVALID /*typeId*/,\
0/*dbId*/,\
NULL/*next*/\
}
const fsl_db_record fsl_db_record_empty = fsl_db_record_empty_m;
const fsl_user_r fsl_user_r_empty = {
fsl_db_record_empty_m /*base*/,
fsl_buffer_empty_m /* name */,
FSL_ROLE_GUEST /* roles */,
0/*mtime*/
};
const fsl_tag_r fsl_tag_r_empty = {
fsl_db_record_empty_m /*base*/,
fsl_buffer_empty_m /* key */,
fsl_buffer_empty_m /* value */
};
const fsl_blob_r fsl_blob_r_empty = {
fsl_db_record_empty_m /*base*/,
0/*mtime*/,
{/*sha1*/0}
};
const fsl_allocator fsl_allocator_stdalloc = {
fsl_realloc_f_stdalloc,
NULL
};
fsl_allocator fsl_memory_allocator = {
fsl_realloc_f_stdalloc,
NULL
};
void * fsl_malloc( fsl_size_t n ){
return n
? fsl_memory_allocator.f(fsl_memory_allocator.state, NULL, n)
: NULL;
}
void fsl_free( void * mem ){
if(mem) fsl_memory_allocator.f(fsl_memory_allocator.state, mem, 0);
}
void * fsl_realloc( void * mem, fsl_size_t n ){
if(!mem){
return fsl_malloc(n);
}else if(!n){
fsl_free(mem);
return NULL;
}else{
return fsl_memory_allocator.f(fsl_memory_allocator.state, mem, n);
}
}
void * fsl_realloc_f_stdalloc(void * state, void * mem, fsl_size_t n){
if(!mem){
return malloc(n);
}else if(!n){
free(mem);
return NULL;
}else{
return realloc(mem, n);
}
}
int fsl_flush_f_FILE(void * _FILE){
return _FILE
? (fflush((FILE*)_FILE) ? FSL_RC_IO : 0)
: FSL_RC_MISUSE;
}
int fsl_output_f_FILE( void * state,
void const * src, fsl_size_t n ){
return !state
? FSL_RC_MISUSE
: ((1 == fwrite(src, n, 1, state ? (FILE*)state : stdout))
? 0
: FSL_RC_IO);
}
int fsl_input_FILE( void * state, void * dest, fsl_size_t * n ){
FILE * f = (FILE*) state;
if( !state || !dest || !n ) return FSL_RC_MISUSE;
else if( !*n ) return FSL_RC_RANGE;
*n = (fsl_size_t)fread( dest, 1, *n, f );
return !*n
? (feof(f) ? 0 : FSL_RC_IO)
: 0;
}
void fsl_finalizer_f_FILE( void * state, void * mem ){
if(mem && (stdout != mem) && (stderr != mem)){
fclose((FILE*)mem);
}
}
int fsl_output_f_buffer( void * state,
void const * src, fsl_size_t n ){
return !state
? FSL_RC_MISUSE
: fsl_buffer_append((fsl_buffer*)state, src, n);
}
int fsl_finalizer_f_buffer( void * state, void * mem ){
fsl_buffer * b = (fsl_buffer*)mem;
fsl_buffer_reserve(b, 0);
*b = fsl_buffer_empty;
return 0;
}
int fsl_cx_init( fsl_cx ** tgt, fsl_init_param * param ){
static fsl_init_param paramDefaults = fsl_init_param_default_m;
int rc;
fsl_cx * f;
if(!tgt) return FSL_RC_MISUSE;
else if(!param){
if(!paramDefaults.output.state.state){
paramDefaults.output.state.state = stdout;
}
param = ¶mDefaults;
}
f = (fsl_cx*)fsl_malloc(sizeof(fsl_cx));
if(!f) return FSL_RC_OOM;
*tgt = f;
*f = fsl_cx_empty;
f->output = param->output;
f->config = param->config;
f->dbMain.f = f;
rc = fsl_db_open(&f->dbMain, /*"/tmp/foo.db"*/ ":memory:",
FSL_OPEN_F_RWC)
/* i'd prefer to use a temporary file here, because
sqlite does not allow us to create TEMP views/tables
in a non-main db. e.g.
CREATE TEMPORARY VIEW foo.BAR ...
is not legal.
*/;
if(rc){
fsl_error_move( &f->dbMain.error, &f->error );
}else{
f->dbMain.role = FSL_DB_ROLE_MAIN;
}
return rc;
}
int fsl_cx_finalize( fsl_cx * f ){
if(!f) return FSL_RC_MISUSE;
else{
if(f->clientState.finalize.f){
f->clientState.finalize.f( f->clientState.finalize.state,
f->clientState.state );
}
if(f->output.state.finalize.f){
f->output.state.finalize.f( f->output.state.finalize.state,
f->output.state.state );
}
fsl_error_clean(&f->error);
fsl_db_close(&f->dbMain);
fsl_buffer_reserve(&f->fileConfig, 0);
fsl_buffer_reserve(&f->fileRepo, 0);
fsl_buffer_reserve(&f->fileCkout, 0);
fsl_buffer_reserve(&f->scratch, 0);
fsl_buffer_reserve(&f->dirCkout, 0);
*f = fsl_cx_empty;
fsl_free(f);
return 0;
}
}
int fsl_output( fsl_cx * cx, void const * src, fsl_size_t n ){
if(!cx || !src) return FSL_RC_MISUSE;
else if(!n || !cx->output.out) return 0;
else return cx->output.out( cx->output.state.state,
src, n );
}
void fsl_error_clean( fsl_error * err ){
if(err){
if(err->msg.mem) fsl_buffer_reserve(&err->msg, 0);
*err = fsl_error_empty;
}
}
void fsl_error_move( fsl_error * lower, fsl_error * higher ){
fsl_error const err = *lower;
*lower = *higher;
lower->code = 0;
lower->msg.used = 0;
*higher = err;
}
int fsl_error_setv( fsl_error * err, int code, char const * fmt,
va_list args ){
if(!err) return FSL_RC_MISUSE;
else if(!code){ /* clear error state */
err->code = 0;
err->msg.used = 0;
if(err->msg.mem){
err->msg.mem[0] = 0;
}
return 0;
}else{
int rc = 0;
err->msg.used = 0;
err->code = code;
if(FSL_RC_OOM!=code){
rc = fmt
? fsl_buffer_appendfv(&err->msg, fmt, args)
: fsl_buffer_append(&err->msg, fsl_rc_cstr(code), -1);
}
return rc ? rc : code;
}
}
int fsl_error_set( fsl_error * err, int code, char const * fmt,
... ){
int rc;
va_list args;
va_start(args,fmt);
rc = fsl_error_setv(err, code, fmt, args);
va_end(args);
return rc;
}
int fsl_error_get( fsl_error * err, char const ** str, fsl_size_t * len ){
if(!err) return FSL_RC_MISUSE;
else{
if(str) *str = err->msg.used
? (char const *)err->msg.mem
: NULL;
if(len) *len = err->msg.used;
return err->code;
}
}
int fsl_cx_err_set_e( fsl_cx * f, fsl_error * err ){
if(!f) return FSL_RC_MISUSE;
else if(!err){
return fsl_cx_err_set(f, 0, NULL);
}else{
fsl_error_move(err, &f->error);
fsl_error_clean(err);
return f->error.code;
}
}
int fsl_cx_err_setv( fsl_cx * f, int code, char const * fmt,
va_list args ){
return f
? fsl_error_setv( &f->error, code, fmt, args )
: FSL_RC_MISUSE;
}
int fsl_cx_err_set( fsl_cx * f, int code, char const * fmt,
... ){
if(!f) return FSL_RC_MISUSE;
else{
int rc;
va_list args;
va_start(args,fmt);
rc = fsl_error_setv( &f->error, code, fmt, args );
va_end(args);
return rc;
}
}
int fsl_cx_err_get( fsl_cx * f, char const ** str, fsl_size_t * len ){
return f
? fsl_error_get( &f->error, str, len )
: FSL_RC_MISUSE;
}
fsl_id_t fsl_cx_last_insert_id(fsl_cx * f){
return (f && f->dbMain.dbh)
? fsl_db_last_insert_id(&f->dbMain)
: -1;
}
/*
** fsl_appendf_f() impl which sends its output to fsl_output(). state
** must be a (fsl_cx*).
*/
static fsl_int_t fsl_appendf_f_fsl_output( void * state, char const * s,
fsl_int_t n ){
fsl_cx * f = (fsl_cx *)state;
return fsl_output( f, s, (fsl_size_t)n )
? -1
: n;
}
int fsl_outputfv( fsl_cx * f, char const * fmt, va_list args ){
if(!f || !fmt) return FSL_RC_MISUSE;
else if(!*fmt) return FSL_RC_RANGE;
else{
long const prc = fsl_appendfv( fsl_appendf_f_fsl_output,
f, fmt, args );
return (prc>=0) ? 0 : FSL_RC_IO;
}
}
int fsl_outputf( fsl_cx * f, char const * fmt, ... ){
if(!f || !fmt) return FSL_RC_MISUSE;
else if(!*fmt) return FSL_RC_RANGE;
else{
int rc;
va_list args;
va_start(args,fmt);
rc = fsl_outputfv( f, fmt, args );
va_end(args);
return rc;
}
}
char const * fsl_rc_cstr(int rc){
fsl_rc_t const RC = (fsl_rc_t)rc
/* we do this so that gcc will warn if the switch() below is
missing any fsl_rc_t entries. */
;
switch(RC){
#define STR(T) case FSL_RC_##T: return "FSL_RC_" #T
STR(ACCESS);
STR(ALREADY_EXISTS);
STR(AMBIGUOUS);
STR(BREAK);
STR(CHECKSUM_MISMATCH);
STR(CONSISTENCY);
STR(DB);
STR(DELTA_INVALID_OPERATOR);
STR(DELTA_INVALID_SEPARATOR);
STR(DELTA_INVALID_SIZE);
STR(DELTA_INVALID_TERMINATOR);
STR(ERROR);
STR(IO);
STR(MF_SYNTAX);
STR(MISUSE);
STR(NOT_A_CHECKOUT);
STR(NOT_A_REPO);
STR(NOT_FOUND);
STR(NYI);
STR(OK);
STR(OOM);
STR(RANGE);
STR(REPO_MISMATCH);
STR(REPO_NEEDS_REBUILD);
STR(REPO_VERSION);
STR(SIZE_MISMATCH);
STR(STEP_DONE);
STR(STEP_ERROR);
STR(STEP_ROW);
STR(TYPE);
STR(TRAILING_COMMA_KLUDGE);
#undef STR
}
return "Unknown result code";
}
char const * fsl_library_version(){
return FSL_LIBRARY_VERSION;
}
char fsl_library_version_matches(char const * yourLibVersion){
return 0 == fsl_strcmp(FSL_LIBRARY_VERSION, yourLibVersion);
}
fsl_size_t fsl_list_reserve( fsl_list * self, fsl_size_t n )
{
if( !self ) return 0;
else if(0 == n){
if(0 == self->capacity) return 0;
fsl_free(self->list);
self->list = NULL;
self->capacity = self->used = 0;
return 0;
}
else if( self->capacity >= n ){
return self->capacity;
}
else{
size_t const sz = sizeof(void*) * n;
void* * m = (void**)fsl_realloc( self->list, sz );
if( !m ) return self->capacity;
memset( m + self->capacity, 0, (sizeof(void*)*(n-self->capacity)));
self->capacity = n;
self->list = m;
return n;
}
}
int fsl_list_append( fsl_list * self, void* cp ){
if( !self ) return FSL_RC_MISUSE;
else if( self->capacity > fsl_list_reserve(self,
self->used+1)){
return FSL_RC_OOM;
}
else{
self->list[self->used++] = cp;
if(self->used< self->capacity-1) self->list[self->used]=0;
return 0;
}
}
int fsl_list_v_fsl_free(void * obj, void * visitorState ){
if(obj) fsl_free( obj );
return 0;
}
int fsl_list_visit( fsl_list const * self, char order,
fsl_list_visitor_f visitor,
void * visitorState ){
int rc = FSL_RC_OK;
if( self && self->used && visitor ){
int i = 0;
int pos = (order<0) ? self->used-1 : 0;
int step = (order<0) ? -1 : 1;
for( rc = 0; (i < self->used)
&& (0 == rc); ++i, pos+=step ){
void* obj = self->list[pos];
if(obj) rc = visitor( obj, visitorState );
if( obj != self->list[pos] ){
--i;
if(order>=0) pos -= step;
}
}
}
return rc;
}
int fsl_list_visit_p( fsl_list * self, char order,
char shiftIfNulled,
fsl_list_visitor_f visitor, void * visitorState )
{
int rc = FSL_RC_OK;
if( self && self->used && visitor ){
int i = 0;
int pos = (order<0) ? self->used-1 : 0;
int step = (order<0) ? -1 : 1;
for( rc = 0; (i < (int)self->used)
&& (0 == rc); ++i, pos+=step ){
void* obj = self->list[pos];
if(obj) {
assert((order<0) && "TEST THAT THIS WORKS WITH IN-ORDER!");
rc = visitor( &self->list[pos], visitorState );
if( shiftIfNulled && !self->list[pos]){
int x = pos;
int const to = self->used-pos;
assert( to < (int) self->capacity );
for( ; x < to; ++x ) self->list[x] = self->list[x+1];
if( x < (int)self->capacity ) self->list[x] = 0;
--i;
--self->used;
if(order>=0) pos -= step;
}
}
}
}
return rc;
}
fsl_double_t fsl_unix_to_julian( fsl_time_t unix ){
return (unix * 1.0 / 86400.0 ) + 2440587.5;
}
int fsl_strcmp(const char *zA, const char *zB){
if( zA==0 ){
if( zB==0 ) return 0;
return -1;
}else if( zB==0 ){
return +1;
}else{
int a, b;
do{
a = *zA++;
b = *zB++;
}while( a==b && a!=0 );
return ((unsigned char)a) - (unsigned char)b;
}
}
int fsl_strncmp(const char *zA, const char *zB, fsl_size_t nByte){
if( !zA ) return zB ? -1 : 0;
else if( !zB ) return +1;
else if(!nByte) return 0;
else{
int a, b;
do{
a = *zA++;
b = *zB++;
}while( a==b && a!=0 && (--nByte)>0 );
return ((unsigned char)a) - (unsigned char)b;
}
}
/*
** Case insensitive string comparison.
*/
int fsl_strnicmp(const char *zA, const char *zB, fsl_int_t nByte){
if( zA==0 ){
if( zB==0 ) return 0;
return -1;
}else if( zB==0 ){
return +1;
}
if( nByte<0 ) nByte = (fsl_int_t)fsl_strlen(zB);
return sqlite3_strnicmp(zA, zB, nByte);
}
int fsl_stricmp(const char *zA, const char *zB){
fsl_int_t nByte;
int rc;
if( zA==0 ){
if( zB==0 ) return 0;
return -1;
}else if( zB==0 ){
return +1;
}
nByte = (fsl_int_t)fsl_strlen(zB);
rc = sqlite3_strnicmp(zA, zB, nByte);
if( rc==0 && zA[nByte] ) rc = 1;
return rc;
}
fsl_size_t fsl_strlen( char const * src ){
fsl_size_t i = 0;
if(src) for( ; *src; ++i, ++src ){}
return i;
}
char * fsl_strndup( char const * src, fsl_int_t len ){
fsl_buffer b = fsl_buffer_empty;
if(!src) return NULL;
else if(len<0) len = (fsl_int_t)fsl_strlen(src);
fsl_buffer_append( &b, len ? src : "\0", len ? len : 1 )
/*special case for 0-length strings, to avoid returning NULL
there. */;
return (char*)b.mem;
}
char * fsl_strdup( char const * src ){
return fsl_strndup(src, -1);
}
char fsl_str_glob(const char *zGlob, const char *z){
int c, c2;
int invert;
int seen;
while( (c = (*(zGlob++)))!=0 ){
if( c=='*' ){
while( (c=(*(zGlob++))) == '*' || c=='?' ){
if( c=='?' && (*(z++))==0 ) return 0;
}
if( c==0 ){
return 1;
}else if( c=='[' ){
while( *z && fsl_str_glob(zGlob-1,z)==0 ){
z++;
}
return (*z)!=0;
}
while( (c2 = (*(z++)))!=0 ){
while( c2!=c ){
c2 = *(z++);
if( c2==0 ) return 0;
}
if( fsl_str_glob(zGlob,z) ) return 1;
}
return 0;
}else if( c=='?' ){
if( (*(z++))==0 ) return 0;
}else if( c=='[' ){
int prior_c = 0;
seen = 0;
invert = 0;
c = *(z++);
if( c==0 ) return 0;
c2 = *(zGlob++);
if( c2=='^' ){
invert = 1;
c2 = *(zGlob++);
}
if( c2==']' ){
if( c==']' ) seen = 1;
c2 = *(zGlob++);
}
while( c2 && c2!=']' ){
if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
c2 = *(zGlob++);
if( c>=prior_c && c<=c2 ) seen = 1;
prior_c = 0;
}else{
if( c==c2 ){
seen = 1;
}
prior_c = c2;
}
c2 = *(zGlob++);
}
if( c2==0 || (seen ^ invert)==0 ) return 0;
}else{
if( c!=(*(z++)) ) return 0;
}
}
return *z==0;
}
fsl_int32_t fsl_cx_lget_int32( fsl_cx * f, fsl_int32_t dflt,
char const * key ){
fsl_int32_t rv = dflt;
fsl_db * db = fsl_cx_db_checkout(f);
if(db){
fsl_db_get_int32(db, &rv,
"SELECT value FROM %s.vvar WHERE name=%Q",
fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
key);
}
return rv;
}
fsl_int64_t fsl_cx_lget_int64( fsl_cx * f, fsl_int64_t dflt,
char const * key ){
fsl_int64_t rv = dflt;
fsl_db * db = fsl_cx_db_checkout(f);
if(db){
fsl_db_get_int64(db, &rv,
"SELECT value FROM %s.vvar WHERE name=%Q",
fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
key);
}
return rv;
}
fsl_id_t fsl_cx_lget_id( fsl_cx * f, fsl_id_t dflt,
char const * key ){
fsl_id_t rv = dflt;
fsl_db * db = fsl_cx_db_checkout(f);
if(db){
fsl_db_get_id(db, &rv,
"SELECT value FROM %s.vvar WHERE name=%Q",
fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
key);
}
return rv;
}
fsl_double_t fsl_cx_lget_double( fsl_cx * f, fsl_double_t dflt,
char const * key ){
fsl_double_t rv = dflt;
fsl_db * db = fsl_cx_db_checkout(f);
if(db){
fsl_db_get_double(db, &rv,
"SELECT value FROM %s.vvar WHERE name=%Q",
fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
key);
}
return rv;
}
char * fsl_cx_lget_text( fsl_cx * f, fsl_size_t * len,
char const * key ){
char * rv = NULL;
fsl_db * db = fsl_cx_db_checkout(f);
if(db){
fsl_db_get_text(db, &rv, len,
"SELECT value FROM %s.vvar WHERE name=%Q",
fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
key);
}
return rv;
}
/*
** Return TRUE if the string begins with something that looks roughly
** like an ISO date/time string. The SQLite date/time functions will
** have the final say-so about whether or not the date/time string is
** well-formed.
*/
char fsl_str_is_date(const char *z){
if(!z || !*z) return 0;
if( !fsl_isdigit(z[0]) ) return 0;
if( !fsl_isdigit(z[1]) ) return 0;
if( !fsl_isdigit(z[2]) ) return 0;
if( !fsl_isdigit(z[3]) ) return 0;
if( z[4]!='-') return 0;
if( !fsl_isdigit(z[5]) ) return 0;
if( !fsl_isdigit(z[6]) ) return 0;
if( z[7]!='-') return 0;
if( !fsl_isdigit(z[8]) ) return 0;
if( !fsl_isdigit(z[9]) ) return 0;
return 1;
}
char const * fsl_atype_cstr(fsl_artifact_t t){
switch(t){
case FSL_ATYPE_ANY: return "*";
case FSL_ATYPE_CHECKIN: return "ci";
case FSL_ATYPE_EVENT: return "e";
case FSL_ATYPE_G: return "g";
case FSL_ATYPE_TICKET: return "t";
case FSL_ATYPE_WIKI: return "w";
default:
return NULL;
}
}
int fsl_cx_sym_to_rid( fsl_cx * f, char const * sym, fsl_artifact_t type,
fsl_id_t * rv ){
fsl_id_t rid = 0;
fsl_id_t vid;
fsl_size_t symLen;
/* fsl_int_t i; */
fsl_db * dbR = fsl_cx_db_repo(f);
fsl_db * dbC = fsl_cx_db_checkout(f);
if(!f || !sym || !*sym || !rv) return FSL_RC_MISUSE;
else if(!dbR) return FSL_RC_NOT_A_REPO;
/* special keyword: "tip" */
if( 0==fsl_strcmp(sym,"tip")
&& (FSL_ATYPE_ANY==type || FSL_ATYPE_CHECKIN==type)){
rid = fsl_db_g_id(dbR, 0,
"SELECT objid FROM event"
" WHERE type='ci'"
" ORDER BY event.mtime DESC"
" LIMIT 1");
if(rid>0) goto gotit;
}
/* special keywords: "prev", "previous", "current", and "next".
These require a checkout.
*/
vid = dbC
? fsl_cx_lget_id(f, 0, "checkout")
: 0;
if( vid>0){
if( 0==fsl_strcmp(sym, "current") ){
rid = vid;
}
else if( 0==fsl_strcmp(sym, "prev")
|| 0==fsl_strcmp(sym, "previous") ){
rid = fsl_db_g_id(dbR, 0,
"SELECT pid FROM plink WHERE "
"cid=%"FSL_ID_T_PFMT" AND isprim",
vid);
}
else if( 0==fsl_strcmp(sym, "next") ){
rid = fsl_db_g_id(dbR, 0,
"SELECT cid FROM plink WHERE "
"pid=%"FSL_ID_T_PFMT
" ORDER BY isprim DESC, mtime DESC",
vid);
}
if(rid>0) goto gotit;
}
/* Date and times */
if( 0==memcmp(sym, "date:", 5) ){
rid = fsl_db_g_id(dbR, 0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
&sym[5], sym);
*rv = rid;
return 0;
}
if( fsl_str_is_date(sym) ){
rid = fsl_db_g_id(dbR, 0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
sym, fsl_atype_cstr(type));
if(rid>0) goto gotit;
}
/* Deprecated time formats elided: local:..., utc:... */
/* "tag:" + symbolic-name */
if( memcmp(sym, "tag:", 4)==0 ){
rid = fsl_db_g_id(dbR, 0,
"SELECT event.objid, max(event.mtime)"
" FROM tag, tagxref, event"
" WHERE tag.tagname='sym-%q' "
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
" AND event.objid=tagxref.rid "
" AND event.type GLOB '%q'",
&sym[4], fsl_atype_cstr(type)
);
goto gotit;
}
#if 0
/* TODO */
/* root:TAG -> The origin of the branch */
if( memcmp(zTag, "root:", 5)==0 ){
Stmt q;
int rc;
char *zBr;
rid = symbolic_name_to_rid(zTag+5, zType);
zBr = db_text("trunk","SELECT value FROM tagxref"
" WHERE rid=%d AND tagid=%d"
" AND tagtype>0",
rid, TAG_BRANCH);
db_prepare(&q,
"SELECT pid, EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=%d AND tagtype>0"
" AND value=%Q AND rid=plink.pid)"
" FROM plink"
" WHERE cid=:cid AND isprim",
TAG_BRANCH, zBr
);
fossil_free(zBr);
do{
db_reset(&q);
db_bind_int(&q, ":cid", rid);
rc = db_step(&q);
if( rc!=SQLITE_ROW ) break;
rid = db_column_int(&q, 0);
}while( db_column_int(&q, 1)==1 && rid>0 );
db_finalize(&q);
return rid;
}
#endif
symLen = fsl_strlen(sym);
/* SHA1 hash or prefix */
if( symLen>=4
&& symLen<=FSL_UUID_STRLEN
&& fsl_validate16(sym, symLen) ){
fsl_stmt q = fsl_stmt_empty;
char zUuid[FSL_UUID_STRLEN+1];
memcpy(zUuid, sym, symLen);
zUuid[symLen] = 0;
fsl_canonical16(zUuid, symLen);
rid = 0;
if( FSL_ATYPE_ANY==type ){
fsl_stmt_prepare(dbR, &q,
"SELECT rid FROM blob WHERE uuid GLOB '%s*'",
zUuid);
}else{
fsl_stmt_prepare(dbR, &q,
"SELECT blob.rid"
" FROM blob, event"
" WHERE blob.uuid GLOB '%s*'"
" AND event.objid=blob.rid"
" AND event.type GLOB '%q'",
zUuid, fsl_atype_cstr(type)
);
}
if( fsl_stmt_step(&q)==FSL_RC_STEP_ROW ){
fsl_int64_t r64 = 0;
fsl_stmt_get_int64(&q, 0, &r64);
if( fsl_stmt_step(&q)==FSL_RC_STEP_ROW ) rid = -1
/* Ambiguous results */
;
else rid = (fsl_id_t)r64;
}
fsl_stmt_finalize(&q);
if(rid<0) return FSL_RC_AMBIGUOUS;
else if(rid>0) goto gotit;
}
/* Symbolic name ... */
rid = fsl_db_g_id(dbR, 0,
"SELECT event.objid, max(event.mtime)"
" FROM tag, tagxref, event"
" WHERE tag.tagname='sym-%q' "
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
" AND event.objid=tagxref.rid "
" AND event.type GLOB '%q'",
sym, fsl_atype_cstr(type)
);
if( rid>0 ) goto gotit;
#if 0
/*
v1 has this undocumented feature, but it is being elided for now.
*/
/* Undocumented: numeric tags get translated directly into the RID */
for(i=0; fsl_isdigit(sym[i]); i++){}
if( sym[i]==0 ){
if( FSL_ATYPE_ANY==type ){
rid = atoi(sym);
}else{
rid = fsl_db_g_id(dbR, 0,
"SELECT event.objid"
" FROM event"
" WHERE event.objid=%s"
" AND event.type GLOB '%q'", zTag, zType);
}
if( rid>0 ) goto gotit;
}
#endif
return FSL_RC_NOT_FOUND;
gotit:
*rv = rid;
return 0;
}
int fsl_cx_sym_to_uuid( fsl_cx * f, char const * sym, fsl_artifact_t type,
char ** rv, fsl_id_t * rvId ){
fsl_id_t rid = 0;
int rc = fsl_cx_sym_to_rid(f, sym, type, &rid);
if(rvId) *rvId = rid;
return rc
? rc
: fsl_db_get_text( &f->dbMain, rv, NULL,
"SELECT uuid FROM blob WHERE rid=%"FSL_ID_T_PFMT,
rid );
}