// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>
#include <array>
#include <cctype>
#include <cstdint>
#include <iomanip>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include "third_party/sqlite/fuzz/icu_codes.pb.h"
#include "third_party/sqlite/fuzz/sql_queries.pb.h"
#include "third_party/sqlite/fuzz/sql_query_grammar.pb.h"
#include "third_party/sqlite/fuzz/sql_query_proto_to_string.h"

using namespace sql_query_grammar;

#define CONV_FN(TYPE, VAR_NAME) std::string TYPE##ToString(const TYPE& VAR_NAME)

#define RETURN_IF_DISABLED_QUERY(TYPE)     \
  if (disabled_queries_.count(#TYPE) != 0) \
    return "";

namespace sql_fuzzer {

namespace {
constexpr uint32_t kMaxColumnNumber = 20;
#if !defined(FUZZ_FTS3)
constexpr uint32_t kMaxTableNumber = 8;
#endif
constexpr uint32_t kMaxSchemaNumber = 4;
constexpr uint32_t kMaxWindowNumber = 5;

constexpr uint32_t kMaxColumnConstraintNumber = 10;
constexpr uint32_t kMaxTableConstraintNumber = 10;

constexpr uint32_t kMaxIndexNumber = 10;

// should be less than kMaxTableNumber
constexpr uint32_t kMaxFTS3TableNumber = 2;

constexpr uint32_t kMaxStrLength =
    200;  // So these are readable and somewhat performant, keep a maximum
          // string length......

#if !defined(FUZZ_OMIT_SAVEPOINT)
constexpr uint32_t kMaxSavePointNumber = 10;
#endif

constexpr uint32_t kMaxViewNumber = 5;
constexpr uint32_t kMaxTriggerNumber = 10;

std::set<std::string> disabled_queries_;
}  // namespace

CONV_FN(Expr, expr);
CONV_FN(Select, select);
CONV_FN(TableOrSubquery, tos);
CONV_FN(FTS3Table, ft);
CONV_FN(FTS3NearQuery, fnq);
CONV_FN(FTS3AuxiliaryFn, faf);
CONV_FN(FTS3MatchFormat, fmf);
CONV_FN(DateAndTimeFn, sfn);
CONV_FN(ExprSchemaTableFn, estf);

// ~~~~Numbered values to string~~~

// WARNING does not include space at the end
CONV_FN(Column, col) {
  if (col.has_rowid() && col.rowid())
    return "rowid";
#if defined(FUZZ_FTS3)
  if (col.has_fts3_docid() && col.fts3_docid())
    return "docid";
  if (col.has_fts3_table())
    return FTS3TableToString(col.fts3_table());
#endif
  std::string ret("Col");
  ret += std::to_string(col.column() % kMaxColumnNumber);
  return ret;
}

// WARNING does not include space at the end
CONV_FN(Table, table) {
  std::string ret("Table");
#if defined(FUZZ_FTS3)
  // only fuzzing FTS3 tables, clamp to the max FTS3 table num.
  ret += std::to_string(table.table() & kMaxFTS3TableNumber);
  if (table.fts3_content_table())
    ret += "_content";
#else
  ret += std::to_string(table.table() % kMaxTableNumber);
#endif
  return ret;
}

// WARNING does not include space at the end
CONV_FN(Schema, schema) {
  if (schema.main()) {
    return "main";
  }
  if (schema.temp()) {
    return "temp";
  }
  std::string ret("Schema");
  ret += std::to_string(schema.schema() % kMaxSchemaNumber);
  return ret;
}

// WARNING does not include space at the end
CONV_FN(WindowName, win) {
  std::string ret("Window");
  ret += std::to_string(win.window_name() % kMaxWindowNumber);
  return ret;
}

// WARNING does not include space at the end
CONV_FN(ColumnConstraintName, cc) {
  std::string ret("ColConstraint");
  ret += std::to_string(cc.constraint_name() % kMaxColumnConstraintNumber);
  return ret;
}

// WARNING does not include space at the end
CONV_FN(TableConstraintName, tc) {
  std::string ret("TableConstraint");
  ret += std::to_string(tc.constraint_name() % kMaxTableConstraintNumber);
  return ret;
}

// WARNING does not include space at the end
CONV_FN(Index, index) {
  std::string ret("Index");
  ret += std::to_string(index.index() % kMaxIndexNumber);
  return ret;
}

#if !defined(FUZZ_OMIT_SAVEPOINT)
CONV_FN(SavePoint, sp) {
  std::string ret("SavePoint");
  ret += std::to_string(sp.savepoint_num() % kMaxSavePointNumber);
  return ret;
}
#endif

CONV_FN(View, v) {
  std::string ret("View");
  ret += std::to_string(v.view() % kMaxViewNumber);
  return ret;
}

CONV_FN(Trigger, t) {
  std::string ret("Trigger");
  ret += std::to_string(t.trigger() % kMaxTriggerNumber);
  return ret;
}

// ~~~~Utility functions~~~~

std::string AscDescToString(AscDesc a) {
  switch (a) {
    case ASCDESC_NONE:
      return " ";
    case ASCDESC_ASC:
      return "ASC ";
    case ASCDESC_DESC:
      return "DESC ";
    default:
      return " ";
  }
}

std::string StrToLower(std::string s) {
  std::transform(
      s.begin(), s.end(), s.begin(),
      [](unsigned char c) -> unsigned char { return std::tolower(c); });
  return s;
}

std::string StripTrailingUnderscores(std::string s) {
  s.erase(std::find_if(s.rbegin(), s.rend(),
                       [](unsigned char ch) { return ch != '_'; })
              .base(),
          s.end());
  return s;
}

// Converts underscores to spaces in a string.
// This is because many enums like SET_NULL will be displayed at SET NULL in
// the query, and I want to use protobuf's enum to string function to save time.
std::string EnumStrReplaceUnderscores(std::string s) {
  std::transform(s.begin(), s.end(), s.begin(),
                 [](unsigned char c) -> unsigned char {
                   if (c == '_')
                     return ' ';
                   return c;
                 });
  return s;
}

// Takes garbage data and produces a string, with quotes escaped.
// Caps the number of bytes received from the protobuf at kMaxStrLength.
// The final string could be as much as kMaxStrLength*2 as we added an extra
// single quote for every single quote in the input string.
std::string ConvertToSqlString(const std::string& s) {
  std::string ret;
  ret.reserve(kMaxStrLength * 2);
  for (size_t i = 0; i < s.length() && i < kMaxStrLength; i++) {
    ret += s[i];
    if (s[i] == '\'')
      ret += '\'';
  }
  return ret;
}

// WARNING does not include space
std::string BytesToHex(const std::string& str) {
  std::ostringstream ss;
  ss << std::hex << std::setfill('0');
  for (size_t i = 0; i < str.size() && i < kMaxStrLength; i++) {
    ss << std::setw(2) << static_cast<int>(str[i]);
  }
  return ss.str();
}

// WARNING no space at end
CONV_FN(ExprSchemaTable, st) {
  std::string ret;
  if (st.has_schema_name()) {
    ret += SchemaToString(st.schema_name());
    ret += ".";
  }
  ret += TableToString(st.table_name());
  return ret;
}

CONV_FN(ExprSchemaTableColumn, stc) {
  std::string ret;
  if (stc.has_schema()) {
    ret += SchemaToString(stc.schema());
    ret += ".";
  }
  if (stc.has_table()) {
    ret += TableToString(stc.table());
    ret += ".";
  }
  ret += ColumnToString(stc.col());
  return ret;
}

// WARNING does not include parentheses, nor a space at the end
CONV_FN(ColumnList, cl) {
  std::string ret = ColumnToString(cl.col());
  for (int i = 0; i < cl.extra_cols_size(); i++) {
    ret += ", ";
    ret += ColumnToString(cl.extra_cols(i));
  }
  return ret;
}

CONV_FN(ExprList, me) {
  std::string ret = ExprToString(me.expr());
  for (int i = 0; i < me.extra_exprs_size(); i++) {
    ret += ", ";
    ret += ExprToString(me.extra_exprs(i));
  }
  return ret;
}

// WARNING does not include space
CONV_FN(CollateType, collate_type) {
  std::string ct = CollateType_Name(collate_type);
  ct.erase(0, std::string("COLLATE_").length());
  return ct;
}

// WARNING does not include space
CONV_FN(IndexedColumn, ic) {
  std::string ret;
  if (ic.has_expr()) {
    ret += ExprToString(ic.expr());
  } else {
    ret += ColumnToString(ic.col());
  }
  ret += " ";
  if (ic.has_collate_type()) {
    ret += "COLLATE ";
    ret += CollateTypeToString(ic.collate_type());
    ret += " ";
  }
  ret += AscDescToString(ic.asc_desc());

  return ret;
}

CONV_FN(IndexedColumnList, ic_list) {
  std::string ret;
  ret += IndexedColumnToString(ic_list.indexed_col());
  for (int i = 0; i < ic_list.extra_indexed_cols_size(); i++) {
    ret += ", ";
    ret += IndexedColumnToString(ic_list.extra_indexed_cols(i));
  }
  return ret;
}

CONV_FN(SchemaTableAsAlias, staa) {
  std::string ret;
  ret += ExprSchemaTableToString(staa.schema_table());
  ret += " ";

  if (staa.has_as_table_alias()) {
    ret += "AS ";
    ret += TableToString(staa.as_table_alias());
    ret += " ";
  }
  return ret;
}

// ~~~~Expression stuff~~~~

// WARNING does not include space
CONV_FN(NumericLiteral, nl) {
  static constexpr char hex_digits[] = "0123456789ABCDEF";
  static constexpr char digits[] = "0123456789";
  std::string ret;
  if (nl.hex_digits_size() > 0) {
    ret += "0x";
    for (int i = 0; i < nl.hex_digits_size(); i++) {
      ret += hex_digits[nl.hex_digits(i) % sizeof(hex_digits)];
    }

    return ret;
  }
  for (int i = 0; i < nl.digits_size(); i++) {
    ret += digits[nl.digits(i) % sizeof(digits)];
  }
  if (nl.decimal_point()) {
    ret += ".";
    if (nl.dec_digits_size() == 0) {
      ret += "0";
    } else {
      for (int i = 0; i < nl.dec_digits_size(); i++) {
        ret += digits[nl.dec_digits(i) % sizeof(digits)];
      }
    }
  }
  if (nl.exp_digits_size() > 0) {
    ret += "E";
    if (nl.negative_exp())
      ret += "-";
    if (nl.exp_digits_size() == 0) {
      ret += "0";
    } else {
      for (int i = 0; i < nl.exp_digits_size(); i++) {
        ret += digits[nl.exp_digits(i) % sizeof(digits)];
      }
    }
  }

  return ret;
}

// WARNING does not include space
CONV_FN(LiteralValue, lit_val) {
  std::string ret;
  using LitValType = LiteralValue::LitValOneofCase;
  switch (lit_val.lit_val_oneof_case()) {
    case LitValType::kNumLit:
      return std::to_string(lit_val.num_lit());
    case LitValType::kStringLit:
      ret += '\'';
      ret += ConvertToSqlString(lit_val.string_lit());
      ret += '\'';
      return ret;
    case LitValType::kBlobLit:
      ret += "x\'";
      ret += BytesToHex(lit_val.blob_lit());
      ret += '\'';
      return ret;
    case LitValType::kSpecialVal:
      // special case for NULL, TRUE, FALSE
      if (lit_val.special_val() == LiteralValue::VAL_NULL)
        return "NULL";
      if (lit_val.special_val() == LiteralValue::VAL_TRUE)
        return "TRUE";
      if (lit_val.special_val() == LiteralValue::VAL_FALSE)
        return "FALSE";
      // do not remove underscores
      return LiteralValue_SpecialVal_Name(lit_val.special_val());
    case LitValType::kNumericLit:
      return NumericLiteralToString(lit_val.numeric_lit());
    default:
      return "1";
  }
}

CONV_FN(UnaryExpr, uexpr) {
  std::string ret;
  switch (uexpr.unary_op()) {
    case UNOP_MINUS:
      ret += "-";
      break;
    case UNOP_PLUS:
      ret += "+";
      break;
    case UNOP_TILDE:
      ret += "~";
      break;
    case UNOP_NOT:
      ret += "NOT ";
      break;
    default:
      break;
  }
  ret += ExprToString(uexpr.expr());
  return ret;
}

CONV_FN(BinaryOperator, bop) {
  switch (bop) {
    case BINOP_CONCAT:
      return " || ";
      break;
    case BINOP_STAR:
      return " * ";
      break;
    case BINOP_SLASH:
      return " / ";
      break;
    case BINOP_PERCENT:
      return " % ";
      break;
    case BINOP_PLUS:
      return " + ";
      break;
    case BINOP_MINUS:
      return " - ";
      break;
    case BINOP_LELE:
      return " << ";
      break;
    case BINOP_GRGR:
      return " >> ";
      break;
    case BINOP_AMPERSAND:
      return " & ";
      break;
    case BINOP_PIPE:
      return " | ";
      break;
    case BINOP_LE:
      return " < ";
      break;
    case BINOP_LEQ:
      return " <= ";
      break;
    case BINOP_GR:
      return " > ";
      break;
    case BINOP_GREQ:
      return " >= ";
      break;
    case BINOP_EQ:
      return " = ";
      break;
    case BINOP_EQEQ:
      return " == ";
      break;
    case BINOP_NOTEQ:
      return " != ";
      break;
    case BINOP_LEGR:
      return " <> ";
      break;
    case BINOP_IS:
      return " IS ";
      break;
    case BINOP_ISNOT:
      return " IS NOT ";
      break;
    case BINOP_IN:
      return " IN ";  // CORPUS specialize?
      break;
    case BINOP_LIKE:
      return " LIKE ";  // CORPUS specialize?
      break;
    case BINOP_GLOB:
      return " GLOB ";  // CORPUS
      break;
    case BINOP_MATCH:
      return " MATCH ";  // CORPUS
      break;
    case BINOP_REGEXP:
      return " REGEXP ";  // CORPUS
      break;
    case BINOP_AND:
      return " AND ";
      break;
    case BINOP_OR:
      return " OR ";
      break;
    default:
      return " AND ";
      break;
  }
}

// TODO(mpdenton) generate better REGEXP queries in non-fts3 case. (in
// ColumnComparison as well)
// TODO(mpdenton) generate better MATCH queries in non-fts3 case.
CONV_FN(BinaryExpr, bexpr) {
  std::string ret;
  ret += ExprToString(bexpr.lhs());
  ret += BinaryOperatorToString(bexpr.op());
#if defined(FUZZ_FTS3)
  if (bexpr.op() == BINOP_MATCH && bexpr.has_fmt()) {
    ret += FTS3MatchFormatToString(bexpr.fmt());
    return ret;
  }
#endif
  ret += ExprToString(bexpr.rhs());
  return ret;
}

CONV_FN(AggregateFn, af) {
  std::string ret;
  ret += StrToLower(AggregateFn_FnName_Name(af.fn_name()));
  ret += "(";
  if (af.fn_name() == AggregateFn::COUNT && af.count_star())
    return ret + "*) ";
  if (af.distinct())
    ret += "DISTINCT ";
  if (af.has_col1()) {
    ret += ColumnToString(af.col1());
    if (af.fn_name() == AggregateFn::GROUP_CONCAT && af.has_col2()) {
      ret += ", ";
      ret += ColumnToString(af.col2());
    }
  } else {
    ret += ExprToString(af.expr1());
    if (af.fn_name() == AggregateFn::GROUP_CONCAT && af.has_expr2()) {
      ret += ", ";
      ret += ExprToString(af.expr2());
    }
  }

  ret += ") ";
  return ret;
}

CONV_FN(ZeroArgFn, zaf) {
  std::string func = ZeroArgFn_Name(zaf);
  // Remove ZFN_ prefix
  func.erase(0, std::string("ZFN_").length());
  return StrToLower(func) + "() ";
}

CONV_FN(OneArgFn, oaf) {
  std::string ret;
  ret += StripTrailingUnderscores(
      StrToLower(OneArgFn_OneArgFnEnum_Name(oaf.fn_enum())));
  ret += "(";
  ret += ExprToString(oaf.arg1());
  ret += ") ";
  return ret;
}

CONV_FN(TwoArgFn, taf) {
  std::string ret;
  ret += StrToLower(TwoArgFn_TwoArgFnEnum_Name(taf.fn_enum()));
  ret += "(";
  ret += ExprToString(taf.arg1());
  ret += ", ";
  ret += ExprToString(taf.arg2());
  ret += ") ";
  return ret;
}

CONV_FN(ThreeArgFn, taf) {
  std::string ret;
  ret += StrToLower(ThreeArgFn_ThreeArgFnEnum_Name(taf.fn_enum()));
  ret += "(";
  ret += ExprToString(taf.arg1());
  ret += ", ";
  ret += ExprToString(taf.arg2());
  ret += ", ";
  ret += ExprToString(taf.arg3());
  ret += ") ";
  return ret;
}

CONV_FN(VarNumFn, vfn) {
  std::string ret;
  ret += StrToLower(VarNumFn_VarNumFnEnum_Name(vfn.fn_enum()));
  ret += "(";
  ret += ExprToString(vfn.arg1());
  ret += ", ";
  ret += ExprToString(vfn.arg2());
  for (int i = 0; i < vfn.other_args_size(); i++) {
    ret += ", ";
    ret += ExprToString(vfn.other_args(i));
  }
  ret += ") ";
  return ret;
}

CONV_FN(CharFn, cfn) {
  std::string ret("char(");
  ret += std::to_string(cfn.char_());
  for (int i = 0; i < cfn.extra_chars_size(); i++) {
    ret += ", ";
    ret += std::to_string(cfn.extra_chars(i));
  }
  ret += ") ";
  return ret;
}

CONV_FN(SimpleFn, sfn) {
  // oneof
  if (sfn.has_zero_arg_fn()) {
    return ZeroArgFnToString(sfn.zero_arg_fn());
  } else if (sfn.has_one_arg_fn()) {
    return OneArgFnToString(sfn.one_arg_fn());
  } else if (sfn.has_two_arg_fn()) {
    return TwoArgFnToString(sfn.two_arg_fn());
  } else if (sfn.has_three_arg_fn()) {
    return ThreeArgFnToString(sfn.three_arg_fn());
  } else if (sfn.has_varnum_fn()) {
    return VarNumFnToString(sfn.varnum_fn());
  } else if (sfn.has_char_fn()) {
    return CharFnToString(sfn.char_fn());
  } else {
    return "changes() ";
  }
}

CONV_FN(PrintfFormatSpecifier, pfs) {
  std::string ret("%");
  for (int i = 0; i < pfs.flags_size(); i++) {
    switch (pfs.flags(i)) {
      case PrintfFormatSpecifier::MINUS:
        ret += "-";
        break;
      case PrintfFormatSpecifier::PLUS:
        ret += "+";
        break;
      case PrintfFormatSpecifier::SPACE:
        ret += " ";
        break;
      case PrintfFormatSpecifier::ZERO:
        ret += "0";
        break;
      case PrintfFormatSpecifier::HASH:
        ret += "#";
        break;
      case PrintfFormatSpecifier::COMMA:
        ret += ",";
        break;
      case PrintfFormatSpecifier::BANG:
        ret += "!";
        break;
    }
  }
  if (pfs.has_width()) {
    ret += std::to_string(pfs.width());
  } else if (pfs.width_star()) {
    ret += "*";
  }
  if (pfs.has_precision()) {
    ret += ".";
    ret += std::to_string(pfs.precision());
  }
  if (pfs.has_length()) {
    if (pfs.length() % 3 == 1) {
      ret += "l";
    } else if (pfs.length() % 3 == 2) {
      ret += "ll";
    }
  }
  if (pfs.percent()) {
    ret += "%";
  } else {
    std::string specifier = PrintfFormatSpecifier_SubType_Name(pfs.sub_type());
    if (pfs.lowercase())
      specifier = StrToLower(specifier);
    ret += specifier;
  }
  return ret;
}

CONV_FN(Printf, p) {
  std::string ret("printf(\'");
  for (int i = 0; i < p.specifiers_size(); i++) {
    ret += PrintfFormatSpecifierToString(p.specifiers(i));
    if (i < p.strings_size()) {
      ret += ConvertToSqlString(p.strings(i));
    }
  }
  ret += "\'";
  for (int i = 0; i < p.exprs_size(); i++) {
    ret += ", ";
    ret += ExprToString(p.exprs(i));
  }
  ret += ") ";
  return ret;
}

CONV_FN(Fn, fn) {
  // oneof
  if (fn.has_simple_fn()) {
    return SimpleFnToString(fn.simple_fn());
  } else if (fn.has_fts_aux_fn()) {
#if defined(FUZZ_FTS3)
    return FTS3AuxiliaryFnToString(fn.fts_aux_fn()) + " ";
#else
    return "changes() ";
#endif
  } else if (fn.has_dat_fn()) {
    return DateAndTimeFnToString(fn.dat_fn());
  } else if (fn.has_aggregate_fn()) {
    return AggregateFnToString(fn.aggregate_fn());
  } else if (fn.has_printf()) {
    return PrintfToString(fn.printf());
  } else {
    return "changes() ";
  }
}

CONV_FN(ParenthesesExpr, pexpr) {
  std::string ret("(");
  ret += ExprToString(pexpr.expr());
  for (int i = 0; i < pexpr.other_exprs_size(); i++) {
    ret += ", ";
    ret += ExprToString(pexpr.other_exprs(i));
  }
  ret += ") ";
  return ret;
}

CONV_FN(CastExpr, cexpr) {
  std::string ret("CAST(");
  ret += ExprToString(cexpr.expr());
  ret += " AS ";
  ret += EnumStrReplaceUnderscores(
      CastTypeName_CastTypeNameEnum_Name(cexpr.type_name().type_enum()));
  ret += ") ";
  return ret;
}

CONV_FN(CollateExpr, cexpr) {
  std::string ret;
  ret += ExprToString(cexpr.expr());
  ret += " COLLATE ";
  ret += CollateTypeToString(cexpr.collate_type());
  return ret;
}

CONV_FN(Expr1, e) {
  std::string ret;
  ret += ExprToString(e.expr1());
  ret += " ";
  if (e.not_())
    ret += "NOT ";
  ret += EnumStrReplaceUnderscores(Expr1_PossibleKeywords_Name(e.keyword()));
  ret += " ";
  ret += ExprToString(e.expr2());
  ret += " ";  //
  if (e.has_escape_expr()) {
    ret += "ESCAPE ";
    ret += ExprToString(e.escape_expr());
    ret += " ";  //
  }
  return ret;
}

CONV_FN(ExprNullTests, e) {
  std::string ret = ExprToString(e.expr());
  ret += " ";
  ret += EnumStrReplaceUnderscores(
      ExprNullTests_PossibleKeywords_Name(e.keyword()));
  ret += " ";
  return ret;
}

CONV_FN(ExprIs, e) {
  std::string ret = ExprToString(e.expr1());
  ret += " IS ";
  if (e.not_())
    ret += "NOT ";
  ret += ExprToString(e.expr2());
  ret += " ";  //
  return ret;
}

CONV_FN(ExprBetween, e) {
  std::string ret;
  ret += ExprToString(e.expr1());
  ret += " ";
  if (e.not_())
    ret += "NOT ";
  ret += "BETWEEN ";
  ret += ExprToString(e.expr2());
  ret += " AND ";
  ret += ExprToString(e.expr3());
  return ret;
}

CONV_FN(ExprInParen, e) {
  std::string ret("(");
  // oneof
  if (e.has_select()) {
    ret += SelectToString(e.select());
  } else if (e.has_exprs()) {
    ret += ExprListToString(e.exprs());
  }

  ret += ") ";
  return ret;
}

CONV_FN(ExprIn, e) {
  std::string ret = ExprToString(e.expr());
  ret += " ";
  if (e.not_())
    ret += "NOT ";
  if (e.has_expr_in_paren()) {
    ret += ExprInParenToString(e.expr_in_paren());
  } else if (e.has_schema_table()) {
    ret += ExprSchemaTableToString(e.schema_table());
  } else if (e.has_schema_table_fn()) {
    ret += ExprSchemaTableFnToString(e.schema_table_fn());
  } else {
    ret += "()";
  }
  return ret + " ";
}

CONV_FN(ExprExists, e) {
  std::string ret;
  if (e.not_())
    ret += "NOT EXISTS ";
  else if (e.exists())
    ret += "EXISTS ";
  ret += "(";
  ret += SelectToString(e.select());
  ret += ") ";
  return ret;
}

// WARNING no space at end
CONV_FN(ExprWhenThen, e) {
  std::string ret("WHEN ");
  ret += ExprToString(e.when_expr());
  ret += " THEN ";
  ret += ExprToString(e.then_expr());
  return ret;
}

CONV_FN(ExprCase, e) {
  std::string ret("CASE ");
  if (e.has_expr()) {
    ret += ExprToString(e.expr());
    ret += " ";
  }
  ret += ExprWhenThenToString(e.when_then());
  ret += " ";
  for (int i = 0; i < e.extra_when_thens_size(); i++) {
    ret += ExprWhenThenToString(e.extra_when_thens(i));
    ret += " ";
  }
  if (e.has_else_expr()) {
    ret += "ELSE ";
    ret += ExprToString(e.else_expr());
    ret += " ";
  }
  ret += "END ";
  return ret;
}

CONV_FN(ExprRaiseFn, e) {
  std::string ret("RAISE(");
  if (e.ignore()) {
    ret += "IGNORE";
  } else {
    ret +=
        EnumStrReplaceUnderscores(ExprRaiseFn_RaiseFnEnum_Name(e.raise_fn()));
    ret += " ";
    ret += ", \'";
    ret += ConvertToSqlString(e.error_msg());
    ret += "\'";
  }
  ret += ") ";
  return ret;
}

CONV_FN(ComplicatedExpr, expr) {
  using ExprType = ComplicatedExpr::ComplicatedExprOneofCase;
  switch (expr.complicated_expr_oneof_case()) {
    case ExprType::kExprStc:
      return ExprSchemaTableColumnToString(expr.expr_stc());
    case ExprType::kUnaryExpr:
      return UnaryExprToString(expr.unary_expr());
    case ExprType::kBinaryExpr:
      return BinaryExprToString(expr.binary_expr());
    case ExprType::kFnExpr:
      return FnToString(expr.fn_expr());
    case ExprType::kParExpr:
      return ParenthesesExprToString(expr.par_expr());
    case ExprType::kCastExpr:
      return CastExprToString(expr.cast_expr());
    case ExprType::kCollateExpr:
      return CollateExprToString(expr.collate_expr());
    case ExprType::kExpr1:
      return Expr1ToString(expr.expr1());
    case ExprType::kExprNullTests:
      return ExprNullTestsToString(expr.expr_null_tests());
    case ExprType::kExprIs:
      return ExprIsToString(expr.expr_is());
    case ExprType::kExprBetween:
      return ExprBetweenToString(expr.expr_between());
    case ExprType::kExprIn:
      return ExprInToString(expr.expr_in());
    case ExprType::kExprExists:
      return ExprExistsToString(expr.expr_exists());
    case ExprType::kExprCase:
      return ExprCaseToString(expr.expr_case());
    case ExprType::kExprRaise:
      return ExprRaiseFnToString(expr.expr_raise());
    default:
      return "1";
  }
}

// TODO(mpdenton) wrap in parentheses???
CONV_FN(Expr, expr) {
  if (expr.has_lit_val()) {
    return LiteralValueToString(expr.lit_val());
  } else if (expr.has_comp_expr()) {
    return ComplicatedExprToString(expr.comp_expr());
  } else {  // default
    return "1";
  }
}

// ~~~~Other~~~~

std::string ForeignKeyClauseNotMatchToString(
    const ForeignKeyClauseNotMatch& nm) {
  std::string ret("ON ");
  ret += EnumStrReplaceUnderscores(
      ForeignKeyClauseNotMatch_DeleteOrUpdate_Name(nm.del_or_update()));
  ret += " ";
  ret += EnumStrReplaceUnderscores(
      ForeignKeyClauseNotMatch_Action_Name(nm.action()));
  ret += " ";
  return ret;
}

CONV_FN(ForeignKeyClauseCore, fkc) {
  if (fkc.has_fkey_not_match())
    return ForeignKeyClauseNotMatchToString(fkc.fkey_not_match());

  return "MATCH PARTIAL";  // Sqlite does not actually parse MATCH clauses. This
                           // is assumed to be MATCH SIMPLE.
}

CONV_FN(DeferStrategy, ds) {
  std::string ret;
  if (ds.not_()) {
    ret += "NOT ";
  }
  ret += "DEFERRABLE ";
  ret +=
      EnumStrReplaceUnderscores(DeferStrategy_DeferStratEnum_Name(ds.strat()));
  return ret;
}

CONV_FN(ForeignKeyClause, fkey_clause) {
  std::string ret("REFERENCES ");
  ret += TableToString(fkey_clause.foreign_table());
  if (fkey_clause.has_col_list()) {
    ret += "(";
    ret += ColumnListToString(fkey_clause.col_list());
    ret += ")";
  }
  ret += " ";
  for (int i = 0; i < fkey_clause.fkey_cores_size(); i++) {
    ret += ForeignKeyClauseCoreToString(fkey_clause.fkey_cores(i));
    ret += " ";
  }
  if (fkey_clause.has_defer_strat()) {
    ret += DeferStrategyToString(fkey_clause.defer_strat());
    ret += " ";
  }
  return ret;
}

CONV_FN(ConflictClause, conf) {
  if (!conf.has_on_conflict())
    return " ";

  std::string ret("ON CONFLICT ");
  ret += EnumStrReplaceUnderscores(
      ConflictClause_OnConflict_Name(conf.on_conflict()));
  ret += " ";
  return ret;
}

CONV_FN(ColConstraintOpt1, opt1) {
  std::string ret("PRIMARY KEY ");
  ret += AscDescToString(opt1.asc_desc());
  // space at the end already
  ret += ConflictClauseToString(opt1.conflict());
  if (opt1.autoincrement())
    ret += "AUTOINCREMENT ";

  return ret;
}

CONV_FN(ColConstraintOpt2, opt2) {
  std::string ret("DEFAULT ");
  if (opt2.has_expr()) {
    ret += "(";
    ret += ExprToString(opt2.expr());
    ret += ")";
  } else {
    ret += LiteralValueToString(opt2.lit_val());
  }

  ret += " ";
  return ret;
}

CONV_FN(ColumnConstraint, col_constr) {
  std::string ret;
  if (col_constr.has_constraint_name()) {
    ret += "CONSTRAINT ";
    ret += ColumnConstraintNameToString(col_constr.constraint_name());
    ret += " ";
  }

  using ColConstrType = ColumnConstraint::ColConstraintOneofCase;
  switch (col_constr.col_constraint_oneof_case()) {
    case ColConstrType::kOpt1:
      ret += ColConstraintOpt1ToString(col_constr.opt1());
      break;
    case ColConstrType::kNotNullConfClause:
      ret += "NOT NULL ";
      ret += ConflictClauseToString(col_constr.not_null_conf_clause());
      break;
    case ColConstrType::kUniqueConfClause:
      ret += "UNIQUE ";
      ret += ConflictClauseToString(col_constr.unique_conf_clause());
      break;
    case ColConstrType::kCheckExpr:
      ret += "CHECK(";
      ret += ExprToString(col_constr.check_expr());
      ret += ") ";
      break;
    case ColConstrType::kOpt2:
      ret += ColConstraintOpt2ToString(col_constr.opt2());
      break;
    case ColConstrType::kCollate:
      ret += "COLLATE ";
      ret += CollateTypeToString(col_constr.collate());
      ret += " ";
      break;
    case ColConstrType::kFkeyClause:
      ret += ForeignKeyClauseToString(col_constr.fkey_clause());
      break;
    default:
      ret += ColConstraintOpt2ToString(col_constr.opt2_fallback());
  }

  return ret;
}

CONV_FN(TypeName, type_name) {
  std::string ret;
  ret += EnumStrReplaceUnderscores(
      CastTypeName_CastTypeNameEnum_Name(type_name.ctn().type_enum()));
  if (type_name.has_sn()) {
    ret += "(";
    ret += std::to_string(type_name.sn());
    ret += ")";
  }
  ret += " ";
  return ret;
}

CONV_FN(ColumnDef, col_def) {
  std::string ret;
  ret += ColumnToString(col_def.col());
  ret += " ";
  if (col_def.has_type_name()) {
    ret += TypeNameToString(col_def.type_name());
    ret += " ";
  }

  for (int i = 0; i < col_def.col_constraints_size(); i++) {
    ret += ColumnConstraintToString(col_def.col_constraints(i));
    ret += " ";
  }

  return ret;
}

CONV_FN(TableConstraintOpt1, opt1) {
  std::string ret;
  ret += EnumStrReplaceUnderscores(
      TableConstraintOpt1_ConstraintType_Name(opt1.constraint_type()));
  ret += "(";
  ret += IndexedColumnListToString(opt1.indexed_col_list());
  ret += ") ";
  ret += ConflictClauseToString(opt1.conf_clause());

  return ret;
}

CONV_FN(TableConstraintOpt2, opt2) {
  std::string ret("FOREIGN KEY (");
  ret += ColumnListToString(opt2.cols());
  ret += ") ";

  ret += ForeignKeyClauseToString(opt2.fkey_clause());
  return ret;
}

CONV_FN(TableConstraint, t_constr) {
  std::string ret;
  if (t_constr.has_name()) {
    ret += "CONSTRAINT ";
    ret += TableConstraintNameToString(t_constr.name());
    ret += " ";
  }

  if (t_constr.has_opt1()) {
    ret += TableConstraintOpt1ToString(t_constr.opt1());
  } else if (t_constr.has_check_expr()) {
    ret += "CHECK(";
    ret += ExprToString(t_constr.check_expr());  // TODO(mpdenton)
    ret += ") ";
  } else if (t_constr.has_opt2()) {
    ret += TableConstraintOpt2ToString(t_constr.opt2());
  } else {
    // default to no constraint
    ret += "CHECK(1)";
  }

  ret += " ";
  return ret;
}

CONV_FN(CreateTableOpt1, opt1) {
  std::string ret("(");
  ret += ColumnDefToString(opt1.col_def());
  for (int i = 0; i < opt1.extra_col_defs_size(); i++) {
    ret += ", ";
    ret += ColumnDefToString(opt1.extra_col_defs(i));
  }
  for (int i = 0; i < opt1.table_constraints_size(); i++) {
    ret += ", ";
    ret += TableConstraintToString(opt1.table_constraints(i));
  }
  ret += ") ";

  if (opt1.without_rowid())
    ret += "WITHOUT ROWID ";

  return ret;
}

CONV_FN(CreateTable, create_table) {
  RETURN_IF_DISABLED_QUERY(CreateTable);
#if defined(FUZZ_FTS3)
  return "";  // Don't create normal tables in FTS3 fuzzing mode.
#endif
  std::string ret("CREATE ");
  if (create_table.has_temp_modifier()) {
    ret += EnumStrReplaceUnderscores(
               TempModifier_Name(create_table.temp_modifier()))
               .erase(0, std::string("TM_").length());
    ret += " ";
  }
  ret += "TABLE ";
  if (create_table.if_not_exists())
    ret += "IF NOT EXISTS ";

  ret += ExprSchemaTableToString(create_table.schema_table());
  ret += " ";

  // TODO(mpdenton) need spaces at the end here???
  using TableCreationType = CreateTable::CreateTableOneofCase;
  switch (create_table.create_table_oneof_case()) {
    case TableCreationType::kOp1:
      ret += CreateTableOpt1ToString(create_table.op1());
      break;
    case TableCreationType::kAsSelectStmt:
      ret += SelectToString(create_table.as_select_stmt());
      break;
    default:
      ret += CreateTableOpt1ToString(create_table.op());
      break;
  }

  return ret;  // TODO(mpdenton)
}

// ~~~~For INSERT and SELECT~~~~

CONV_FN(CommonTableExpr, cte) {
  std::string ret;
  ret += TableToString(cte.table());
  if (cte.columns_size() > 0) {
    ret += "(";
    ret += ColumnToString(cte.columns(0));
    for (int i = 1; i < cte.columns_size(); i++) {
      ret += ", ";
      ret += ColumnToString(cte.columns(i));
    }
    ret += ")";
  }
  ret += " AS (";
  ret += SelectToString(cte.select());
  ret += ") ";
  return ret;
}

CONV_FN(WithStatement, ws) {
  std::string ret("WITH ");
  if (ws.recursive())
    ret += "RECURSIVE ";
  ret += CommonTableExprToString(ws.table_expr());
  ret += " ";
  for (int i = 0; i < ws.extra_table_exprs_size(); i++) {
    ret += CommonTableExprToString(ws.extra_table_exprs(i));
    ret += " ";
  }
  ret += " ";
  return ret;
}

// ~~~~INSERT~~~~

// WARNING no space at end
CONV_FN(ColumnComparison, cc) {
  std::string ret;
  ret += ExprSchemaTableColumnToString(cc.col());
  ret += BinaryOperatorToString(cc.op());
#if defined(FUZZ_FTS3)
  if (cc.op() == BINOP_MATCH && cc.has_fmt()) {
    ret += FTS3MatchFormatToString(cc.fmt());
    return ret;
  }
#endif
  ret += ExprToString(cc.expr());
  return ret;
}

// WARNING no space at end
CONV_FN(ExprComparisonHighProbability, echp) {
  if (echp.has_cc()) {
    return ColumnComparisonToString(echp.cc());
  } else if (echp.has_expr()) {
    return ExprToString(echp.expr());
  } else {
    return "Col0 = 1";  // default
  }
}

CONV_FN(WhereStatement, ws) {
  return "WHERE " + ExprComparisonHighProbabilityToString(ws.expr()) + " ";
}

#ifndef SQLITE_OMIT_UPSERT
CONV_FN(UpsertClausePart1, uc1) {
  std::string ret;
  ret += "(";
  ret += IndexedColumnListToString(uc1.icol_list());
  ret += ") ";
  if (uc1.has_where_stmt()) {
    ret += WhereStatementToString(uc1.where_stmt());
    ret += " ";
  }
  return ret;
}
#endif

// WARNING no space at end
CONV_FN(ColEqualsExpr, cee) {
  std::string ret;
  if (cee.has_col()) {
    ret += ColumnToString(cee.col());
  } else if (cee.has_col_list()) {
    ret += ColumnListToString(cee.col_list());
  } else {
    ret += "Col0";  // default
  }
  ret += " = ";
  ret += ExprToString(cee.expr());
  return ret;
}

CONV_FN(UpsertClausePart2, uc2) {
  std::string ret("SET ");
  ret += ColEqualsExprToString(uc2.cee());
  for (int i = 0; i < uc2.extra_cees_size(); i++) {
    ret += ", ";
    ret += ColEqualsExprToString(uc2.extra_cees(i));
  }
  if (uc2.has_where_stmt()) {
    ret += " ";
    ret += WhereStatementToString(uc2.where_stmt());
  }
  ret += " ";
  return ret;
}

CONV_FN(UpsertClause, uc) {
#ifndef SQLITE_OMIT_UPSERT
  std::string ret("ON CONFLICT ");
  if (uc.has_uclause_p1()) {
    ret += UpsertClausePart1ToString(uc.uclause_p1());
  }
  ret += "DO ";
  if (uc.has_uclause_p2()) {
    ret += "UPDATE ";
    ret += UpsertClausePart2ToString(uc.uclause_p2());
  } else {
    ret += "NOTHING ";
  }
  return ret;
#else
  return "";  // fine to return empty string here
#endif
}

CONV_FN(ValuesStatement, values) {
  std::string ret("VALUES (");
  ret += ExprListToString(values.expr_list());
  ret += ")";
  for (int i = 0; i < values.extra_expr_lists_size(); i++) {
    ret += ", (";
    ret += ExprListToString(values.extra_expr_lists(i));
    ret += ")";
  }
  ret += " ";
  return ret;
}

CONV_FN(Insert, insert) {
  RETURN_IF_DISABLED_QUERY(Insert);
  std::string ret;
  if (insert.has_with()) {
    ret += WithStatementToString(insert.with());
    ret += " ";
  }

  ret +=
      EnumStrReplaceUnderscores(Insert_InsertType_Name(insert.insert_type()));
  ret += " INTO ";
  ret += SchemaTableAsAliasToString(insert.staa());

  if (insert.has_col_list()) {
    ret += "(";
    ret += ColumnListToString(insert.col_list());
    ret += ") ";
  }

  // oneof
  if (insert.has_values()) {
    ret += ValuesStatementToString(insert.values());
    ret += " ";
  } else if (insert.has_select()) {
    ret += SelectToString(insert.select());
    ret += " ";
  } else {
    ret += "DEFAULT VALUES ";
  }

  if (insert.has_upsert_clause()) {
    ret += UpsertClauseToString(insert.upsert_clause());
    ret += " ";
  }
  return ret;
}

// ~~~~DELETE~~~~

CONV_FN(QualifiedTableName, qtn) {
  std::string ret;
  ret += SchemaTableAsAliasToString(qtn.staa());
  ret += " ";
  if (qtn.indexed()) {
    if (qtn.not_indexed()) {
      ret += "NOT INDEXED ";
    } else {
      ret += "INDEXED BY ";
      ret += IndexToString(qtn.indexed_by());
      ret += " ";
    }
  }
  return ret;
}

CONV_FN(Delete, delete_) {
  RETURN_IF_DISABLED_QUERY(Delete);
  std::string ret;
  if (delete_.has_with()) {
    ret += WithStatementToString(delete_.with());
    ret += " ";
  }
  ret += "DELETE FROM ";
  ret += QualifiedTableNameToString(delete_.qtn());
  if (delete_.has_where()) {
    ret += WhereStatementToString(delete_.where());
  }
  ret += " ";
  return ret;
}

// ~~~~UPDATE~~~~
// WARNING no space at end
CONV_FN(Update, update) {
  RETURN_IF_DISABLED_QUERY(Update);
  std::string ret;
  if (update.has_with()) {
    ret += WithStatementToString(update.with());
    ret += " ";
  }
  ret += "UPDATE ";
  if (update.has_update_type()) {
    ret +=
        EnumStrReplaceUnderscores(Update_UpdateType_Name(update.update_type()));
    ret += " ";
  }
  ret += QualifiedTableNameToString(update.qtn());
  ret += " ";
  ret += UpsertClausePart2ToString(update.ucp2());
  return ret;
}
// TODO(mpdenton) restrictions on UPDATEs in CREATE TRIGGER????

// ~~~~SELECT~~~~

CONV_FN(ExprColAlias, eca) {
  std::string ret;
  ret += ExprToString(eca.expr());
  ret += " ";
  if (eca.has_col_alias()) {
    if (eca.as()) {
      ret += "AS ";
    }
    ret += ColumnToString(eca.col_alias());
    ret += " ";
  }
  return ret;
}

// WARNING no space at end
CONV_FN(ResultColumn, rc) {
  std::string ret;
  // oneof
  if (rc.has_col()) {
    return ColumnToString(rc.col());
  } else if (rc.has_eca()) {
    return ExprColAliasToString(rc.eca());
  } else if (rc.has_table_star()) {
    return TableToString(rc.table_star()) + ".*";
  } else if (rc.has_fts3_fn()) {
#if defined(FUZZ_FTS3)
    return FTS3AuxiliaryFnToString(rc.fts3_fn());
#else
    return "*";
#endif
  } else {
    return "*";
  }
}

CONV_FN(AsTableAlias, ata) {
  std::string ret;
  if (ata.as()) {
    ret += "AS ";
  }
  ret += TableToString(ata.table_alias());
  ret += " ";
  return ret;
}

// WARNING no space at end
CONV_FN(JoinOperator, jo) {
  if (jo.comma())
    return ",";

  std::string ret;
  if (jo.natural())
    ret += "NATURAL ";

  if (jo.join_type() != JoinOperator::NONE) {
    ret +=
        EnumStrReplaceUnderscores(JoinOperator_JoinType_Name(jo.join_type()));
    ret += " ";
  }
  ret += "JOIN ";
  return ret;
}

CONV_FN(JoinConstraint, jc) {
  // oneof
  if (jc.has_on_expr()) {
    return "ON " + ExprToString(jc.on_expr()) + " ";
  } else if (jc.has_using_expr()) {
    std::string ret("(");
    ret += ColumnListToString(jc.using_expr().col_list());
    ret += ") ";
    return ret;
  }
  return " ";
}

CONV_FN(JoinClauseCore, jcc) {
  std::string ret;
  ret += JoinOperatorToString(jcc.join_op());
  ret += " ";
  ret += TableOrSubqueryToString(jcc.tos());
  ret += " ";
  ret += JoinConstraintToString(jcc.join_constraint());
  ret += " ";
  return ret;
}

CONV_FN(JoinClause, jc) {
  std::string ret;
  ret += TableOrSubqueryToString(jc.tos());
  ret += " ";
  for (int i = 0; i < jc.clauses_size(); i++) {
    ret += JoinClauseCoreToString(jc.clauses(i));
  }
  ret += " ";
  return ret;
}

// TODO(mpdenton) ExprIn needs it schematablefn!!!!!

CONV_FN(ExprSchemaTableFn, estf) {
  std::string ret;
  const TableFn& tfn = estf.table_fn();
  // oneof for pragma fns
  if (tfn.has_foreign_key_list()) {
    ret += "pragma_foreign_key_list(\'";
    ret += TableToString(tfn.foreign_key_list());
    ret += "\') ";
  } else if (tfn.has_index_info()) {
    ret += "pragma_index_info(\'";
    ret += IndexToString(tfn.index_info());
    ret += "\') ";
  } else if (tfn.has_index_list()) {
    ret += "pragma_index_list(\'";
    ret += TableToString(tfn.index_list());
    ret += "\') ";
  } else if (tfn.has_index_xinfo()) {
    ret += "pragma_index_xinfo(\'";
    ret += IndexToString(tfn.index_xinfo());
    ret += "\') ";
  } else if (tfn.has_integrity_check()) {
    ret += "pragma_integrity_check(\'";
    ret += std::to_string(tfn.integrity_check());
    ret += "\') ";
  } else if (tfn.has_optimize()) {
    ret += "pragma_optimize(\'";
    ret += std::to_string(tfn.optimize());
    ret += "\') ";
  } else if (tfn.has_quick_check()) {
    ret += "pragma_quick_check(\'";
    ret += std::to_string(tfn.quick_check());
    ret += "\') ";
  } else if (tfn.has_table_info()) {
    ret += "pragma_table_info(\'";
    ret += TableToString(tfn.table_info());
    ret += "\') ";
  } else if (tfn.has_table_xinfo()) {
    ret += "pragma_table_xinfo(\'";
    ret += TableToString(tfn.table_xinfo());
    ret += "\') ";
  } else {
    ret += StrToLower(PragmaFnZeroArgOneResult_Name(tfn.no_arg_one_result()))
               .erase(0, std::string("PFN_ZO_").length());
    ret += "() ";
  }
  return ret;
}

CONV_FN(TableOrSubqueryOption2, toso2) {
  std::string ret;
  ret += ExprSchemaTableFnToString(toso2.schema_table_fn());
  ret += " ";
  if (toso2.has_as_table_alias()) {
    ret += AsTableAliasToString(toso2.as_table_alias());
  }
  return ret;
}

CONV_FN(TableOrSubqueryOption3, tos3) {
  std::string ret;
  if (tos3.tos_list_size() > 0) {
    ret += TableOrSubqueryToString(tos3.tos_list(0));
    for (int i = 1; i < tos3.tos_list_size(); i++) {
      ret += ", ";
      ret += TableOrSubqueryToString(tos3.tos_list(i));
    }
  } else {
    ret += JoinClauseToString(tos3.join_clause());
  }
  return ret;
}

CONV_FN(TableOrSubqueryOption4, tos4) {
  std::string ret("(");
  ret += SelectToString(tos4.select());
  ret += ") ";
  if (tos4.has_as_table_alias()) {
    ret += AsTableAliasToString(tos4.as_table_alias());
    ret += " ";
  }
  return ret;
}

CONV_FN(TableOrSubquery, tos) {
  // oneof
  if (tos.has_qtn()) {
    return QualifiedTableNameToString(tos.qtn()) + " ";
  } else if (tos.has_toso2()) {
    return TableOrSubqueryOption2ToString(tos.toso2()) + " ";
  } else if (tos.has_toso3()) {
    return "(" + TableOrSubqueryOption3ToString(tos.toso3()) + ") ";
  } else if (tos.has_toso4()) {
    return TableOrSubqueryOption4ToString(tos.toso4()) + " ";
  } else {
    return ExprSchemaTableToString(tos.schema_table_expr()) + " ";
  }
}

CONV_FN(FromStatement, fs) {
  return "FROM " + TableOrSubqueryOption3ToString(fs.tos3());
}

CONV_FN(GroupByStatement, gbs) {
  std::string ret("GROUP BY ");
  ret += ExprListToString(gbs.exprs());
  ret += " ";
  if (gbs.has_having_expr()) {
    ret += "HAVING ";
    ret += ExprToString(gbs.having_expr());
    ret += " ";
  }
  return ret;
}

CONV_FN(WindowStatement, ws) {
#if !defined(SQLITE_OMIT_WINDOWFUNC)
  return "";
#else
  return "";
#endif
}

CONV_FN(SelectStatementCore, ssc) {
  std::string ret;
  ret += EnumStrReplaceUnderscores(
      SelectStatementCore_SelectOrDistinct_Name(ssc.s_or_d()));
  ret += " ";
  if (ssc.result_columns_size() == 0) {
    ret += "* ";
  } else {
    ret += ResultColumnToString(ssc.result_columns(0));
    for (int i = 1; i < ssc.result_columns_size(); i++) {
      ret += ", ";
      ret += ResultColumnToString(ssc.result_columns(i));
    }
    ret += " ";
  }
  if (ssc.has_from()) {
    ret += FromStatementToString(ssc.from());
    ret += " ";
  }
  if (ssc.has_where()) {
    ret += WhereStatementToString(ssc.where());
    ret += " ";
  }
  if (ssc.has_groupby()) {
    ret += GroupByStatementToString(ssc.groupby());
    ret += " ";
  }
  if (ssc.has_window()) {
    ret += WindowStatementToString(ssc.window());
    ret += " ";
  }
  return ret;
}

CONV_FN(SelectSubStatement, sss) {
  // oneof
  if (sss.has_select_core()) {
    return SelectStatementCoreToString(sss.select_core());
  } else if (sss.has_values()) {
    return ValuesStatementToString(sss.values());
  } else {
    return ValuesStatementToString(sss.values_fallback());
  }
}

CONV_FN(ExprOrderingTerm, eot) {
  std::string ret = ExprToString(eot.expr());
  ret += " ";
  if (eot.has_collate_type()) {
    ret += "COLLATE ";
    ret += CollateTypeToString(eot.collate_type());
    ret += " ";
  }
  ret += AscDescToString(eot.asc_desc());
  return ret;
}

CONV_FN(OrderByStatement, obs) {
  std::string ret("ORDER BY ");
  ret += ExprOrderingTermToString(obs.ord_term());
  for (int i = 0; i < obs.extra_ord_terms_size(); i++) {
    ret += ", ";
    ret += ExprOrderingTermToString(obs.extra_ord_terms(i));
  }
  ret += " ";
  return ret;
}

CONV_FN(LimitStatement, ls) {
  std::string ret("LIMIT ");
  ret += ExprToString(ls.limit_expr());
  ret += " ";
  if (ls.has_second_expr()) {
    if (ls.offset()) {
      ret += "OFFSET ";
    } else {
      ret += ", ";
    }
    ret += ExprToString(ls.second_expr());
    ret += " ";
  }
  return ret;
}

CONV_FN(ExtraSelectSubStatement, esss) {
  std::string ret, enum1;
  enum1 = CompoundOperator_Name(esss.compound_op());
  // erase prefix
  enum1.erase(0, std::string("CO_").length());
  ret += EnumStrReplaceUnderscores(enum1);
  ret += " ";
  ret += SelectSubStatementToString(esss.select_substatement());
  return ret;
}

CONV_FN(Select, select) {
  RETURN_IF_DISABLED_QUERY(Select);
  std::string ret;
  if (select.has_with()) {
    ret += WithStatementToString(select.with());
    ret += " ";
  }
  ret += SelectStatementCoreToString(select.select_core());
  for (int i = 0; i < select.extra_select_substatements_size(); i++) {
    ret +=
        ExtraSelectSubStatementToString(select.extra_select_substatements(i));
    ret += " ";
  }
  if (select.has_orderby()) {
    ret += OrderByStatementToString(select.orderby());
    ret += " ";
  }
  if (select.has_limit()) {
    ret += LimitStatementToString(select.limit());
  }
  return ret;
}

// ~~~~FTS3~~~~

// CORPUS currently relying on normal SELECTs to generate good compound
// queries for FTS, like AND, OR, and NOT. Generate a corpus entry with a lot of
// creative FTS queries.

CONV_FN(FTS3Table, ft) {
  // std::string ret("FTS3Table");
  std::string ret(
      "Table");  // for now, use the same naming scheme as normal tables.
  ret += std::to_string(ft.table() % kMaxFTS3TableNumber);
  return ret;
}

CONV_FN(FTS3MatchToken, fmt) {
  std::string ret;
  if (fmt.has_col()) {
    ret += ColumnToString(fmt.col());
    ret += ":";
  }
  if (fmt.negate()) {
    ret += "-";
  }
  if (fmt.token().length() == 0)
    ret += "a";
  else
    ret += ConvertToSqlString(
        fmt.token());  // TODO(mpdenton) good enough? Need something better????
  if (fmt.prefix())
    ret += "*";
  return ret;
}

CONV_FN(FTS3PhraseQuery, fpq) {
  std::string ret("\"");
  ret += FTS3MatchTokenToString(fpq.mt());
  for (int i = 0; i < fpq.extra_mts_size(); i++) {
    ret += " ";
    ret += FTS3MatchTokenToString(fpq.extra_mts(i));
  }
  ret += "\"";
  return ret;
}

CONV_FN(FTS3MatchFormatCore, fmfc) {
  // oneof
  if (fmfc.has_pq()) {
    return FTS3PhraseQueryToString(fmfc.pq());
  } else if (fmfc.has_nq()) {
    return FTS3NearQueryToString(fmfc.nq());
  } else {
    return FTS3MatchTokenToString(fmfc.mt_fallback());
  }
}

CONV_FN(FTS3NearQuery, fnq) {
  std::string ret = FTS3MatchFormatCoreToString(fnq.format_core1());
  ret += " NEAR";
  if (fnq.has_num_tokens_near()) {
    ret += "/";
    ret += std::to_string(fnq.num_tokens_near());
  }
  ret += " ";
  ret += FTS3MatchFormatCoreToString(fnq.format_core2());
  return ret;
}

CONV_FN(FTS3CompoundAndCore, fcac) {
  std::string ret(" ");
  ret += FTS3CompoundAndCore_CompoundOp_Name(fcac.op());
  ret += " ";
  ret += FTS3MatchFormatCoreToString(fcac.core());
  return ret;
}

CONV_FN(FTS3MatchCompoundFormat, fmcf) {
  std::string ret = FTS3MatchFormatCoreToString(fmcf.core());
  for (int i = 0; i < fmcf.compound_and_cores_size(); i++) {
    ret += FTS3CompoundAndCoreToString(fmcf.compound_and_cores(i));
  }
  return ret;
}

CONV_FN(FTS3MatchFormat, fmf) {
  std::string ret("\'");
  if (fmf.ft_size() > 0) {
    ret += FTS3MatchCompoundFormatToString(fmf.ft(0));
  }
  for (int i = 1; i < fmf.ft_size(); i++) {
    ret += " ";
    ret += FTS3MatchCompoundFormatToString(fmf.ft(i));
  }
  ret += "\'";
  return ret;
}

CONV_FN(FTS3SpecialCommand, fsc) {
  RETURN_IF_DISABLED_QUERY(FTS3SpecialCommand);
  std::string ret("INSERT INTO ");
  ret += FTS3TableToString(fsc.table());
  ret += "(";
  ret += FTS3TableToString(fsc.table());
  ret += ") VALUES(\'";
  switch (fsc.command()) {
    case FTS3SpecialCommand::OPTIMIZE:
      ret += "optimize";
      break;
    case FTS3SpecialCommand::REBUILD:
      ret += "rebuild";
      break;
    case FTS3SpecialCommand::INTEGRITY_CHECK:
      ret += "integrity-check";
      break;
    case FTS3SpecialCommand::MERGE:
      ret += "merge=";
      ret += std::to_string(fsc.val1());
      ret += ",";
      ret += std::to_string(fsc.val2());
      break;
    case FTS3SpecialCommand::AUTOMERGE:
      ret += "automerge=";
      ret += std::to_string(fsc.val1() % 16);
      break;
  }
  ret += "\')";
  return ret;
}

// WARNING no space at end
CONV_FN(FTS3SelectMatch, fsm) {
  RETURN_IF_DISABLED_QUERY(FTS3SelectMatch);
  std::string ret("SELECT * FROM ");
  ret += FTS3TableToString(fsm.table());
  ret += " WHERE ";
  ret += ColumnToString(fsm.col());
  ret += " MATCH ";
  ret += FTS3MatchFormatToString(fsm.match_pattern());
  return ret;
}

CONV_FN(FTS3SpecificQuery, fsq) {
  RETURN_IF_DISABLED_QUERY(FTS3SpecificQuery);
#if defined(FUZZ_FTS3)
  // oneof
  if (fsq.has_command()) {
    return FTS3SpecialCommandToString(fsq.command());
  } else if (fsq.has_select()) {
    return FTS3SelectMatchToString(fsq.select());
  } else {
    return "";
  }

#else
  return "";
#endif
}

CONV_FN(ICULocale, il) {
  std::string ret;
  std::string lc = IsoLangCode_Name(il.iso_lang_code());
  lc.erase(0, std::string("ISO_LANG_CODE_").length());
  ret += lc;
  ret += "_";
  // extract country code from integer
  ret += (char)((il.country_code() & 0xFF) % 26) + 'A';
  ret += (char)(((il.country_code() & 0xFF00) >> 8) % 26) + 'A';
  return ret;
}

CONV_FN(CreateFTS3Table, cft) {
  RETURN_IF_DISABLED_QUERY(CreateFTS3Table);
  std::string ret("CREATE VIRTUAL TABLE ");
  if (cft.if_not_exists())
    ret += "IF NOT EXISTS ";
  if (cft.has_schema()) {
    ret += SchemaToString(cft.schema());
    ret += ".";
  }
  ret += FTS3TableToString(cft.table());
  ret += " USING fts3(";
  // TODO(mpdenton) not using schema here, should I???
  if (cft.has_col_list()) {
    ret += ColumnListToString(cft.col_list());
    if (cft.has_tokenizer_type())
      ret += ", ";
  }
  if (cft.has_tokenizer_type()) {
    ret += "tokenize=";
    std::string tt = TokenizerType_Name(cft.tokenizer_type());
    tt.erase(0, std::string("TT_").length());
    tt = StrToLower(tt);
#if defined(SQLITE_DISABLE_FTS3_UNICODE)
    if (tt == "unicode61")
      tt = "porter";
#endif
    ret += tt;
    // now generate locales for ICU
    if (cft.tokenizer_type() == TokenizerType::TT_ICU) {
      ret += " ";
      ret += ICULocaleToString(cft.locale());
    } else if (cft.tokenizer_type() == TokenizerType::TT_UNICODE61) {
      // Chrome does not actually enable this option. FIXME in the future.
    }
  }
  ret += ")";
  return ret;
}

// WARNING no space at end
CONV_FN(FTS3OffsetsFn, fof) {
  return "offsets(" + FTS3TableToString(fof.table()) + ")";
}

// WARNING no space at end
CONV_FN(FTS3SnippetsFn, fsf) {
  std::string ret("snippets(");
  ret += FTS3TableToString(fsf.table());
  // Now (possibly) emit the five optional arguments.
  int num_args = (int)fsf.num_optional_args();
  if (num_args >= 1) {
    ret += ", \'";
    ret += ConvertToSqlString(fsf.start_match());
    ret += "\'";
  }
  if (num_args >= 2) {
    ret += ", \'";
    ret += ConvertToSqlString(fsf.end_match());
    ret += "\'";
  }
  if (num_args >= 3) {
    ret += ", \'";
    ret += ConvertToSqlString(fsf.ellipses());
    ret += "\'";
  }
  if (num_args >= 4) {
    ret += ", ";
    if (fsf.has_col_number()) {
      ret += std::to_string(fsf.col_number() % kMaxColumnNumber);
    } else {
      ret += "-1";
    }
  }
  if (num_args >= 5) {
    ret += ", ";
    ret +=
        std::to_string((fsf.num_tokens() % 129) - 64);  // clamp into [-64, 64]
  }
  ret += ")";
  return ret;
}

// WARNING no space at end
CONV_FN(FTS3MatchInfoFn, fmi) {
  constexpr static char matchinfo_chars[] = {
      'p', 'c', 's', 'x', 'y', 'b',
      // 'n', 'a', 'l', // These characters only available for FTS4.
  };
  std::string ret("matchinfo(");
  ret += FTS3TableToString(fmi.table());
  if (fmi.chars_size() > 0) {
    ret += ", \'";
    for (int i = 0; i < fmi.chars_size(); i++) {
      ret += matchinfo_chars[fmi.chars(i) % sizeof(matchinfo_chars)];
    }
    ret += "\'";
  }
  ret += ")";
  return ret;
}

// WARNING no space at end
CONV_FN(FTS3AuxiliaryFn, faf) {
  if (faf.has_snippets()) {
    return FTS3SnippetsFnToString(faf.snippets());
  } else if (faf.has_matchinfo()) {
    return FTS3MatchInfoFnToString(faf.matchinfo());
  } else {
    return FTS3OffsetsFnToString(faf.offsets_fallback());
  }
}

CONV_FN(FTS3HiddenTable, fht) {
  std::string tab = FTS3HiddenTable_HiddenTableVal_Name(fht.htv());
  tab = StrToLower(tab);
  return FTS3TableToString(fht.table()) + "_" + tab;
}

CONV_FN(FTS3HiddenTableColumn, fhtc) {
  std::string tab = FTS3HiddenTableColumn_Name(fhtc);
  tab = tab.erase(0, std::string("FTS3_HT_").length());
  tab = StrToLower(tab);
  return tab;
}

CONV_FN(FTS3HiddenTableInsert, fi) {
  RETURN_IF_DISABLED_QUERY(FTS3HiddenTableInsert);
  std::string ret("INSERT INTO ");
  ret += FTS3HiddenTableToString(fi.fht());
  if (fi.col_vals_size() == 0) {
    ret += " DEFAULT VALUES";
    return ret;
  }
  ret += "(";
  ret += FTS3HiddenTableColumnToString(fi.col_vals(0).col());
  for (int i = 1; i < fi.col_vals_size(); i++) {
    ret += ", ";
    ret += FTS3HiddenTableColumnToString(fi.col_vals(i).col());
  }
  ret += ") VALUES(";
  ret += ExprToString(fi.col_vals(0).expr());
  for (int i = 0; i < fi.col_vals_size(); i++) {
    ret += ", ";
    ret += ExprToString(fi.col_vals(i).expr());
  }
  ret += ")";
  return ret;
}

CONV_FN(FTS3HiddenTableUpdate, fu) {
  RETURN_IF_DISABLED_QUERY(FTS3HiddenTableUpdate);
  std::string ret("UPDATE ");
  ret += FTS3HiddenTableToString(fu.fht());
  ret += " ";
  if (fu.col_vals_size() == 0) {
    ret += "start_block = 0";
    return ret;
  }
  ret += "SET ";
  ret += FTS3HiddenTableColumnToString(fu.col_vals(0).col());
  ret += " = ";
  ret += ExprToString(fu.col_vals(0).expr());
  for (int i = 1; i < fu.col_vals_size(); i++) {
    ret += ", ";
    ret += FTS3HiddenTableColumnToString(fu.col_vals(i).col());
    ret += " = ";
    ret += ExprToString(fu.col_vals(i).expr());
  }
  if (fu.has_col_where()) {
    ret += " WHERE ";
    ret += FTS3HiddenTableColumnToString(fu.col_where());
    ret += BinaryOperatorToString(fu.bin_op());
    ret += ExprToString(fu.comp_expr());
  }
  return ret;
}

CONV_FN(FTS3HiddenTableDelete, fd) {
  RETURN_IF_DISABLED_QUERY(FTS3HiddenTableDelete);
  std::string ret("DELETE FROM ");
  ret += FTS3HiddenTableToString(fd.fht());
  if (fd.has_col_where()) {
    ret += " WHERE ";
    ret += FTS3HiddenTableColumnToString(fd.col_where());
    ret += BinaryOperatorToString(fd.bin_op());
    ret += ExprToString(fd.comp_expr());
  }
  return ret;
}

// ~~~~TRANSACTIONS/SAVEPOINTS
CONV_FN(BeginTransaction, bt) {
  RETURN_IF_DISABLED_QUERY(BeginTransaction);
  std::string ret("BEGIN ");
  if (bt.has_type()) {
    ret += BeginTransaction_TransactionType_Name(bt.type());
    ret += " ";
  }
  ret += "TRANSACTION";
  return ret;
}

CONV_FN(CommitTransaction, ct) {
  RETURN_IF_DISABLED_QUERY(CommitTransaction);
  return EnumStrReplaceUnderscores(
      CommitTransaction_CommitText_Name(ct.text()));
}

CONV_FN(RollbackStatement, rt) {
  RETURN_IF_DISABLED_QUERY(RollbackStatement);
#if !defined(FUZZ_OMIT_SAVEPOINT)
  if (rt.has_save_point()) {
    return "ROLLBACK TO SAVEPOINT " + SavePointToString(rt.save_point());
  }
#endif
  return "ROLLBACK TRANSACTION";
}

#if !defined(FUZZ_OMIT_SAVEPOINT)
CONV_FN(CreateSavePoint, csp) {
  RETURN_IF_DISABLED_QUERY(CreateSavePoint);
  return "SAVEPOINT " + SavePointToString(csp.save_point());
}

CONV_FN(ReleaseSavePoint, rsp) {
  RETURN_IF_DISABLED_QUERY(ReleaseSavePoint);
  return "RELEASE SAVEPOINT " + SavePointToString(rsp.save_point());
}
#endif

CONV_FN(Analyze, a) {
  RETURN_IF_DISABLED_QUERY(Analyze);
  std::string ret("ANALYZE");
  if (a.has_schema_name()) {
    ret += " ";
    ret += SchemaToString(a.schema_name());
    if (a.has_table_name()) {
      ret += ".";
      ret += TableToString(a.table_name());
    } else if (a.has_index_name()) {
      ret += ".";
      ret += IndexToString(a.index_name());
    }
  } else if (a.has_table_name()) {
    ret += " ";
    ret += TableToString(a.table_name());
  } else if (a.has_index_name()) {
    ret += " ";
    ret += IndexToString(a.index_name());
  }

  return ret;
}

// ~~~~VACUUM~~~~
CONV_FN(Vacuum, v) {
  RETURN_IF_DISABLED_QUERY(Vacuum);
  std::string ret("VACUUM");
  if (v.has_schema()) {
    ret += " ";
    ret += SchemaToString(v.schema());
  }
  return ret;
}

// ~~~~PRAGMA~~~~
CONV_FN(Pragma, p) {
  RETURN_IF_DISABLED_QUERY(Pragma);
#if defined(FUZZ_OMIT_PRAGMA)
  return "";
#else
  constexpr static const char* locking_modes[] = {"NORMAL", "EXCLUSIVE"};
  constexpr static const char* journal_modes[] = {
      "DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"};

  Table table;
  std::string ret("PRAGMA ");
  if (p.has_schema()) {
    ret += SchemaToString(p.schema());
    ret += ".";
  }
  ret += StripTrailingUnderscores(
      StrToLower(Pragma_PragmaCommand_Name(p.command())));
  switch (p.command()) {
    case Pragma::AUTO_VACUUM:
      ret += " = ";
      ret += std::to_string((uint32_t)p.arg1() % 3);
      break;
    case Pragma::WRITEABLE_SCHEMA:
      ret += " = ";
      ret += std::to_string((uint32_t)p.arg1() % 2);
      break;
    case Pragma::LOCKING_MODE:
      ret += " = ";
      ret += locking_modes[(uint32_t)p.arg1() % 2];
      break;
    case Pragma::TEMP_STORE:
      ret += " = ";
      ret += std::to_string((uint32_t)p.arg1() % 3);
      break;
    case Pragma::PAGE_SIZE_:
      ret += " = ";
      ret += std::to_string(p.arg1());
      break;
    case Pragma::TABLE_INFO:
      ret += "(\'";
      table.set_table((uint32_t)p.arg1());
      ret += TableToString(table);
      ret += "\')";
      break;
    case Pragma::JOURNAL_MODE:
      ret += " = ";
      ret += journal_modes[(uint32_t)p.arg1() % 6];
      break;
    case Pragma::MMAP_SIZE:
      ret += " = ";
      ret += std::to_string(p.arg1());
      break;
  }
  return ret;
#endif
}

// ~~~~CREATE INDEX~~~~
CONV_FN(CreateIndex, ci) {
  RETURN_IF_DISABLED_QUERY(CreateIndex);
  std::string ret("CREATE ");
  if (ci.unique())
    ret += "UNIQUE ";
  ret += "INDEX ";
  if (ci.if_not_exists())
    ret += "IF NOT EXISTS ";
  if (ci.has_schema()) {
    ret += SchemaToString(ci.schema());
    ret += ".";
  }
  ret += IndexToString(ci.index());
  ret += " ON ";
  ret += TableToString(ci.table());
  ret += "(";
  ret += IndexedColumnListToString(ci.icol_list());
  ret += ")";
  if (ci.has_where()) {
    ret += " ";
    ret += WhereStatementToString(ci.where());
  }
  return ret;
}

// ~~~~CREATE VIEW~~~~
CONV_FN(CreateView, cv) {
  RETURN_IF_DISABLED_QUERY(CreateView);
  std::string ret("CREATE ");
  if (cv.has_temp_modifier()) {
    ret += EnumStrReplaceUnderscores(TempModifier_Name(cv.temp_modifier()))
               .erase(0, std::string("TM_").length());
    ret += " ";
  }
  ret += "VIEW ";
  if (cv.if_not_exists())
    ret += "IF NOT EXISTS ";

  if (cv.has_schema()) {
    ret += SchemaToString(cv.schema());
    ret += ".";
  }
  ret += ViewToString(cv.view());
  ret += " ";
  if (cv.has_col_list()) {
    ret += "(";
    ret += ColumnListToString(cv.col_list());
    ret += ") ";
  }
  ret += SelectToString(cv.select());
  return ret;
}

// ~~~~CREATE TRIGGER~~~~

CONV_FN(TypicalQuery, tq) {
  // oneof
  if (tq.has_update())
    return UpdateToString(tq.update());
  else if (tq.has_insert())
    return InsertToString(tq.insert());
  else if (tq.has_select())
    return SelectToString(tq.select());
  else
    return DeleteToString(tq.delete_fallback());
}

// WARNING no space at end
CONV_FN(CreateTrigger, ct) {
  RETURN_IF_DISABLED_QUERY(CreateTrigger);
  std::string ret("CREATE ");
  if (ct.has_temp_modifier()) {
    ret += EnumStrReplaceUnderscores(TempModifier_Name(ct.temp_modifier()))
               .erase(0, std::string("TM_").length());
    ret += " ";
  }
  ret += "TRIGGER ";
  if (ct.if_not_exists())
    ret += "IF NOT EXISTS ";

  if (ct.has_schema()) {
    ret += SchemaToString(ct.schema());
    ret += " ";
  }

  ret += TriggerToString(ct.trigger());
  ret += " ";
  if (ct.has_trigger_type()) {
    ret += EnumStrReplaceUnderscores(
        CreateTrigger_TriggerType_Name(ct.trigger_type()));
    ret += " ";
  }
  ret += CreateTrigger_TriggerInstr_Name(ct.trigger_instr());
  ret += " ";
  if (ct.trigger_instr() == CreateTrigger::UPDATE) {
    ret += "OF ";
    ret += ColumnListToString(ct.col_list());
    ret += " ";
  }
  ret += "ON ";
  ret += TableToString(ct.table());
  ret += " ";
  if (ct.for_each_row())
    ret += "FOR EACH ROW ";

  if (ct.has_when()) {
    ret += "WHEN ";
    ret += ExprComparisonHighProbabilityToString(ct.when());
    ret += " ";
  }

  ret += "BEGIN ";
  ret += TypicalQueryToString(ct.tq());
  ret += "; ";
  for (int i = 0; i < ct.extra_tqs_size(); i++) {
    ret += TypicalQueryToString(ct.extra_tqs(i));
    ret += "; ";
  }
  ret += "END";
  return ret;
}

// ~~~~REINDEX~~~~
CONV_FN(ReIndex, ri) {
  RETURN_IF_DISABLED_QUERY(ReIndex);
// Chrome doesn't use REINDEX
#if !defined(SQLITE_OMIT_REINDEX)
  if (ri.empty())
    return "REINDEX";
  std::string ret("REINDEX ");
  if (ri.has_collate_type()) {
    ret += CollateTypeToString(ri.collate_type());
    return ret;
  }
  if (ri.has_schema()) {
    ret += SchemaToString(ri.schema());
    ret += ".";
  }
  if (ri.has_table())
    ret += TableToString(ri.table());
  else
    ret += IndexToString(ri.index());

  return ret;
#else
  return "";
#endif
}

CONV_FN(Drop, d) {
  RETURN_IF_DISABLED_QUERY(Drop);
  std::string ret("DROP ");
  std::string if_exists("");
  std::string schema("");
  if (d.if_exists())
    if_exists = "IF EXISTS ";
  if (d.has_schema()) {
    schema = SchemaToString(d.schema());
    schema += " ";
  }
  // oneof
  if (d.has_index()) {
    ret += "INDEX ";
    ret += if_exists;
    ret += schema;
    ret += IndexToString(d.index());
  } else if (d.has_table()) {
    ret += "TABLE ";
    ret += if_exists;
    ret += schema;
    ret += TableToString(d.table());
  } else if (d.has_trigger()) {
    ret += "TRIGGER ";
    ret += if_exists;
    ret += schema;
    ret += TriggerToString(d.trigger());
  } else {
    ret += "VIEW ";
    ret += if_exists;
    ret += schema;
    ret += ViewToString(d.view_fallback());
  }
  return ret;
}

// ~~~~ALTER TABLE~~~~
CONV_FN(AlterTable, at) {
  RETURN_IF_DISABLED_QUERY(AlterTable);
  std::string ret("ALTER TABLE ");
  ret += ExprSchemaTableToString(at.schema_table());
  ret += " ";
  if (at.has_col()) {
    ret += "RENAME ";
    if (at.column())
      ret += "COLUMN ";
    ret += ColumnToString(at.col());
    ret += " TO ";
    ret += ColumnToString(at.col_to());
  } else if (at.has_col_def()) {
    ret += "ADD ";
    if (at.column())
      ret += "COLUMN ";
    ret += ColumnDefToString(at.col_def());
  } else {
    ret += "RENAME TO ";
    ret += TableToString(at.table_fallback());
  }
  return ret;
}

// ~~~~ATTACH DATABASE~~~~
CONV_FN(AttachDatabase, ad) {
  RETURN_IF_DISABLED_QUERY(AttachDatabase);
  std::string ret("ATTACH DATABASE \'");
  if (ad.in_memory()) {
    if (ad.file_uri()) {
      ret += "file:";
      std::string add;
      if (ad.has_db_name()) {
        ret += SchemaToString(ad.db_name());
        ret += "?mode=memory";
        add = "&";
      } else {
        ret += ":memory:";
        add = "?";
      }

      if (ad.shared_cache()) {
        ret += add;
        ret += "cache=shared";
      }
    }
  }
  ret += "\' AS ";
  ret += SchemaToString(ad.schema());
  return ret;
}

// ~~~~DETACH DATABASE~~~~
CONV_FN(DetachDatabase, dd) {
  RETURN_IF_DISABLED_QUERY(DetachDatabase);
  std::string ret("DETACH DATABASE ");
  ret += SchemaToString(dd.schema());
  return ret;
}

// ~~~~Time and date fns~~~~
CONV_FN(HoursStuff, hs) {
  std::string ret;
  if (hs.has_hh()) {
    ret += std::to_string(hs.hh() % 100);
    if (hs.has_mm()) {
      ret += ":";
      ret += std::to_string(hs.mm() % 100);
      if (hs.has_ss()) {
        ret += ":";
        ret += std::to_string(hs.ss() % 100);
        if (hs.has_sss()) {
          ret += ".";
          ret += std::to_string(hs.sss() % 1000);
        }
      }
    }
  }
  return ret;
}

CONV_FN(TimeString, ts) {
  std::string ret;
  if (ts.has_yyyy()) {
    // FIXME in the future add zeroes for integers < 1000.
    ret += std::to_string(ts.yyyy() % 10000);
    ret += "-";
    ret += std::to_string(ts.mm() % 100);
    ret += "-";
    ret += std::to_string(ts.dd() % 100);
    if (ts.extra_t())
      ret += "T";
    if (ts.has_hs())
      ret += HoursStuffToString(ts.hs());
  } else if (ts.has_hs()) {
    ret += HoursStuffToString(ts.hs());
  } else if (ts.has_dddddddddd()) {
    ret += std::to_string(ts.dddddddddd() % 10000000000);
  } else if (ts.now()) {
    ret += "now";
  } else {
    ret += ConvertToSqlString(ts.random_bytes());
  }

  if (ts.has_tz_plus()) {
    if (ts.z()) {
      ret += "Z";
    } else {
      if (ts.plus())
        ret += "+";
      else
        ret += "-";
      ret += std::to_string(ts.tz_hh() % 100);
      ret += std::to_string(ts.tz_mm() % 100);
    }
  }
  return ret;
}

CONV_FN(TimeModifier, tm) {
  std::string ret;
  if (tm.has_nm()) {
    ret += std::to_string(tm.num());
    ret += " ";
    if (tm.has_dot_num()) {
      ret += ".";
      ret += std::to_string(tm.dot_num());
    }
    ret += StrToLower(TimeModifier_NumberedModifiers_Name(tm.nm()));
  } else {
    ret += StrToLower(
        EnumStrReplaceUnderscores(TimeModifier_OtherModifiers_Name(tm.om())));
  }
  if (tm.om() == TimeModifier::WEEKDAY) {
    ret += " ";
    ret += std::to_string(tm.num());
  }
  return ret;
}

CONV_FN(SimpleDateAndTimeFn, sfn) {
  std::string ret;
  ret += StrToLower(SimpleDateAndTimeFn_FnName_Name(sfn.fn_name()));
  ret += "(\'";
  ret += TimeStringToString(sfn.time_string());
  ret += "\'";
  for (int i = 0; i < sfn.modifiers_size(); i++) {
    ret += ", \'";
    ret += TimeModifierToString(sfn.modifiers(i));
    ret += "\'";
  }
  ret += ") ";
  return ret;
}

CONV_FN(StrftimeFormat, sf) {
  std::string ret;
  if (sf.has_subs()) {
    std::string subs = StrftimeFormat_Substitution_Name(sf.subs());
    if (sf.lowercase())
      subs = StrToLower(subs);
    ret += "%" + subs;
  } else {
    ret += "%%";
  }

  ret += ConvertToSqlString(sf.bytes());
  return ret;
}

CONV_FN(StrftimeFn, sfn) {
  std::string ret("strftime(\'");
  for (int i = 0; i < sfn.fmts_size(); i++) {
    ret += StrftimeFormatToString(sfn.fmts(i));
  }
  ret += "\', \'";
  ret += TimeStringToString(sfn.time_string());
  ret += "\'";
  for (int i = 0; i < sfn.modifiers_size(); i++) {
    ret += ", \'";
    ret += TimeModifierToString(sfn.modifiers(i));
    ret += "\'";
  }
  ret += ") ";
  return ret;
}

CONV_FN(DateAndTimeFn, dat) {
  if (dat.has_simple())
    return SimpleDateAndTimeFnToString(dat.simple());
  else
    return StrftimeFnToString(dat.strftime());
}

// ~~~~QUERY~~~~
CONV_FN(SQLQuery, query) {
  using QueryType = SQLQuery::QueryOneofCase;
  switch (query.query_oneof_case()) {
    case QueryType::kSelect:
      return SelectToString(query.select());
    case QueryType::kCreateTable:
      return CreateTableToString(query.create_table());
    case QueryType::kInsert:
      return InsertToString(query.insert());
    case QueryType::kDelete:
      return DeleteToString(query.delete_());
    case QueryType::kFts3Table:
      return CreateFTS3TableToString(query.fts3_table());
    case QueryType::kFtsQuery:
      return FTS3SpecificQueryToString(query.fts_query());
    case QueryType::kBeginTxn:
      return BeginTransactionToString(query.begin_txn());
    case QueryType::kCommitTxn:
      return CommitTransactionToString(query.commit_txn());
    case QueryType::kRollbackStmt:
      return RollbackStatementToString(query.rollback_stmt());
#if !defined(FUZZ_OMIT_SAVEPOINT)
    case QueryType::kCreateSavePoint:
      return CreateSavePointToString(query.create_save_point());
    case QueryType::kReleaseSavePoint:
      return ReleaseSavePointToString(query.release_save_point());
#endif
    case QueryType::kAnalyze:
      return AnalyzeToString(query.analyze());
    case QueryType::kVacuum:
      return VacuumToString(query.vacuum());
    case QueryType::kPragma:
      return PragmaToString(query.pragma());
    case QueryType::kUpdate:
      return UpdateToString(query.update());
    case QueryType::kCreateIndex:
      return CreateIndexToString(query.create_index());
    case QueryType::kCreateView:
      return CreateViewToString(query.create_view());
    case QueryType::kCreateTrigger:
      return CreateTriggerToString(query.create_trigger());
    case QueryType::kReindex:
      return ReIndexToString(query.reindex());
    case QueryType::kDrop:
      return DropToString(query.drop());
    case QueryType::kAlterTable:
      return AlterTableToString(query.alter_table());
    case QueryType::kAttachDb:
      return AttachDatabaseToString(query.attach_db());
    case QueryType::kDetachDb:
      return DetachDatabaseToString(query.detach_db());
#if defined(FUZZ_FTS3)
    case QueryType::kFts3Insert:
      return FTS3HiddenTableInsertToString(query.fts3_insert());
    case QueryType::kFts3Update:
      return FTS3HiddenTableUpdateToString(query.fts3_update());
    case QueryType::kFts3Delete:
      return FTS3HiddenTableDeleteToString(query.fts3_delete());
#endif
    default:
      return "";
  }
}

std::vector<std::string> SQLQueriesToVec(const SQLQueries& sql_queries) {
  std::vector<std::string> queries;
  queries.reserve(sql_queries.extra_queries_size() + 1);
  queries.push_back(CreateTableToString(sql_queries.create_table()) + ";");
  for (int i = 0; i < sql_queries.extra_queries_size(); i++) {
    std::string query = SQLQueryToString(sql_queries.extra_queries(i));
    if (query == "")
      continue;
    query += ";";
    queries.push_back(query);
  }
  return queries;
}

CONV_FN(SQLQueries, sql_queries) {
  std::string queries;

  for (std::string& query : SQLQueriesToVec(sql_queries)) {
    queries += query;
    queries += "\n";
  }

  return queries;
}

void SetDisabledQueries(std::set<std::string> disabled_queries) {
  disabled_queries_ = disabled_queries;
}

}  // namespace sql_fuzzer