0.2.1 available for download
[sqlgg.git] / overview.md
blob6db1b45a69dd2e46bf9bbd3edc89cd2200a24f26
1 sqlgg: SQL Guided (code) Generator
2 ==================================
4 *SQL query parser and binding code generator for C++, Java, OCaml.*
6 * [Problem](#problem)
7 * [Solution](#solution)
8 * [Complete example](#example)
9 * [Details](#details)
10 * [Download](#download)
11 * [TODO](#todo)
13 <a id="problem"/>
14 Problem
15 -------
17 Writing database layer code is usually tedious and error-prone, due to the mix of different
18 languages. SQL queries constructed dynamically need to bind external data (from application), and
19 the resulting rowset must be decomposed into application native data. Data crossing these
20 application-to-database boundaries is what causes troubles. One can factor out all common database
21 communication code, hide the database under some application-specific abstraction, but one always
22 needs to manually specify correspondence between SQL query binding slots (or resulting rowset
23 columns) and code variables. This mapping should be updated manually every time SQL query is
24 modified.
26 <a id="solution"/>
27 Solution
28 --------
30 SQL query parser and code generator which ensures that application code and database queries are in sync.
31 It analyzes SQL query and determines the set of input parameters (values for INSERT, run-time
32 substitution parameters) and the set of resulting columns (for SELECT). Then it generates the
33 code in host language, matching query input and output to function parameters and return values with
34 corresponding native data types. So basically you provide an SQL query and generator creates a
35 function which takes the set of typed parameters as required to fill slots in a query. Generated
36 code binds provided parameters into query and executes it. SELECT statements additionally return the
37 collection of structures with fields representing columns of resulting rowset (or pass those
38 structures to callback-function). The most fruitful consequence of such approach is that
39 the host language compiler will itself check that functions generated from SQL queries will be
40 called correctly (i.e. all parameters bound with correct types). So if you modify the query and
41 forget to update the code -- the compiler will point on erroneous parts.
43 <a id="example"/>
44 Complete example
45 ----------------
47 Suppose we have the database of some money relationships for the group of people. We use the
48 following SQL tables and queries:
50         -- @create_person
51         CREATE TABLE person (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,surname TEXT);
52         -- @add_person
53         INSERT INTO person (name,surname) VALUES;
55         -- @create_money
56         CREATE TABLE money (src INTEGER, dst INTEGER, amount INTEGER);
57         -- @add_money
58         INSERT INTO money VALUES;
60         -- @calc_total
61         SELECT name || ' ' || surname AS fullname, SUM(amount) as total FROM person JOIN money ON src = id GROUP BY id;
62         -- @list_donors
63         SELECT DISTINCT surname FROM person JOIN money ON src = id AND dst = (SELECT id FROM person WHERE surname LIKE @surname) LIMIT @limit;
65 Generate the binding C++ code (boilerplate omitted, only function prototypes shown):
67         // DO NOT EDIT MANUALLY
69         // generated by sqlgg 0.2.0 (735e2815)
71         #pragma once
73         template <class Traits>
74         struct sqlgg
75         {
76                 // CREATE TABLE person (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,surname TEXT)
77                 static bool create_person(typename Traits::connection db);
79                 // INSERT INTO person (name,surname) VALUES
80                 static bool add_person(typename Traits::connection db, typename Traits::Text const& name,       typename Traits::Text const& surname);
82                 // CREATE TABLE money (src INTEGER, dst INTEGER, amount INTEGER)
83                 static bool create_money(typename Traits::connection db);
85                 // INSERT INTO money VALUES
86                 static bool add_money(typename Traits::connection db, typename Traits::Int const& src, typename Traits::Int const& dst, typename Traits::Int const& amount);
88                 // SELECT name || ' ' || surname AS fullname, SUM(amount) as total FROM person JOIN money ON src = id GROUP BY id
89                 struct data_4
90                 {
91                         typename Traits::Text fullname;
92                         typename Traits::Int total;
93                 }; // struct data_4
95                 template<class T>
96                 static bool calc_total(typename Traits::connection db, T& result);
98                 // SELECT DISTINCT surname FROM person JOIN money ON src = id AND dst = (SELECT id FROM person WHERE surname LIKE @surname)
99                 struct data_5
100                 {
101                         typename Traits::Text surname;
102                 }; // struct data_5
104                 template<class T>
105                 static bool list_donors(typename Traits::connection db, T& result, typename Traits::Text const& surname, typename Traits::Int const& limit);
107         }; // struct sqlgg
109 Things to note above:
111 2. Syntax. All queries are written in plain SQL. Every statement should be terminated with
112     semicolon. There is no need to write (?,?) after VALUES in INSERT statement. The annotation
113     `[sqlgg] name=function_name` (or simply `@function_name`) before the query specifies the name
114     of the generated function (this annotation optional but very convenient).
115 1. The generated code is parametrized by database-specific class `Traits`. It specifies the
116                 correspondence between SQL and native types, provides types for database connection and
117     implements actual code to execute statements. `Traits` should be implemented
118                 once for every specific database API.
119 1. `add_money()` function takes three data parameters -- the values to INSERT into table (note the
120     names and types).
121 3. `calc_total()` returns data via `result` parameter. Under the scenes it will
122     bind columns of resulting rowset to the fields of `T::value_type`, which should provide fields
123     `fullname` of type `Traits::Text` and
124     `total` of type `Traits::Int` (otherwise it will fail to compile). For convenience a structure
125     satisfying the requirements for output type is generated alongside the function, `data_4` in
126     this particular case, so `std::vector<data_4>` for `result` is fine.
127 4. The types of parameters for `list_donors` were inferred correctly (`limit` is `Int` and `surname`
128     is `Text`. SQL is not a statically-typed language so the inferred types are based on some
129     reasonable assumptions.
130 5. Statements of arbitrary depth across many tables should be supported.
131 6. Statements are checked for correctness as far as generator is concerned, so it will detect
132                 syntax errors, non-existent columns in expressions, mismatched columns in compound statements,
133                 ambiguous column names etc.
135 Then manually-written C++ code boils down to (without error-checking):
137     #include "../sqlite3_helper.hpp" // sqlite3 traits
138     #include "demo_cxx_gen.hpp" // generated
139     #include <iostream>
140     #include <vector>
142     using namespace std;
144     typedef sqlgg<sqlite3_traits> gen;
145     typedef long long int64;
147     int main()
148     {
149       sqlite3* db = NULL;
150       sqlite3_open(":memory:", &db);
152       // create tables
153       gen::create_person(db);
154       gen::create_money(db);
156       // add all person records
157       gen::add_person(db,"John","Black");
158       int64 john = sqlite3_last_insert_rowid(db);
159       gen::add_person(db,"Ivan","Petrov");
160       int64 ivan = sqlite3_last_insert_rowid(db);
161       gen::add_person(db,"Sancho","Alvares");
162       int64 sancho = sqlite3_last_insert_rowid(db);
164       // add money relations
165       gen::add_money(db,john,ivan,200);
166       gen::add_money(db,john,sancho,100);
167       gen::add_money(db,john,sancho,250);
168       gen::add_money(db,sancho,ivan,300);
170       // summarize by person
171       typedef vector<gen::data_4> collection;
172       collection all;
173       gen::calc_total(db,all);
175       // output
176       cout << "Total transfers:" << endl;
177       for (collection::const_iterator i = all.begin(), end = all.end(); i != end; ++i)
178       {
179          cout << i->fullname << " = " << i->total << endl;
180       }
182       // list donors
183       typedef vector<gen::data_5> people;
184       people p;
185       gen::list_donors(db,p,"petrov",100);
187       cout << "Donors:" << endl;
188       for (people::const_iterator i = p.begin(), end = p.end(); i != end; ++i)
189       {
190         cout << i->surname << endl;
191       }
193       // properly close database
194       sqlite3_close(db);
196       return 0;
197     }
199 The code is straightforward and free of SQL-specific details. More importantly it is statically
200 checked by the compiler -- suppose we change the database schema and add one more field to `money`
201 table (e.g. timestamp of transfer). Compilation rightfully fails:
203     demo_cxx.cpp: In function `int main()':
204     demo_cxx.cpp:29: error: no matching function for call to
205     `sqlgg<sqlite3_traits>::add_money(sqlite3*&, int64&, int64&, int)'
206     demo_cxx_gen.hpp:75: note: candidates are: static bool sqlgg<Traits>::add_money(typename
207     Traits::connection, const typename Traits::Int&, const typename Traits::Int&, const typename
208     Traits::Int&, const typename Traits::Int&) [with Traits = sqlite3_traits]
210 <a id="details"/>
211 Details
212 -------
214 The main idea is that the generator should take care only of semantic binding between SQL and code
215 sides, being as unobtrusive as possible. So the choice of the specific database and API is a
216 programmer's choice. Similarly, queries to the database are expressed in plain SQL, so that the
217 generator can be easily plugged in any existing project -- just move all SQL statements used in the
218 code to separate file and feed it to generator.
220 Distinguishing feature of **sqlgg** is that it starts off with actual SQL queries, not object models
221 or SQL table descriptions. Ideally, it generates {meta,template,abstract,generic} code which is later
222 specialized for actual database/environment (if the target language provides such ability).
224 This is work in progress and there is plenty of room for improvement. For now the status of this
225 project is **works for me** .  I use it for some simple database-access code with
226 [sqlite3](http://sqlite.org) engine (using suitable [sqlite3_traits.hpp][sqlite3_cxx] helper).
227 This project was started when I found myself editing existing code with tons of C++ wrappers for SQL
228 queries, each binding several parameters and decomposing results.
230 **Sqlgg** understands the subset of the standard SQL language with arbitrary database-specific
231 extensions. The goal is to cover as much SQL as possible in order to accept most of the real-world
232 SQL statements unmodified.
234 Available output languages:
236 * **C++**: code is parametrized with template class for database specific code
237   ([generated code example][demo_cxx_gen], [sqlite3 traits][sqlite3_cxx],
238   [usage example (sqlite3)][demo_cxx_sqlite3], [mysql traits][mysql_cxx],
239   [usage example (mysql)][demo_cxx_mysql]).
240 * **Java**: code for JDBC ([generated code example][demo_java_gen],
241         [usage example (mysql)][demo_java_mysql]).
242 * **OCaml**: functor parametrized with module for database specific code
243         [Sqlgg\_traits.M][sqlgg_caml] ([generated code example][demo_caml_gen],
244   [OCaml-SQLite3 traits][sqlite3_caml], [usage example][demo_caml_sqlite3]).
245 * **XML**: input parameters and rowset schema with types and names -- everything needed
246   to further generate code (e.g. using XSLT) in some other language
247   ([generated xml example][demo_xml_gen])
249 [OCaml-SQLite3]:        http://caml.inria.fr/cgi-bin/hump.en.cgi?contrib=471
251 [sqlite3_cxx]:  impl/sqlite3_traits.hpp
252 [mysql_cxx]: impl/mysql_traits.hpp
253 [sqlite3_caml]: impl/sqlgg_sqlite3.ml
254 [sqlgg_caml]: impl/sqlgg_traits.ml
256 [demo_cxx_gen]: demo/demo_cxx_gen.hpp
257 [demo_caml_gen]: demo/demo_caml_gen.ml
258 [demo_xml_gen]: demo/demo_xml_gen.xml
259 [demo_java_gen]: demo/demo_java_gen.java
261 [demo_cxx_sqlite3]:     demo/demo_cxx.cpp
262 [demo_cxx_mysql]: demo/demo_cxx_mysql.cpp
263 [demo_caml_sqlite3]: demo/demo_caml.ml
264 [demo_java_mysql]: demo/demo_java.java
266 <a id="download"/>
267 Download
268 --------
270 sqlgg 0.2.1 for [Windows](dist/sqlgg-0.2.1.zip), [Linux](dist/sqlgg-0.2.1.tar.gz).
272 Try it [online](sql.cgi).
274 <a id="todo"/>
275 TODO
276 ----
278 * reuse prepared statements
279 * query parameter placeholders are specific to database API, decide what to do
280 * choose better names for some common cases (WHERE id = ? etc)
281 * fix line numbers in error output
282 * enhance error reporting
283 * type-inferrer is too primitive
284 * detect statements on single tables and group the corresponding generated code together
285 * check function names for uniqueness
286 * support/test other SQL engines
287 * generate code for more languages
288 * read SQL spec
289 * distinguish predicates and expressions
290 * type check expressions
292 ----
293 2009-06-05
295 <style>
296 code { font-family: monospace; }
297 pre { background-color: #eee; border: 1px solid #0f0; }
298 :not(pre) > code { font-size: 1em; }
299 </style>