month+1
[sqlgg.git] / overview.md
blob383d1d77f38501681271a4f0cc1a15bff4b7ba57
1 <title>sqlgg: SQL Guided (code) Generator</title>
2 <span class="right border">[ygrek.org.ua](http://ygrek.org.ua/) / [p](http://ygrek.org.ua/p/) / [sqlgg](http://ygrek.org.ua/p/sqlgg/)</span>
4 sqlgg: SQL Guided (code) Generator
5 ==================================
7 *SQL query parser and binding code generator for C#, C++, Java, Objective Caml.*
9 * [Problem](#problem)
10 * [Solution](#solution)
11 * [Complete example](#example)
12 * [Details](#details)
13 * [Download](#download)
14 * [TODO](#todo)
16 <a id="problem"/>
17 Problem
18 -------
20 Writing database layer code is usually tedious and error-prone, due to the mix of different
21 languages. SQL queries constructed dynamically need to bind external data (from application), and
22 the resulting rowset must be decomposed into application native data. The impedance mismatch between
23 database layer and application code, as well as the absence of static (compile-time) checking, 
24 frequently leads to runtime failures (and sometimes even security holes). One
25 can factor out all common database communication code, hide the database under some
26 application-specific abstraction, but one always needs to manually specify correspondence between
27 SQL query binding slots (or resulting rowset columns) and code variables. This mapping should be
28 updated manually every time SQL query is modified.
30 <a id="solution"/>
31 Solution
32 --------
34 SQL query parser and code generator which ensures that application code and database queries are in
35 sync. It analyzes SQL query and determines the set of input parameters (values for INSERT, run-time
36 substitution parameters) and the set of resulting columns (for SELECT). Then it generates the code
37 in host language, mapping query parameters on function arguments with corresponding native data
38 types. So basically you provide an SQL query and generator creates a function which takes the set of
39 typed parameters as required to fill slots in a query. Generated code binds provided parameters into
40 query and executes it. The code for SELECT statements additionally invokes a strongly-typed callback
41 function for every row of the resulting rowset. The most fruitful consequence of such
42 approach is that the host language compiler will itself check that functions generated from SQL
43 queries will be called correctly (i.e. all parameters bound with correct types). So if you modify
44 the query and forget to update the code -- the compiler will point on erroneous parts.
46 <a id="example"/>
47 Complete example
48 ----------------
50 Suppose we have the database of some money relationships for the group of people. We use the
51 following SQL tables and queries:
53         -- @create_person
54         CREATE TABLE person (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,surname TEXT);
55         -- @add_person
56         INSERT INTO person (name,surname) VALUES;
58         -- @create_money
59         CREATE TABLE money (src INTEGER, dst INTEGER, amount INTEGER);
60         -- @add_money
61         INSERT INTO money VALUES;
63         -- @calc_total
64         SELECT name || ' ' || surname AS fullname, SUM(amount) as total FROM person JOIN money ON src = id GROUP BY id;
65         -- @list_donors
66         SELECT DISTINCT surname FROM person JOIN money ON src = id AND dst = (SELECT id FROM person WHERE surname LIKE @surname) LIMIT @limit;
68 Generate the binding C++ code (boilerplate omitted, only function prototypes shown):
69 <span class="right also">
70 (corresponding [C#][demo_csharp_gen] [C++][demo_cxx_gen] [Java][demo_java_gen]
71 [OCaml][demo_caml_gen] [XML][demo_xml_gen])
72 </span>
74         // DO NOT EDIT MANUALLY
76         // generated by sqlgg 0.2.0 (735e2815)
78         #pragma once
80         template <class Traits>
81         struct sqlgg
82         {
83                 // CREATE TABLE person (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,surname TEXT)
84                 static bool create_person(typename Traits::connection db);
86                 // INSERT INTO person (name,surname) VALUES
87                 static bool add_person(typename Traits::connection db, typename Traits::Text const& name,       typename Traits::Text const& surname);
89                 // CREATE TABLE money (src INTEGER, dst INTEGER, amount INTEGER)
90                 static bool create_money(typename Traits::connection db);
92                 // INSERT INTO money VALUES
93                 static bool add_money(typename Traits::connection db, typename Traits::Int const& src, typename Traits::Int const& dst, typename Traits::Int const& amount);
95                 // SELECT name || ' ' || surname AS fullname, SUM(amount) as total FROM person JOIN money ON src = id GROUP BY id
96                 struct data_4
97                 {
98                         typename Traits::Text fullname;
99                         typename Traits::Int total;
100                 }; // struct data_4
102                 template<class T>
103                 static bool calc_total(typename Traits::connection db, T& result);
105                 // SELECT DISTINCT surname FROM person JOIN money ON src = id AND dst = (SELECT id FROM person WHERE surname LIKE @surname)
106                 struct data_5
107                 {
108                         typename Traits::Text surname;
109                 }; // struct data_5
111                 template<class T>
112                 static bool list_donors(typename Traits::connection db, T& result, typename Traits::Text const& surname, typename Traits::Int const& limit);
114         }; // struct sqlgg
116 Things to note above:
118 2. Syntax. All queries are written in plain SQL. Every statement should be terminated with
119     semicolon. There is no need to write (?,?) after VALUES in INSERT statement. The annotation
120     `[sqlgg] name=function_name` (or simply `@function_name`) before the query specifies the name
121     of the generated function (this annotation is optional but very convenient).
122 1. The generated code is parametrized by database-specific class `Traits`. It specifies the
123                 correspondence between SQL and native types, provides types for database connection and
124     implements actual code to execute statements. `Traits` should be implemented
125                 once for every specific database API.
126 1. `add_money()` function takes three data parameters -- the values to INSERT into table (note the
127     names and types).
128 3. `calc_total()` returns data via `result` parameter. Behind the scenes it will
129     bind columns of resulting rowset to the fields of `T::value_type`, which should provide fields
130     `fullname` of type `Traits::Text` and
131     `total` of type `Traits::Int` (otherwise it will fail to compile). For convenience a structure
132     satisfying the requirements for output type is generated alongside the function, `data_4` in
133     this particular case, so `std::vector<data_4>` for `result` is fine.
134 4. The types of parameters for `list_donors` were inferred correctly (`limit` is `Int` and `surname`
135     is `Text`. SQL is not a statically-typed language so the inferred types are based on some
136     reasonable assumptions.
137 5. Statements of arbitrary depth across many tables should be supported.
138 6. Statements are checked for correctness as far as generator is concerned, so it will detect
139                 syntax errors, non-existent columns in expressions, mismatched columns in compound statements,
140                 ambiguous column names etc.
142 Then manually-written C++ code boils down to (without error-checking):
143 <span class="right also">
144 (corresponding [C#][demo_csharp_mysql] [C++][demo_cxx_sqlite3] [Java][demo_java_mysql]
145 [OCaml][demo_caml_sqlite3])
146 </span>
148     #include "../sqlite3_helper.hpp" // sqlite3 traits
149     #include "demo_cxx_gen.hpp" // generated
150     #include <iostream>
151     #include <vector>
153     using namespace std;
155     typedef sqlgg<sqlite3_traits> gen;
156     typedef long long int64;
158     int main()
159     {
160       sqlite3* db = NULL;
161       sqlite3_open(":memory:", &db);
163       // create tables
164       gen::create_person(db);
165       gen::create_money(db);
167       // add all person records
168       gen::add_person(db,"John","Black");
169       int64 john = sqlite3_last_insert_rowid(db);
170       gen::add_person(db,"Ivan","Petrov");
171       int64 ivan = sqlite3_last_insert_rowid(db);
172       gen::add_person(db,"Sancho","Alvares");
173       int64 sancho = sqlite3_last_insert_rowid(db);
175       // add money relations
176       gen::add_money(db,john,ivan,200);
177       gen::add_money(db,john,sancho,100);
178       gen::add_money(db,john,sancho,250);
179       gen::add_money(db,sancho,ivan,300);
181       // summarize by person
182       typedef vector<gen::data_4> collection;
183       collection all;
184       gen::calc_total(db,all);
186       // output
187       cout << "Total transfers:" << endl;
188       for (collection::const_iterator i = all.begin(), end = all.end(); i != end; ++i)
189       {
190          cout << i->fullname << " = " << i->total << endl;
191       }
193       // list donors
194       typedef vector<gen::data_5> people;
195       people p;
196       gen::list_donors(db,p,"petrov",100);
198       cout << "Donors:" << endl;
199       for (people::const_iterator i = p.begin(), end = p.end(); i != end; ++i)
200       {
201         cout << i->surname << endl;
202       }
204       // properly close database
205       sqlite3_close(db);
207       return 0;
208     }
210 The code is straightforward and free of most database-specific details. More importantly it is
211 statically checked by the compiler -- suppose we change the database schema and add one more field
212 to `money` table (e.g. timestamp of transfer). Compilation rightfully fails:
214     demo_cxx.cpp: In function `int main()':
215     demo_cxx.cpp:29: error: no matching function for call to
216     `sqlgg<sqlite3_traits>::add_money(sqlite3*&, int64&, int64&, int)'
217     demo_cxx_gen.hpp:75: note: candidates are: static bool sqlgg<Traits>::add_money(typename
218     Traits::connection, const typename Traits::Int&, const typename Traits::Int&, const typename
219     Traits::Int&, const typename Traits::Int&) [with Traits = sqlite3_traits]
221 <a id="details"/>
222 Details
223 -------
225 Distinguishing feature of **sqlgg** is that it starts off with actual SQL queries, not object models
226 or SQL table descriptions. Ideally, it generates {meta,template,abstract,generic} code which is later
227 specialized for the actual database/environment by the means of the target language. The
228 generated code doesn't require any special runtime support and doesn't impose any restrictions on
229 the application.
231 The main idea is that the generator should take care only of semantic binding between SQL and code
232 sides, being as unobtrusive as possible. So the choice of the specific database and API is a
233 programmer's choice. Similarly, queries to the database are expressed in plain SQL, so that the
234 generator can be easily plugged in any existing project -- just move all SQL statements used in the
235 code to a separate file and feed it to generator. The generated code can be easily inspected just as
236 any other code in the project, and you can even edit it manually if such need arises (though it is
237 highly not recommended). In order to keep code and database layer in sync
238 the generator must be invoked every time SQL queries are modified.
240 For now the status of this project is **works for me** . It is evolving and there is
241 plenty of room for improvement. The generated code and traits interfaces may change from time to
242 time. I am interested in opinions on output code structure/naming best fitting the target
243 language/platform.
245 This project was started when I found myself editing existing code
246 with tons of C++ wrappers for SQL queries, each binding several parameters and decomposing results.
247 I used it for C++ desktop applications with [sqlite3](http://sqlite.org) engine and 
248 C#/[Mono](http://mono-project.com) server-side 
249 component with [mysql](http://dev.mysql.com) database.
251 **Sqlgg** understands the subset of the standard SQL language with arbitrary database-specific
252 extensions. The goal is to cover as much SQL as possible in order to accept most of the real-world
253 SQL statements unmodified.
255 Available output languages:
257 * **C#**: code for System.Data ([generated code example][demo_csharp_gen],
258   [usage example][demo_csharp_mysql]).
259 * **C++**: code is parametrized with template class for database specific code
260   ([generated code example][demo_cxx_gen], [sqlite3 traits][sqlite3_cxx],
261   [usage example (sqlite3)][demo_cxx_sqlite3], [mysql traits][mysql_cxx],
262   [usage example (mysql)][demo_cxx_mysql]).
263 * **Java**: code for JDBC ([generated code example][demo_java_gen],
264         [usage example][demo_java_mysql]).
265 * **OCaml**: functor parametrized with module for database specific code
266         [Sqlgg\_traits.M][sqlgg_caml] ([generated code example][demo_caml_gen],
267   [OCaml-SQLite3 traits][sqlite3_caml], [usage example][demo_caml_sqlite3]).
268 * **XML**: input parameters and rowset schema with types and names -- everything needed
269   to further generate code (e.g. using XSLT) in some other language
270   ([generated xml example][demo_xml_gen])
272 [OCaml-SQLite3]:        http://caml.inria.fr/cgi-bin/hump.en.cgi?contrib=471
274 [sqlite3_cxx]:  impl/sqlite3_traits.hpp
275 [mysql_cxx]: impl/mysql_traits.hpp
276 [sqlite3_caml]: impl/sqlgg_sqlite3.ml
277 [sqlgg_caml]: impl/sqlgg_traits.ml
279 [demo_cxx_gen]: demo/demo_cxx_gen.hpp
280 [demo_caml_gen]: demo/demo_caml_gen.ml
281 [demo_xml_gen]: demo/demo_xml_gen.xml
282 [demo_java_gen]: demo/demo_java_gen.java
283 [demo_csharp_gen]: demo/demo_csharp_gen.cs
285 [demo_cxx_sqlite3]:     demo/demo_cxx.cpp
286 [demo_cxx_mysql]: demo/demo_cxx_mysql.cpp
287 [demo_caml_sqlite3]: demo/demo_caml.ml
288 [demo_java_mysql]: demo/demo_java.java
289 [demo_csharp_mysql]: demo/demo_csharp.cs
291 <a id="download"/>
292 Download
293 --------
295 sqlgg 0.2.2 for [Windows](dist/sqlgg-0.2.2.zip) and [Linux](dist/sqlgg-0.2.2.tar.gz) (released on 29 June 2009). [Changes history](dist/changelog).
297 Try it [online](sql.cgi).
299 <a id="todo"/>
300 TODO
301 ----
303 * some database API are inadequately strict about data types (notably ADO.NET), need native-type annotations in queries
304 * choose better names for some common cases (WHERE id = ? etc)
305 * fix line numbers in error output
306 * enhance error reporting
307 * type-inferrer is too primitive
308 * detect statements on single tables and group the corresponding generated code together
309 * check function names for uniqueness
310 * support/test other SQL engines
311 * generate code for more languages
312 * read SQL spec
313 * distinguish predicates and expressions
314 * type check expressions
316 ----
317 2009-06-29
318 <span class="right">[sqlgg&#x40;ygrek&#x2E;org&#x2E;ua](mailto:sqlgg&#x40;ygrek&#x2E;org&#x2E;ua)</span>
320 <style>
321 .right { position: absolute; right: 1em; }
322 .also { margin-top: 1em; font-style: italic; font-size: 80%; }
323 .border { padding: 0.5em; border: 1px solid black; }
324 code { font-family: monospace; }
325 pre { background-color: #eee; border: 1px solid #0f0; }
326 :not(pre) > code { font-size: 1em; }
327 html { font-family: sans-serif; }
328 </style>