A C++ DAL / ORM code generation framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

477 lines
12 KiB

/*
WORM - a DAL/ORM code generation framework
Copyright (C) 2011 Erik Winn <sidewalksoftware@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WSQLDATATYPE_H
#define WSQLDATATYPE_H
#include <string>
#include <iostream>
#include <algorithm>
namespace WSql
{
/*! \namespace WSql::WSqlDataType
* \brief WSqlDataType namespace - Utilities and definitions of supported data types
*
* This is a container for type flags and convenience functions for the supported
* SQL datatypes. In this namespace are definitions for datatypes, facilities for
* translating these to and from other naming conventions as well as facilities for
* generating transformations of datatypes into various strings used in the ORM
* generating classes.
*
* This provides a central utility namespace for transformations and definitions
* related to data types as defined in DBMSs and C++, including ORM mapping
* of table names to class names, column names and types to variable names and
* types, etc.
*
* Below is a list the ANSI SQl standard types supported by WSQL - these will be
* mapped to native C++ data types in ORM class generation. For example a
* TINYINT column will declared as a member of type "short", a VARCHAR or
* TEXT as type std::string, a DECIMAL to a double, etc. Implementers of drivers
* can use this as a guide for translating types for a particular DBMS.
*
* Writers of drivers must translate any proprietary or other data types specific to the
* DBMS of the driver to these types. Most DBMS metadata is returned in a string identifier
* of one of these types and can usually be mapped conveniently by using the functions
* toString(type) or toType(string).
*
* See the following for more information on the specific data types:
* \include datatypes.txt
*
* \ingroup WSql
*
*/
namespace WSqlDataType
{
/*!\enum Type - flags representing data types
* The types currently supported - adjust this if/when new strings
* are added.
*/
enum Type
{
NOTYPE = 0,
TINYINT,
SMALLINT,
MEDIUMINT,
INT,
BIGINT,
FLOAT,
DOUBLE,
DECIMAL,
DATE,
DATETIME,
YEAR,
TIME,
TIMESTAMP,
TIMESTAMPTZ,
CHAR,
VARCHAR,
NCHAR,
NVARCHAR,
TEXT,
TINYTEXT,
MEDIUMTEXT,
LONGTEXT,
ENUM,
SET,
BLOB
};
/*! \a TypeNames - array of strings representing data types
* The types currently supported in handy string format - adjust this if/when new strings
* are added.
*/
static const char *const TypeNames[] =
{
"NOTYPE",
"TINYINT",
"SMALLINT",
"MEDIUMINT",
"INT",
"BIGINT",
"FLOAT",
"DOUBLE",
"DECIMAL",
"DATE",
"DATETIME",
"TIME",
"YEAR",
"TIMESTAMP",
"TIMESTAMPTZ",
"CHAR",
"VARCHAR",
"NCHAR",
"NVARCHAR",
"TEXT",
"TINYTEXT",
"MEDIUMTEXT",
"LONGTEXT",
"ENUM",
"SET",
"BLOB"
};
//! Number of types supported. \note Change this if you add types!!
static const unsigned short number_of_datatypes = 26;
/*! \brief Returns true if the datatype of the column is supported by the ORM generator
*/
static bool typeIsSupported(Type _type)
{
switch ( _type )
{
case WSqlDataType::NCHAR:
case WSqlDataType::CHAR:
case WSqlDataType::TEXT:
case WSqlDataType::TINYTEXT:
case WSqlDataType::LONGTEXT:
case WSqlDataType::MEDIUMTEXT:
case WSqlDataType::VARCHAR:
case WSqlDataType::NVARCHAR:
case WSqlDataType::DATE:
case WSqlDataType::DATETIME:
case WSqlDataType::YEAR:
case WSqlDataType::TIME:
case WSqlDataType::TIMESTAMP:
case WSqlDataType::TIMESTAMPTZ:
case WSqlDataType::TINYINT:
case WSqlDataType::SMALLINT:
case WSqlDataType::MEDIUMINT:
case WSqlDataType::INT:
case WSqlDataType::BIGINT:
case WSqlDataType::FLOAT:
case WSqlDataType::DECIMAL:
case WSqlDataType::DOUBLE:
return true;
default:
return false;
}
}
/*! Transform a string to all upper case
*/
static void toUpper ( std::string &s )
{
std::transform ( s.begin(), s.end(), s.begin(), ::toupper );
}
/*! Transform a string to all lower case
*/
static void toLower ( std::string &s )
{
std::transform ( s.begin(), s.end(), s.begin(), ::tolower );
}
/*! Case insensitive find string in string
*/
static size_t iFind ( std::string haystack, std::string needle, size_t pos = 0 )
{
toUpper(haystack);
toUpper(needle);
return haystack.find ( needle, pos );
}
/*! Trim whitespace from beginning of string ..
*/
static void lTrim ( std::string &s )
{
size_t pos = 0;
size_t len = s.length();
while(pos < len && isspace( s[pos] ) )
pos++;
if(pos && pos < len)
s.erase(0, pos);
}
/*! Trim whitespace from end of string ..
*/
static void rTrim ( std::string &s )
{
size_t pos = s.length() - 1;
if(!pos)//ingnore empty strings
return;
while( isspace( s[pos] ) )
pos--;
if(pos != s.length() -1)
s.erase(pos + 1);
}
/*! Trim whitespace from string ..
*/
static void trim ( std::string &s )
{
lTrim(s);
rTrim(s);
}
/*! Trim chars in findme from beginning of string s..
*/
static void lTrim ( std::string &s, std::string removeme )
{
if(removeme.empty())
return lTrim(s);
size_t pos = 0;
size_t len = s.length();
size_t removeme_len = removeme.length();
for(int i = 0; i < removeme_len; i++)
for(pos = 0; pos < len; pos++)
{
if( s[pos] != removeme[i] )
{
if(pos)
{
s.erase(0, pos);
len = s.length();
}
break;
}
}
}
/*! Trim chars in findme from end of string s..
*/
static void rTrim ( std::string &s, std::string removeme )
{
if(removeme.empty())
return rTrim(s);
size_t pos = 0;
size_t len = s.length();
size_t removeme_len = removeme.length();
for(int i = 0; i < removeme_len; i++)
for(pos = len; pos; )
{
if( s[--pos] != removeme[i] )
{
if(pos != len - 1 )
{
s.erase(pos + 1);
len = s.length();
}
break;
}
}
}
/*! Trim chars in removeme from string ..
*/
static void trim ( std::string &s, std::string removeme )
{
lTrim(s, removeme);
rTrim(s, removeme);
}
//! Covenience function - returns a string for the type
static std::string toString ( Type type )
{
std::string strToReturn;
if ( type < 0 || type > ( number_of_datatypes - 1 ) )
strToReturn = "INVALID";
else
strToReturn = TypeNames[type]; //careful .. dont mess this up, add types and name in order.
return strToReturn;
}
//! Convenience function - translates a string to a type flag
static Type toType ( std::string name )
{
WSqlDataType::toUpper ( name );
WSqlDataType::trim ( name );
//!\todo intelligence - support more type names, translate to ours ..
int i = 0;
for ( ; i < number_of_datatypes; ++i )
if ( name.compare ( TypeNames[i] ) == 0 )
return static_cast<Type> ( i );
return static_cast<Type> ( 0 ); //NOTYPE
}
/*! \brief Returns a C++ type declaration
* This method returns a string suitable for a type declaration of a variable in C++ code.
*/
static std::string typeDeclaration(Type _type)
{
std::string strToReturn;
switch ( _type )
{
case WSqlDataType::NCHAR:
case WSqlDataType::CHAR:
strToReturn = "char";
break;
case WSqlDataType::TEXT:
case WSqlDataType::TINYTEXT:
case WSqlDataType::LONGTEXT:
case WSqlDataType::MEDIUMTEXT:
case WSqlDataType::VARCHAR:
case WSqlDataType::NVARCHAR:
case WSqlDataType::DATE:
case WSqlDataType::DATETIME:
case WSqlDataType::YEAR:
case WSqlDataType::TIME:
case WSqlDataType::TIMESTAMP:
case WSqlDataType::TIMESTAMPTZ:
strToReturn = "std::string";
break;
case WSqlDataType::TINYINT:
strToReturn = "short";
break;
case WSqlDataType::SMALLINT:
case WSqlDataType::MEDIUMINT:
case WSqlDataType::INT:
strToReturn = "int";
break;
case WSqlDataType::BIGINT:
strToReturn = "long long";
break;
case WSqlDataType::FLOAT:
strToReturn = "float";
break;
case WSqlDataType::DECIMAL:
case WSqlDataType::DOUBLE:
strToReturn = "double";
break;
default:
strToReturn = WSqlDataType::toString ( _type );
}
return strToReturn;
}
//! Attempt to return a singularized form of \a name
static std::string toSingular ( const std::string &name )
{
std::string strToReturn = name;
size_t size = strToReturn.size();
if ( !size )
return strToReturn;
if ( 's' == strToReturn[size - 1] && 's' != strToReturn[size - 2] ) //dont fix dress, address ..
{
strToReturn.erase ( size - 1 );
if ( 'e' == strToReturn[size - 2] )
{
//eg. Cities to City ..
if ( 'i' == strToReturn[size - 3] )
{
strToReturn.erase ( size - 3 );
strToReturn.append ( "y" );
}
else
if ( 'h' == strToReturn[size - 3] ) //eg. bushes .. might need fixing ..
strToReturn.erase ( size - 2 );
}
return strToReturn;
}
/*!\todo add intelligence: std::string cmp = to_lower(strToReturn);
* if(cmp.compare("people") .. or some such ..*/
return strToReturn;
}
//! Attempt to return a pluralized form of \a name
static std::string toPlural ( const std::string &name )
{
std::string strToReturn = name;
size_t sz = name.size();
if ( sz && 's' == strToReturn[sz - 1] )
strToReturn.append ( "es" );
else
if ( sz > 2 && 'y' == strToReturn[sz - 1]
&& 'o' != strToReturn[sz - 2]
&& 'a' != strToReturn[sz - 2] )
{
strToReturn.erase ( sz - 1 );
strToReturn.append ( "ies" );
}
else
strToReturn.append ( "s" );
//!\todo make me a little smarter .. people, fish, sheep etc.
return strToReturn;
}
/*! \brief Returns a suitable variable name transformed from \a columnname
*
* This translates a column name as defined in a database to a variable name.
* A column name should have the format "name" or "some_name", eg. "user" or
* "order_id" - these will be rendered as "user" and "orderId". Note that in contrast
* to tableNameToClass() the first letter is not capitalized and plural are left plural.
*
* \param std::string - columnname - the name to transform
* \retval std::string - a string suitable for a variable name
*/
static std::string columnNameToVariable ( const std::string &columnname )
{
std::string strToReturn = columnname;
size_t pos = 0;
pos = strToReturn.find ( '_' );
while ( pos != std::string::npos )
{
strToReturn.erase ( pos, 1 );
if ( ( pos + 1 ) < strToReturn.size() )
strToReturn[pos] = toupper ( strToReturn[pos] );
pos = strToReturn.find ( '_' );
}
return strToReturn;
}
/*! \brief Returns a transformed table name as a class name
*
* This translates a table name as defined in a database to a class name.
* A table name should have the format "names" or "some_names", eg. "users" or
* "phone_numbers" - these will be rendered as "User" and "PhoneNumber".
* Note that in keeping with accepted convention the table names are plural;
* these will also be singularized; this results in the table "orders" being
* rendered as "Order" and "order_items" as "OrderItem"
*
* \note This assumes that tables are named according to convention as
* found also in Rails - tablenames without this convention are left as
* is - they will be rendered but may cause problems with class instance
* declarations in generated output.
*
* \param std::string tablename - the name to transform
* \retval std::string - a string suitable for a class name
*/
static std::string tableNameToClass ( const std::string &tablename )
{
std::string strToReturn = toSingular ( tablename );
strToReturn[0] = toupper ( strToReturn[0] );
return columnNameToVariable ( strToReturn );
}
} //namespace WSqlDataType
}// namespace WSql
#endif // WSQLDATATYPE_H