SSQLSv2 needs to be some kind of specialized declaration language that's translated by a tool to C++ implementation and header files. The main goal is to fix the type safety and debuggability issues people have had with SSQLS.
The make(1)
rules for this look like this:
.SUFFIXES: .ssqls
.ssqls.cpp:
ssqlsxlat -i $<
my-module.o: my-module.cpp stock.cpp
This says my-module.cpp
also needs stock.cpp
to be built, and how to build it using ssqlsxlat
, the tool we create along with the MySQL++ library. You can do essentially the same thing in other build systems, too, or you can run the tool by hand if your declarations change rarely.
What I've not been certain about is what sort of syntax to use in .ssqls
files. I'm now thinking about something very simple, like the
following. First, the file stock.ssqls
:
include common.ssqls
table stock alias SQLStock filebase ssqls_stock:
field item type bigint alias id not null auto_increment
field num type bigint
field weight type double
field price type double
field sdate type date
And common.ssqls
:
option implementation_extension cc
option header_extension hh
option use_accessors getX
You could put the option declarations into the same file as the table declaration, but I think this will be a popular way to do things, since even the MySQL++ examples now have more than one table. Alternately, you can put everything into a single file, even multiple table declarations; ssqlsxlat
won't care.
The above declarations create ssqls_stock.hh
:
class SQLStock : public mysqlpp::SSQLS {
public:
SQLStock();
SQLStock(
mysqlpp::sql_bigint f1,
mysqlpp::sql_bigint f2,
mysqlpp::sql_double f3,
mysqlpp::sql_double f4,
mysqlpp::sql_date f5);
mysqlpp::sql_bigint getId() const;
mysqlpp::sql_bigint getNum() const;
mysqlpp::sql_double getWeight() const;
mysqlpp::sql_double getPrice() const;
const mysqlpp::sql_date& getSdate() const;
void getId(mysqlpp::sql_bigint value);
void getNum(mysqlpp::sql_bigint value);
void getWeight(mysqlpp::sql_double value);
void getPrice(mysqlpp::sql_double value);
void getSdate(const mysqlpp::sql_date& value);
private:
mysqlpp::sql_bigint id_;
mysqlpp::sql_bigint num_;
mysqlpp::sql_double weight_;
mysqlpp::sql_double price_;
mysqlpp::sql_date sdate_;
bool id_is_set_;
static const char* table_name_;
};
It also creates ssqls_stock.cc
, with the implementation of the member functions declared in the .hh
file.
We explicitly set a base name for output files with the filebase
keyword in the table declaration; if that isn't given we will use the
table alias if given, or the table name if not. We've also overridden the default file name extension style, which is .cpp
and .h
.
Think of the syntax of .ssqls
files as a pastiche of Python and SQL syntax: whitespace is significant, case is preserved but insignificant in comparisons, quotes are optional unless needed to preserve spaces in names, and named options (type
, alias
, {not_}null
, filebase
...) let us accept them in any order. It's a little verbose, but it's better than XML, and probably nearly as easy to parse. (I still have to write a tokenizer this way instead of using an existing XML parser, but after that it's walking trees no matter which way I go.)
All SSQLSes will derive from a common base class, if for no other reason than it means we can avoid using so many template member functions in the Query class. It will also avoid a lot of repeated code between SSQLSes, as compared to what is generated in the current scheme. Hopefully this will bring binary sizes and compile times down a bit; MySQL++ is a bit of a pig when using SSQLS right now.
Notice that the SSQLS declaration uses SQL type names, not C++ type names, and generated code uses the types declared in MySQL++'s sql_types.h
. This frees the programmer from having to guess the correct C++ type name to use for a given SQL field type. Maybe there is a good argument for letting a programmer pick a different C++ type that also works but has different properties, but if so, that can be supported via an option along the same lines as alias
.
The option
statements let you control naming scheme details. By default it will output code in a style close to that used within
MySQL++; if we have to pick a default, it might as well be that. But I realize this is not the worldwide standard, so wherever possible it
should give you the option to change its output code style.
The alias
option on a field or table name lets you use a different name in your C++ code than is used for the corresponding entity in the database.
I envision a desire for many different accessor styles. get_x
is an obvious one. Another popular one we could call "unified": the same method name is used for both get and set operations, the former taking a value and returning void, and the latter swapping that. If you declare no accessor style, you get the same interface as offered by SSQLSv1: public data members and no accessor methods.
A side benefit of the new accessor feature is a solution to the auto increment problem brought up recently by Graham Reitz. Because data members are hidden behind public member functions, we will know when a field has been explicitly set (id_is_set_ == true
), and when it retains its default. If it still retains its default and it is also marked as auto_increment
, we will know to leave it out when building an INSERT
or UPDATE
query.
The addition of trailing underscores when using private members for the field variables is not purely a style issue. It avoids a name conflict if you use the unified accessor style at the same time. If you don't use accessors, it won't decorate the variable names at all.
We can add SSQLSv1 and MySQL database schema parsing (a la dbinfo.cpp
) to ssqlsxlat
. In each case, it spits out a .ssqls
file:
ssqlsxlat -1 module-containing-ssqlsv1-decls.cpp -o tablename.ssqls
ssqlsxlat -s my.server.org -u fred -p wilma -d dbname \
-t tablename -o tablename.ssqls
If you give both -i
(read from .ssqls
file) and -o
(write one out) you get a debugging feature for free. Programmer sez to tool: "Tell me how you understand the contents of this file, ssqlsxlat
." This also suggests how the previous two features work: they just build up the same type of parse tree created when reading in a .ssqls
file, and -o
walks that same tree, writing out its contents in .ssqls
syntax.
(See the mailing list thread for responses.)