/* -*- 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); }