MySQL++

SSQLSv2 Design
Login

SSQLSv2 Design

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.)