Login
Artifact [c702502c10]
Login

Artifact c702502c10ef53cbdd9fac8fa03e21743d5adadc:


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