Lot of code for generating code. Working on unit tests.

This commit is contained in:
eelke 2018-09-09 18:52:32 +02:00
parent da45929b12
commit 8f4845d4d2
42 changed files with 1089 additions and 267 deletions

View file

@ -0,0 +1,119 @@
#include "CodeBuilder.h"
#include "Pgsql_Result.h"
#include "IndentationConfig.h"
#include "LanguageConfig.h"
#include "StructureTemplate.h"
#include "util.h"
#include <QTextStream>
void FormatToStream(QTextStream &stream, QString format, std::function<void(QTextStream &, QString)> field_callback)
{
// Use static to optimize only once
static QRegularExpression cached_find_var_re("(?:[^\\\\]|^)(\\/%([a-zA-Z0-9_-]+)%\\/)", QRegularExpression::OptimizeOnFirstUsageOption);
int from = 0;
QRegularExpressionMatch match;
while (format.indexOf(cached_find_var_re, from, &match) >= 0) {
if (from > 0) {
// Because the regex has to check for backslash in front we have the from position
// one position before where we actually should continue for the second match and later ie when from > 0
// Therefor increase from by 1 to make the substring (midRef) calculation work
++from;
}
// copy code before the var to the stream
stream << format.midRef(from, match.capturedStart(1) - from);
field_callback(stream, match.captured(2));
from = match.capturedEnd()-1; // -1 because it wants to match one character before or start of line to make sure there is no backslash
}
if (from > 0) {
// same reason as at the start of the loop
++from;
}
stream << format.midRef(from);
}
void CodeBuilder::GenCodeForExecutingQuery(QTextStream &q, const QString &query, const Pgsql::Result &result)
{
// %1 query string
// %2 struct_name
// %3 list of field assignments
QString exec_query_template = "Pgsql::Result result = conn.query(%1);";
QString loop_start_template =
"Pgsql::Result result = conn.query(/%query_literal%/);"
"for (auto row: result) {\n"
" Pgsql::Col col(row);\n"
" /%struct_type%/ v;\n"
"/%assign_fields%/"
"}";
// %field_name%
// %column_name%
// %column_index%
QString assign_result_field_template = "col >> v./%field_name%/";
QString query_string_literal = ConvertToMultiLineCString(query);
// [optional] Gen declaration of result structure
GenReturnStructDefinition(q, result);
// assume we have connection? What name???
// gen code for creating and executing statement
// - bind parameters
// Gen code for iterating through result and filling the result struct/or local variables
}
void CodeBuilder::GenReturnStructDefinition(QTextStream &q, const Pgsql::Result &result) const
{
std::shared_ptr<const StructureTemplate> templ = m_configuration->structureTemplate();
QString struct_name = "TODO";
QString field_format = templ->m_fieldTemplate;
int field_indent_levels = templ->m_fieldIndentation;
QString field_indent_string = m_configuration->indentationConfig()->getIndentString(field_indent_levels);
// Make struct start
q << QString(templ->m_startTemplate).arg(struct_name);
// Process fields
for (int column = 0; column < result.cols(); ++column) {
QString res_col_name = result.getColName(column);
Oid type_oid = result.type(column);
q << field_indent_string;
genFieldDeclaration(q, field_format, res_col_name, type_oid);
q << "\n";
// Unfortunatly there is no easy reliable way to determine if a column can contain null values
// For simple result columns we can have a look at the original column and if that has a NOT NULL constraint
// then we can assume NOT NULL for the result if there are no LEFT/RIGHT JOINS or other constructs that can introduce NULL
// values
// Any way at generation time we might want to be able to specify the null handle
// - exception/error return
// - magic value
// - boost::optional
// - boolean flags
// - null pointer (useful for languages where this has no cost, other cases boolean flags will be more performant)
}
// Finish struct
q << QString(templ->m_endTemplate).arg(struct_name);
}
QString CodeBuilder::columnNameToVariableName(QString column_name) const
{
return m_configuration->columnNameToFieldName(column_name);
}
QString CodeBuilder::getTypeName(Oid dbtype) const
{
return m_configuration->getTypeName(dbtype);
}
void CodeBuilder::genFieldDeclaration(QTextStream &q, const QString &format, const QString &column_name, Oid column_type) const
{
QString field_name = columnNameToVariableName(column_name);
QString type_name = getTypeName(column_type);
q << QString(format).arg(field_name).arg(type_name);
}

