#include <chilon/db/sql_parser.hpp>
#include <chilon/db/column.hpp>
#include <chilon/iterator_range.hpp>
#include <chilon/conf/cmd/command_line.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <unordered_set>
#include <unordered_map>
namespace chilon { namespace bin {
bool verbose;
bool noTags = false;
char const *namespaceOutput; // this is a helper set depending on the value of useNamespace
std::string guard;
std::string fileNamespace;
std::string definitionNamespace = "definition";
std::string tagNamespace;
using namespace db;
struct table {
struct field {
field(std::string const& type, std::string const& name, bool const nullable)
: type_(type), name_(name), nullable_(nullable) {}
std::string type_;
std::string name_;
bool nullable_;
};
table(std::string const& name) : name_(name) {}
std::string name_;
typedef std::vector<field> fields_t;
fields_t fields_;
};
std::string class_string(std::string const& namespace_, std::string const& class_) {
if (namespace_.empty()) return class_;
else return namespace_ + "::" + class_;
}
std::string class_string(char const * const namespace_, std::string const& class_) {
if (*namespace_ == 0) return class_;
else return std::string(namespace_) + "::" + class_;
}
template <class Ostream>
Ostream& operator<<(Ostream& os, table::field const& field) {
std::string fieldType = field.type_ != "time" ? field.type_ : class_string(namespaceOutput, "time");
if (field.nullable_) os << class_string(namespaceOutput, "nullable<") << fieldType << ">";
else os << fieldType;
return os;
}
struct create_table_handler {
private:
struct whitespace : std::unary_function<char const, bool> {
bool operator()(char const ch) const { return ch == ' ' || ch == '\n'; }
};
template <char c>
struct notCharOrWhitespace : whitespace {
bool operator()(char const ch) const { return ! whitespace::operator()(ch) && ch != c; }
};
template <char c>
struct matchChar : whitespace {
bool operator()(char const ch) const { return ch == c; }
};
template <char c, char c2>
struct matchChar2 : whitespace {
bool operator()(char const ch) const { return ch == c || ch == c2; }
};
struct unexpected_whitespace {};
template <class Predicate>
void skip(Predicate p, char *&str) {
if (*str == '\0') throw unexpected_whitespace();
while (p(*(str))) {
++str;
if (*str == '\0') throw unexpected_whitespace();
}
}
public:
bool execute(char *sqlStatement) {
try {
static char const createStr[] = "create ";
if (! std::equal(createStr, createStr + sizeof(createStr) - 1, sqlStatement)) return true;
sqlStatement += sizeof(createStr) - 1;
skip(whitespace(), sqlStatement);
static char const tableStr[] = "table ";
if (! std::equal(tableStr, tableStr + sizeof(tableStr) - 1, sqlStatement)) return true;
sqlStatement += sizeof(tableStr) - 1;
skip(whitespace(), sqlStatement);
char *tableNameBegin = sqlStatement;
skip(notCharOrWhitespace<'('>(), sqlStatement);
tables_.push_back(table(std::string(tableNameBegin, sqlStatement)));
skip(whitespace(), sqlStatement);
if (*sqlStatement != '(') {
error_ = "was expecting ( after create table statement";
return false;
}
++sqlStatement;
if (verbose) std::cerr << "create table (" << tables_.back().name_ << ")\n";
while (*sqlStatement != '\0' && *sqlStatement != ')') {
skip(whitespace(), sqlStatement);
if (*sqlStatement == '-' && *(sqlStatement+1) == '-') {
for (sqlStatement += 2; *sqlStatement != '\0' && *sqlStatement != '\n'; ++sqlStatement) {}
skip(whitespace(), sqlStatement);
}
char *begin = sqlStatement;
skip(std::not1(whitespace()), sqlStatement);
std::string fieldName(begin, sqlStatement);
skip(whitespace(), sqlStatement);
begin = sqlStatement;
skip(notCharOrWhitespace<','>(), sqlStatement);
std::string fieldType(begin, sqlStatement);
skip(whitespace(), sqlStatement);
bool nullable = true;
if (*sqlStatement != ',' && *sqlStatement != ')') {
// test for things implying not null
begin = sqlStatement;
skip(notCharOrWhitespace<','>(), sqlStatement);
if (std::equal(begin, sqlStatement, "auto_increment")) {
skip(whitespace(), sqlStatement);
begin = sqlStatement;
skip(notCharOrWhitespace<','>(), sqlStatement);
}
if (std::equal(begin, sqlStatement, "primary")) {
skip(whitespace(), sqlStatement);
begin = sqlStatement;
skip(notCharOrWhitespace<','>(), sqlStatement);
nullable = ! std::equal(begin, sqlStatement, "key");
}
else if (std::equal(begin, sqlStatement, "not")) {
skip(whitespace(), sqlStatement);
begin = sqlStatement;
skip(notCharOrWhitespace<','>(), sqlStatement);
nullable = ! std::equal(begin, sqlStatement, "null");
}
}
if (verbose) {
if (nullable)
std::cerr << "\t" << fieldName << " : " << fieldType << std::endl;
else
std::cerr << "\t" << fieldName << " : " << fieldType << " - not null\n";
}
std::string::iterator typeEndIt = std::find(fieldType.begin(), fieldType.end(), '(');
if (typeEndIt != fieldType.end()) fieldType.assign(fieldType.begin(), typeEndIt);
type_map_t::const_iterator fieldIt = type_map_.find(fieldType);
if (fieldIt == type_map_.end()) {
error_ = "unknown type";
return false;
}
else {
tables_.back().fields_.push_back(table::field(fieldIt->second, fieldName, nullable));
if (! noTags) column_names_.insert(tables_.back().fields_.back().name_);
}
skip(std::not1(matchChar2<',', ')'>()), sqlStatement);
if (*sqlStatement == ',') ++sqlStatement;
}
return true;
}
catch (unexpected_whitespace& ) {
error_ = "unexpected whitespace\n";
return false;
}
}
void reconnect(char *db) {
// this creates a new database
}
char const *error() const {
return error_;
}
typedef std::unordered_map<std::string, std::string> type_map_t;
create_table_handler() : error_("unknown error") {
type_map_["varchar"] = "char *";
type_map_["text"] = "char *";
type_map_["char"] = "char *";
type_map_["blob"] = "char *";
type_map_["binary"] = "char *";
type_map_["varbinary"] = "char *";
type_map_["integer"] = "int";
type_map_["boolean"] = "bool";
type_map_["smallint"] = "short int";
type_map_["bigint"] = "long long int";
type_map_["float"] = "float";
type_map_["double"] = "double";
type_map_["date"] = "time";
type_map_["time"] = "time";
type_map_["datetime"] = "time";
type_map_["timestamp"] = "time";
}
typedef std::vector<table> tables_t;
typedef std::unordered_set<std::string> column_names_t;
tables_t const& tables() { return tables_; }
column_names_t const& column_names() { return column_names_; }
private:
tables_t tables_;
column_names_t column_names_;
char const *error_;
type_map_t type_map_;
};
namespace command {
// FUTURE: when there are more commands this will no longer be true
bool generateTableHeader = true;
}
int main(int argc, char *argv[]) {
int nPositionals;
{
bool useNamespace;
namespace cmd_line = chilon::conf::cmd;
using cmd_line::options_description;
options_description options;
options.add_options()
.help("chilon_sql_to_source\nusage: [arguments]")
("d,definition-namespace", definitionNamespace, "namespace to put table defintion helper classes in")
("g,guard", guard, "use c-preprocessor guard with this name")
("n,namespace", fileNamespace, "parent namespace(s) of table definition (use / as a separator for multiple namespaces)")
("o", noTags, "do not generate tags")
("t,tag-namespace", tagNamespace, "namespace to use for column tags")
("N,use-namespace", useNamespace, "include chilon::db namespace in generated header")
("T,generate-table-header", command::generateTableHeader, "generate c++ header files representing tables in sql files")
("v,verbose", verbose, "increase verbosity")
;
try {
nPositionals = cmd_line::parser(argc, argv, options)(std::cerr).n_positionals();
}
catch (cmd_line::invalid_arguments& ) {
return 1;
}
if (useNamespace) namespaceOutput = "";
else namespaceOutput = "chilon::db";
}
if (! nPositionals) {
std::cerr << "please supply files to process, see --help\n";
return 1;
}
create_table_handler h;
db::sql_parser<create_table_handler> parse(h);
for (int i = 1; i <= nPositionals; ++i) {
if (verbose) std::cerr << "processing sql file " << argv[i] << std::endl;
if (! parse(argv[i])) {
std::cerr << "error parsing file " << argv[i] << std::endl;
return 1;
}
}
if (command::generateTableHeader) {
if (! guard.empty()) {
std::cout << "#ifndef " << guard << std::endl;
std::cout << "#define " << guard << std::endl << std::endl;
}
std::cout << "#include <chilon/db/table.hpp>\n\n";
int namespaceLevels = 0;
if (! fileNamespace.empty()) {
std::string::const_iterator it = fileNamespace.begin();
std::string::const_iterator end = fileNamespace.end();
std::string::const_iterator endNamespace = std::find(it, end, '/');
std::cout << "namespace " << string_range(it, endNamespace) << " {";
++namespaceLevels;
while (endNamespace != fileNamespace.end()) {
it = endNamespace + 1;
endNamespace = std::find(it, end, '/');
++namespaceLevels;
std::cout << " namespace " << string_range(it, endNamespace) << " {";
}
std::cout << std::endl << std::endl;
}
char const *tab = " ";
if (namespaceOutput) {
std::cout << "namespace " << definitionNamespace << " {\n" << tab
<< "using chilon::db::table;\n" << tab
<< "using chilon::db::nullable;\n";
if (! noTags) std::cout << tab << "using chilon::db::column;\n";
std::cout << "}\n";
}
if (! noTags) {
std::cout << "\n";
char const *tagTab;
if (! tagNamespace.empty()) {
std::cout << "namespace " << tagNamespace << " {\n";
tagTab = " ";
}
else tagTab = "";
for (create_table_handler::column_names_t::const_iterator it = h.column_names().begin();
it != h.column_names().end(); ++it)
{
std::cout << tagTab << "struct " << *it << " {};\n";
}
if (! tagNamespace.empty()) std::cout << "}\n";
}
typedef create_table_handler::tables_t tables_t;
for (tables_t::const_iterator it = h.tables().begin(); it != h.tables().end(); ++it) {
if (it->fields_.empty()) continue;
std::cout << "\nnamespace " << definitionNamespace << " {\n" << tab
<< "typedef " << class_string(namespaceOutput, "table<");
table::fields_t::const_iterator fieldsEnd = it->fields_.end();
table::fields_t::const_iterator fieldsIt = it->fields_.begin();
if (noTags) {
std::cout << fieldsIt->type_;
for (++fieldsIt; fieldsIt != fieldsEnd; ++fieldsIt) {
std::cout << ", " << *fieldsIt;
}
}
else {
std::cout << "\n" << tab << tab << class_string(namespaceOutput, "column<")
<< class_string(tagNamespace, fieldsIt->name_) << ", "
<< fieldsIt->type_ << ">";
for (++fieldsIt; fieldsIt != fieldsEnd; ++fieldsIt) {
std::cout << ",\n" << tab << tab << class_string(namespaceOutput, "column<")
<< class_string(tagNamespace, fieldsIt->name_) << ", "
<< fieldsIt->type_ << ">";
}
}
std::cout << "> " << it->name_ << ";\n}\n";
std::cout << "struct " << it->name_ << " : " << definitionNamespace
<< "::" << it->name_ << " {\n";
std::cout << tab << it->name_ << "() : " << definitionNamespace << "::" << it->name_ << "(\"";
std::cout << it->name_ << '"';
for (fieldsIt = it->fields_.begin(); fieldsIt != fieldsEnd; ++fieldsIt) {
std::cout << ", \"" << fieldsIt->name_ << '"';
}
std::cout << ") {}\n" << tab << "enum { ";
fieldsIt = it->fields_.begin();
std::cout << fieldsIt->name_;
for (++fieldsIt; fieldsIt != fieldsEnd; ++fieldsIt) {
std::cout << ", " << fieldsIt->name_;
}
std::cout << " };\n};\n";
}
if (namespaceLevels) {
std::cout << "\n}";
for (int i = 1; i < namespaceLevels; ++i) {
std::cout << " }";
}
}
if (! guard.empty()) std::cout << "\n#endif\n" << std::flush;
}
return 0;
}
} }
int main(int argc, char *argv[]) {
return chilon::bin::main(argc, argv);
}