/* -*- 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 implements a basic SQL query tool using a libfossil-extended
command set (new SQL-side functions for working with fossil data).
*/
#include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */
/* #include "fossil-scm/fossil-internal.h" */
static struct {
bool showHeaders;
const char * separator;
const char * tmpFlag;
fsl_list eList;
fsl_buffer scriptBuf;
} QueryApp = {
true/*showHeaders*/,
NULL/*separator*/,
NULL,
fsl_list_empty_m,
fsl_buffer_empty_m
};
struct EFlag {
int type;
char const * txt;
};
typedef struct EFlag EFlag;
static int fsl_stmt_each_f_row( fsl_stmt * stmt, void * state ){
int i;
char const * sep = QueryApp.separator
? QueryApp.separator : "\t";
if('\\'==*sep){
/* Translate client-provided \t and \n */
switch(sep[1]){
case 't': sep = "\t"; break;
case 'n': sep = "\n"; break;
}
}
if((1==stmt->rowCount) && QueryApp.showHeaders){
for( i = 0; i < stmt->colCount; ++i){
f_out("%s%s", fsl_stmt_col_name(stmt, i),
(i<(stmt->colCount-1)) ? sep : "\n");
}
}
for( i = 0; i < stmt->colCount; ++i){
char const * col = fsl_stmt_g_text(stmt, i, NULL);
f_out("%s%s", col ? col : "NULL",
(i<(stmt->colCount-1)) ? sep : "\n");
}
return 0;
}
static void new_eflag(bool bigE){
EFlag * ef = (EFlag*)fsl_malloc(sizeof(EFlag));
ef->txt = QueryApp.tmpFlag;
QueryApp.tmpFlag = 0;
ef->type = bigE ? 1 : 0;
fsl_list_append(&QueryApp.eList, ef);
}
static int fcli_flag_callback_f_e(fcli_cliflag const * x){
assert(QueryApp.tmpFlag==*((char const **)x->flagValue));
new_eflag(0);
return FCLI_RC_FLAG_AGAIN;
}
static int fcli_flag_callback_f_eBig(fcli_cliflag const * x){
assert(QueryApp.tmpFlag==*((char const **)x->flagValue));
new_eflag(1);
return FCLI_RC_FLAG_AGAIN;
}
static int fsl_list_visitor_f_e(void * obj, void * visitorState ){
EFlag const * e = (EFlag const *)obj;
const char * sql = e->txt;
int rc;
fsl_cx * f = fcli_cx();
fsl_db * db = fsl_cx_db(f);
fsl_buffer_reuse(&QueryApp.scriptBuf);
if(0==fsl_file_access(e->txt,0)){
rc = fsl_buffer_fill_from_filename(&QueryApp.scriptBuf, sql);
if(rc){
return fcli_err_set(rc, "Error %d (%s) loading SQL from file [%s]",
rc, fsl_rc_cstr(rc), sql);
}
sql = (const char *)QueryApp.scriptBuf.mem;
}
if(e->type>0){
rc = fsl_db_exec_multi(db, "%s", sql);
}else{
fsl_stmt st = fsl_stmt_empty;
rc = fsl_db_prepare(db, &st, "%s", sql);
if(!rc){
if(st.colCount){ /* SELECT-style query */
rc = fsl_stmt_each( &st, fsl_stmt_each_f_row, NULL );
}else{
rc = fsl_stmt_step(&st);
if(FSL_RC_STEP_ROW==rc || FSL_RC_STEP_DONE==rc) rc = 0;
}
}
fsl_stmt_finalize(&st);
}
if(rc) fsl_cx_uplift_db_error(f, db);
return 0;
}
int main(int argc, char const * const * argv ){
int rc = 0;
bool noTransaction = 0;
bool noHeaders = false;
fsl_cx * f;
fsl_db * db;
fcli_cliflag FCliFlags[] = {
/* FIXME: fcli processes flags based on their order in this list,
e.g. all -E flags before any -e flags. That leads to unfortunate
CLI flag usage ordering dependencies. We ought to improve it
someday to process them in their argv order. */
FCLI_FLAG_X("E",0,"SQL",&QueryApp.tmpFlag, fcli_flag_callback_f_eBig,
"Treats its argument as a file or multiple SQL statements."),
FCLI_FLAG_X("e",0,"SQL",&QueryApp.tmpFlag, fcli_flag_callback_f_e,
"Treats its argument as a file or a single SQL statement. "
"ACHTUNG: all -E flags are run before all -e flags, due to "
"a quirk of the argument parser."),
FCLI_FLAG("s", "separator", "string", &QueryApp.separator,
"Separator for columns in SELECT results."),
FCLI_FLAG_BOOL("h", "no-header", &noHeaders,
"Disables output of headers on SELECT queries."),
FCLI_FLAG_BOOL("t","no-transaction", &noTransaction,
"Disables the use of an SQL transaction."),
fcli_cliflag_empty_m
};
fcli.cliFlags = FCliFlags;
fcli_help_info FCliHelp = { "Runs SQL against a fossil repository.", NULL, NULL };
fcli.appHelp = &FCliHelp;
rc = fcli_setup(argc, argv);
if(FCLI_RC_HELP==rc) /* --help */ return 0;
else if(rc) goto end;
if(!QueryApp.eList.used){
rc = fcli_err_set(FSL_RC_MISUSE,
"Missing -e/-E flag(s). Try --help.");
goto end;
}
f = fcli_cx();
db = fsl_cx_db(f);
if(!db){
rc = fcli_err_set(FSL_RC_NOT_A_REPO,
"Requires an opened database. See --help.");
goto end;
}
rc = noTransaction ? 0 : fsl_db_transaction_begin(db);
if(!rc){
rc = fsl_list_visit(&QueryApp.eList, 0, fsl_list_visitor_f_e, NULL);
}
if(db->beginCount>0){
assert(!noTransaction);
if(rc || fcli.clientFlags.dryRun){
FCLI_V(("Rolling back transaction.\n"));
fsl_db_transaction_rollback(db);
}else{
FCLI_V(("Committing transaction.\n"));
rc = fsl_db_transaction_commit(db);
if(rc) fsl_cx_uplift_db_error(f, db);
}
}
end:
fsl_list_visit_free(&QueryApp.eList, true);
fsl_buffer_clear(&QueryApp.scriptBuf);
return fcli_end_of_main(rc);
}