View file

@ -0,0 +1,28 @@
#ifndef CODEBUILDER_H
#define CODEBUILDER_H
#include "Pgsql_declare.h"
#include <QString>
class LanguageConfig;
class QTextStream;
class CodeBuilder {
public:
void GenCodeForExecutingQuery(QTextStream &q, const QString &query, const Pgsql::Result &result);
void GenReturnStructDefinition(QTextStream &q, const Pgsql::Result &result) const;
// Generating code for performing query and going through the result
// - Code for executing the query
// - Code for looping the result
// - Code for processing a single row
// - Declaration of struct for holding single row result
QString columnNameToVariableName(QString column_name) const;
QString getTypeName(Oid dbtype) const;
private:
std::shared_ptr<const LanguageConfig> m_configuration;
void genFieldDeclaration(QTextStream &q, const QString &format, const QString &column_name, Oid column_type) const;
};
#endif // CODEBUILDER_H

View file

@ -0,0 +1,50 @@
#include "DefaultConfigs.h"
#include "LanguageConfig.h"
#include "TypeMappings.h"
#include "Pgsql_oids.h"
using namespace Pgsql;
TypeMappings GetDefaultCppTypeMappings()
{
TypeMappings result = {
{ bool_oid, "bool" },
{ char_oid, "char" },
{ name_oid, "std::string" },
{ int8_oid, "int64_t" },
{ int2_oid, "int16_t" },
{ int4_oid, "int32_t" },
{ text_oid, "std::string" },
{ oid_oid, "Oid" },
{ float4_oid, "float" },
{ float8_oid, "double" }
};
return result;
}
std::shared_ptr<LanguageConfig> buildDefaultCppLanguageConfig()
{
return std::make_shared<LanguageConfig>();
}
std::shared_ptr<const LanguageConfig> getDefaultCppLanguageConfig()
{
static auto config = buildDefaultCppLanguageConfig();
return config;
}
std::shared_ptr<const LanguageConfig> buildPgLabCppLanguageConfig()
{
auto cfg = buildDefaultCppLanguageConfig();
return cfg;
}
std::shared_ptr<const LanguageConfig> getPgLabCppLanguageConfig()
{
static auto config = buildPgLabCppLanguageConfig();
return config;
}

View file

@ -0,0 +1,11 @@
#ifndef DEFAULTCONFIGS_H
#define DEFAULTCONFIGS_H
#include <memory>
class LanguageConfig;
std::shared_ptr<const LanguageConfig> getDefaultCppLanguageConfig();
std::shared_ptr<const LanguageConfig> getPgLabCppLanguageConfig();
#endif // DEFAULTCONFIGS_H

View file

@ -0,0 +1,27 @@
#include "IndentationConfig.h"
IndentationConfig::IndentationConfig() = default;
IndentationConfig::IndentationConfig(int tab_size, int indentation_size, bool use_tabs)
: m_tabSize(tab_size)
, m_indentationSize(indentation_size)
, m_useTabs(use_tabs)
{}
/** Returns a string with the right amount of tabs and spaces for the
* requested indentation level.
*/
QString IndentationConfig::getIndentString(int level) const
{
int spaces = level * m_indentationSize;
int tabs = 0;
if (m_useTabs) {
tabs = spaces / m_tabSize;
spaces -= tabs * m_tabSize;
}
if (tabs > 0)
return QString(tabs, '\t') + QString(spaces, ' ');
else
return QString(spaces, ' ');
}

View file

@ -0,0 +1,23 @@
#ifndef INDENTATIONCONFIG_H
#define INDENTATIONCONFIG_H
#include <QString>
class IndentationConfig {
public:
IndentationConfig();
IndentationConfig(int tab_size, int indentation_size, bool use_tabs);
/** Returns a string with the right amount of tabs and spaces for the
* requested indentation level.
*/
QString getIndentString(int level) const;
private:
int m_tabSize = 8; ///< the size of a tab
int m_indentationSize = 4; ///< Number of positions per level to indent
bool m_useTabs = true; ///< Use tabs as much as possible instead of spaces when indenting
};
#endif // INDENTATIONCONFIG_H

View file

@ -0,0 +1,15 @@
#include "LanguageConfig.h"
#include "NameManglingRules.h"
#include "TypeMappings.h"
LanguageConfig::LanguageConfig() = default;
QString LanguageConfig::columnNameToFieldName(const QString& column_name) const
{
return m_varNaming->transform(column_name);
}
QString LanguageConfig::getTypeName(Oid dbtype) const
{
return m_typeMappings->getTypeForOid(dbtype);
}

View file

@ -0,0 +1,54 @@
#ifndef LANGUAGECONFIG_H
#define LANGUAGECONFIG_H
#include <QString>
#include "Pgsql_oids.h"
class NameManglingRules;
class TypeMappings;
class StructureTemplate;
class IndentationConfig;
/**
*
*/
class LanguageConfig {
public:
LanguageConfig();
QString columnNameToFieldName(const QString& column_name) const;
QString getTypeName(Oid dbtype) const;
std::shared_ptr<const TypeMappings> typeMappings() const { return m_typeMappings; }
void setTypeMappings(std::shared_ptr<const TypeMappings> type_mappings)
{
m_typeMappings = type_mappings;
}
std::shared_ptr<const StructureTemplate> structureTemplate() const
{
return m_structureTemplate;
}
std::shared_ptr<const IndentationConfig> indentationConfig() const
{
return m_indentationConfig;
}
private:
/** Default template for declaring a variable of the correct type.
* exmaple: "{$type} {$varname};"
*/
//QString varDeclTemplate;
std::shared_ptr<const NameManglingRules> m_varNaming;
std::shared_ptr<const TypeMappings> m_typeMappings;
std::shared_ptr<const StructureTemplate> m_structureTemplate;
std::shared_ptr<const IndentationConfig> m_indentationConfig;
enum class VariableStrategy {
UseLocalVariables,
DeclareClass
};
};
#endif // LANGUAGECONFIG_H

View file

@ -0,0 +1,32 @@
#include "NameManglingRules.h"
void NameManglingRules::apply(const ReplaceRule &rule, QString &in) const
{
int from = 0;
int pos;
QRegularExpressionMatch match;
while ((pos = in.indexOf(rule.pattern, from, &match)) >= 0) {
int len = match.capturedLength();
in.replace(pos, len, rule.replace);
from = pos + rule.replace.size();
if (rule.nextToUpper)
in[from] = in[from].toUpper();
}
}
QString NameManglingRules::transform(const QString &input) const
{
QString result;
if (caseConversion == CaseConversion::Lower)
result = input.toLower();
else if (caseConversion == CaseConversion::Upper)
result = input.toUpper();
else
result = input;
for (auto rule : replaceRules) {
apply(rule, result);
}
return result;
}

View file

@ -0,0 +1,50 @@
#ifndef NAMEMANGLINGRULES_H
#define NAMEMANGLINGRULES_H
#include <QString>
#include <QRegularExpression>
#include <vector>
/** Defines how a database result fieldname should be converted into a variable
* name in the target language.
*
*/
class NameManglingRules {
public:
enum class CollisionHandling {
Restrict, ///< An error will be reported and no code generated
Fqn, ///< Turn into fully qualified name (table_column)
Number ///< A number will be appended to fields that have the same name
};
enum class CaseConversion {
AsIs,
Upper,
Lower
};
class ReplaceRule {
public:
QRegularExpression pattern;
QString replace;
bool nextToUpper = false;
};
using ReplaceRules = std::vector<ReplaceRule>;
ReplaceRules replaceRules;
// { {"[ -_]", QRegularExpression::OptimizeOnFirstUsageOption }, "", true }
QString replaceSpaceWith; ///< default is empty string which means remove spaces
//CollisionHandling CollisionHandling = CollisionHandling::Restrict;
CaseConversion caseConversion = CaseConversion::AsIs; ///< overall case conversion rule
//CaseConversion caseFirstChar = CaseConversion::AsIs; ///< case of the first char
bool camelCase = false; ///< removes underscores and make first char after underscore uppercase
void apply(const ReplaceRule &rule, QString &in) const;
QString transform(const QString &input) const;
};
#endif // NAMEMANGLINGRULES_H

View file

@ -0,0 +1,2 @@
#include "StructureTemplate.h"

View file

@ -0,0 +1,16 @@
#ifndef STRUCTURETEMPLATE_H
#define STRUCTURETEMPLATE_H
#include <QString>
class StructureTemplate {
public:
QString m_startTemplate; // class /$structname/ {\npublic:\n
QString m_endTemplate; // };
QString m_fieldTemplate; // /$typename/ /$varname/;
QString m_fieldSeparator; // use when you need something between fields but not after the last field
int m_fieldIndentation = 1;
};
#endif // STRUCTURETEMPLATE_H

View file

@ -0,0 +1,133 @@
#include "TypeMappings.h"
#include "PgTypeContainer.h"
//namespace {
// using Fallbacks = std::unordered_map<Oid, Oid>;
// Fallbacks fallbacks = {
// { oid_text, oid_varchar },
// { }
// };
//}
TypeMappings::TypeMappings() = default;
TypeMappings::TypeMappings(std::initializer_list<Mapping> mappings)
{
m_typeMap.insert(mappings.begin(), mappings.end());
}
QString TypeMappings::getTypeForOid(Oid oid) const
{
// TODO use the catalog to determine if someting is an array or vector type.
// If it is lookup its element type and use the std container
auto res = m_typeMap.find(oid);
if (res != m_typeMap.end()) {
return res->second;
}
if (m_types) {
PgType type = m_types->getByKey(oid);
if (type.oid != InvalidOid && type.elem != InvalidOid) {
res = m_typeMap.find(type.elem);
QString type_string;
if (res == m_typeMap.end()) {
type_string = m_defaultStringType;
}
else {
type_string = res->second;
}
return QString(m_defaultContainerType).arg(type_string);
}
}
return m_defaultStringType;
}
void TypeMappings::setTypes(std::shared_ptr<const PgTypeContainer> types)
{
m_types = types;
}
void TypeMappings::setDefaultStringType(QString str)
{
m_defaultStringType = str;
}
void TypeMappings::setDefaultContainerType(QString str)
{
m_defaultContainerType = str;
}
void TypeMappings::set(Oid oid, QString type)
{
m_typeMap.insert_or_assign(oid, type);
}
//constexpr Oid bool_oid = 16;
//constexpr Oid bytea_oid = 17;
//constexpr Oid char_oid = 18;
//constexpr Oid name_oid = 19;
//constexpr Oid int8_oid = 20;
//constexpr Oid int2_oid = 21;
//constexpr Oid int4_oid = 23;
//constexpr Oid regproc_oid = 24;
//constexpr Oid text_oid = 25;
//constexpr Oid oid_oid = 26;
//constexpr Oid tid_oid = 27;
//constexpr Oid xid_oid = 28;
//constexpr Oid cid_oid = 29;
//constexpr Oid json_oid = 114;
//constexpr Oid xml_oid = 142;
//constexpr Oid point_oid = 600;
//constexpr Oid lseg_oid = 601;
//constexpr Oid path_oid = 602;
//constexpr Oid box_oid = 603;
//constexpr Oid polygon_oid = 604;
//constexpr Oid line_oid = 628;
//constexpr Oid cidr_oid = 650;
//constexpr Oid float4_oid = 700;
//constexpr Oid float8_oid = 701;
//constexpr Oid abstime_oid = 702;
//constexpr Oid reltime_oid = 703;
//constexpr Oid tinterval_oid = 704;
//constexpr Oid circle_oid = 718;
//constexpr Oid money_oid = 790;
//constexpr Oid macaddr_oid = 829;
//constexpr Oid inet_oid = 869;
//constexpr Oid aclitem_oid = 1033;
//constexpr Oid bpchar_oid = 1042;
//constexpr Oid varchar_oid = 1043;
//constexpr Oid date_oid = 1082;
//constexpr Oid time_oid = 1083;
//constexpr Oid timestamp_oid = 1114;
//constexpr Oid timestamptz_oid = 1184;
//constexpr Oid interval_oid = 1186;
//constexpr Oid timetz_oid = 1266;
//constexpr Oid bit_oid = 1560;
//constexpr Oid varbit_oid = 1562;
//constexpr Oid numeric_oid = 1700;
//constexpr Oid refcursor_oid = 1790;
//constexpr Oid regprocedure_oid = 2202;
//constexpr Oid regoper_oid = 2203;
//constexpr Oid regoperator_oid = 2204;
//constexpr Oid regclass_oid = 2205;
//constexpr Oid regtype_oid = 2206;
//constexpr Oid uuid_oid = 2950;
//constexpr Oid txid_snapshot_oid = 2970;
//constexpr Oid pg_lsn_oid = 3220;
//constexpr Oid tsvector_oid = 3614;
//constexpr Oid tsquery_oid = 3615;
//constexpr Oid gtsvector_oid = 3642;
//constexpr Oid regconfig_oid = 3734;
//constexpr Oid regdictionary_oid = 3769;
//constexpr Oid jsonb_oid = 3802;
//constexpr Oid int4range_oid = 3904;
//constexpr Oid numrange_oid = 3906;
//constexpr Oid tsrange_oid = 3908;
//constexpr Oid tstzrange_oid = 3910;
//constexpr Oid daterange_oid = 3912;
//constexpr Oid int8range_oid = 3926;
//constexpr Oid regnamespace_oid = 4089;
//constexpr Oid regrole_oid = 4096;

View file

@ -0,0 +1,46 @@
#ifndef TYPEMAPPINGS_H
#define TYPEMAPPINGS_H
#include <Pgsql_declare.h>
#include <QString>
#include <initializer_list>
#include <unordered_map>
class PgTypeContainer;
class TypeMappings {
public:
using TypeMap = std::unordered_map<Oid, QString>;
using Mapping = std::pair<Oid, QString>;
TypeMappings();
TypeMappings(std::initializer_list<Mapping> mappings);
QString getTypeForOid(Oid oid) const;
const TypeMap& typeMap() const { return m_typeMap; }
QString defaultStringType() const { return m_defaultStringType; }
void setTypes(std::shared_ptr<const PgTypeContainer> types);
void setDefaultStringType(QString str);
void setDefaultContainerType(QString str);
void set(Oid oid, QString type);
/** Removing a type from the mapping will reeastablish its default mapping
* which in most cases is the default string type for the language.
*/
void remove(Oid oid);
private:
TypeMap m_typeMap;
QString m_defaultStringType;
QString m_defaultContainerType; ///< This string should contain a format variable where the element type should go
std::shared_ptr<const PgTypeContainer> m_types;
};
#endif // TYPEMAPPINGS_H