diff --git a/docs/Database.md b/docs/Database.md new file mode 100644 index 00000000..90b6abc8 --- /dev/null +++ b/docs/Database.md @@ -0,0 +1,11 @@ +#Introduction +sqlpp11 is a library mainly for constructing queries and interpreting results. It does not know how to talk to a database on its own. It needs database connectors for that. Depending on the database you want to use, you can use an existing connector, for instance + + * MySQL: https://github.com/rbock/sqlpp11-connector-mysql + * sqlite3: https://github.com/rbock/sqlpp11-connector-sqlite3 + * PostgreSQL: https://github.com/matthijs/sqlpp11-connector-postgresql + * STL Container (highly experimental): https://github.com/rbock/sqlpp11-connector-stl + +Or you have to write your own connector. Don't worry, it is not that hard. + +The api is documented [here](https://github.com/rbock/sqlpp11/blob/master/connector_api/connection.h). \ No newline at end of file diff --git a/docs/Dynamic-Select.md b/docs/Dynamic-Select.md new file mode 100644 index 00000000..f6ce6ff6 --- /dev/null +++ b/docs/Dynamic-Select.md @@ -0,0 +1,69 @@ +# Introduction +_This page explains dynamic select statements. Before studying this page, you should read about [static select statements](Select.md)._ + +If you know the exact structure of your queries at compile time, statically constructed select statements are perfect. But if the structure depends on runtime information like user input, you will need dynamic select statements. Depending on your needs, you can choose the required dosage. + +# A Basic Example +So let us construct select query with a dynamic part +```C++ +auto s = dynamic_select(db, all_of(foo)).from(foo).dynamic_where(); +if (runtimeQuery.id) + s.where.add(foo.id == runtimeQuery.id); +if (!runtimeQuery.name.empty()) + s.where.add(foo.name == runtimeQuery.name); +``` +Admittedly, a rather lame example (please suggest nicer ones), but anyway, this is what's happening: +```C++ +dynamic_select(db, ...) +``` +This initializes a dynamic select. One major difference between `dynamic_select` and `select` is the first argument of `dynamic_select`: a database connection. This is used to evaluate the dynamic parts of the query as they are added. + +```C++ +.dynamic_where(); +... +s.where.add(foo.name == runtimeQuery.name); +s.where.add(foo.id == runtimeQuery.id); +``` +The first part creates a new select object that accepts a dynamically constructed where expression. In this case the user can determine whether to search for a certain name or a certain id, or neither or both. + +# Dynamic Select +## Dynamic Columns +If the (some) selected columns are not known at compile time, you can do something like this: +```C++ +auto s = dynamic_select(db).dynamic_columns(foo.id).from(foo).unconditionally(); +if (someCondition) + s.selected_columns.add(foo.name); +if (someOtherCondition) + s.selected_columns.add(foo.hasFun); +``` +In this example, the column id is always selected. The other two columns may or may not be selected. This is determined at runtime. This impacts the way the results are accessed because the type of the result row is not known at the same level of detail as in the static case. The dynamic fields can be accessed via name lookup like in a map: +```C++ +for (const auto& row : db(s)) +{ + long id = row.id; + if (someCondition) + std::string name = row.at("name"); + if (someOtherCondition) + std::string hasFun = row.at("hasFun"); +} +``` +This also shows another difference. Dynamic fields are of text type. It would be possible to add conversion methods for other types as well, but this has not been coded yet. Please let me know you wishes. + +## Dynamic From +In a static query the compiler can verify whether the `from()` clause is sufficient to support all other aspects of the query. + +With a dynamic from, the compile cannot know tables that going into the `from()`. Such checks would therefore be impossible. But it allows you to add joins at runtime! + +```C++ +auto s = dynamic_select(db, all_of(foo)).dynamic_from(foo).dynamic_where(); +if (someOtherCondition) + s.from.add(dynamic_join(bar).on(foo.barId == bar.id)); +``` + +In this example, the user may want to include `bar` into the query. + +## Dynamic Where +As shown in other examples, the where condition can be constructed dynamically using the methods `dynamic_where()` and `where.add()`. The former prepares the select to accept dynamic where conditions, the latter adds a condition. Several calls to add_where will be combined into one condition by `and`. + +## Dynamic Having, Group By, OrderBy, Limit and Offset +Similar to the dynamic where, you can use `dynamic_having`, `dynamic_group_by`, `dynamic_order_by`, `dynamic_limit` and `dynamic_offset` to prepare the select statement to accept certain dynamic aspects. Using `having.add`, `group_by.add`, `order_by.add`, `limit.set` and `offset.set` you can then adjust those query parts according to the runtime information. diff --git a/docs/Exception-Handling.md b/docs/Exception-Handling.md new file mode 100644 index 00000000..b0411855 --- /dev/null +++ b/docs/Exception-Handling.md @@ -0,0 +1,10 @@ +## When to expect an exception + +sqlpp11 connectors throw the majority of `sqlpp::exception`s so check your connector's documentation. Generally, you should expect an exception when: + +- Connecting to a database +- Preparing a statement +- Executing a statement +- Retrieving and iterating through result rows + +Additionally, the date library used by sqlpp11 may throw `std::runtime_error`. As of 2017-04-08 this only happens when formatting a date using a format string. \ No newline at end of file diff --git a/docs/Functions.md b/docs/Functions.md new file mode 100644 index 00000000..f23bea07 --- /dev/null +++ b/docs/Functions.md @@ -0,0 +1,86 @@ +sqlpp11 offers equivalents for quite a few SQL functions. It also has some additional functions to make the developer's life easier, see [Misc Functions](#Misc-Functions). + +If functions can be used as select columns, the column's name is the name of the function. You can use `.as()` of course to give it another name, see [Select](Select.md). There are just a few exceptions like `any()` which cannot be named. + +# Member Functions +## in and not_in +The methods `in()` and `not_in()` can be used are available for pretty much any expression. They take zero or more value expressions. You can make the list dynamic by using a container of values. + +```C++ +tab.a.in(); // not valid SQL but certainly useful for generic code, evaluates to a false expression (version 0.35 and later). +tab.a.in(x, ...); // x, ... being one or more value expressions +tab.a.in(sub_select); // sub_select being a select expression with one result column of appropriate type. +tab.a.in(sqlpp::value_list(some_container_of_values)); // evaluates to a false expression in case of an empty container +``` + +## is_null +## like + +# Aggregate Functions +The following aggregate functions are supported + * avg + * count + * max + * min + * sum + +For example: +```C++ +for (const auto& row : db(select(tab.name, avg(tab.value)) + .from(tab) + .where(tab.id > 17) + .group_by(tab.name))) +{ + std::cerr << row.name << ": " << row.avg << std::endl; +} +``` + +# Text Functions +## concat +Just use the + operator :-) + +# Sub-Query Functions +## exists +## any +## some + +# Misc Functions +sqlpp11 offers a few functions that do not mimic SQL but are there to make your life easier. + +## value +The `value` function turns the argument into an unnamed SQL expression, e.g. an `int` into an `sqlpp::integral` or a `std::string` into an `sqlpp::text`. You probably don't need to use this function too often, because in most cases, sqlpp expressions do the conversion themselves. For instance +```C++ +tab.foo + 17 +``` +is a perfectly valid expression if `tab.foo` represents an SQL integral value. But when doing some generic query programming you might get into the situation that you want to select a constant value. For instance: +```C++ +for (const auto& row : select(sqlpp::value(7).as(sql::alias::a)).from(tab))) +{ + int64_t a = row.a; +} +``` + +##value_list +`value_list` is a helper function that takes a container of values and turns it into an sqlpp11 construct that is understood by the `in()` member function of expressions, see above. + +## verbatim +sqlpp11 supports quite a few aspects of SQL. But it does certainly not cover everything, nor will it ever. So what if you need to use something that is not supported? At least for expressions there is an easy way to use unsupported features, the `verbatim()` method. It requires a template parameter to determine the SQL type and a string argument containing the expression, for instance: +```C++ +select(all_of(tab)).from(tab).where(tab.foo == 42 and sqlpp::verbatim("mighty special feature")); +``` +_Use with care_, as sqlpp11 does not bother to look into the string you provide. That means you have to handle type checking, syntax checking, escaping of injected evil data from your users, etc. + +## flatten +Say `tab.foo` and `tab.bar` represent two bigint columns. Then the type of `tab.foo` and `tab.bar` is different. Also `tab.foo + 17` and `tab.bar + 17` are expressions of different type. This is because the expression carries information about required tables for instance (and that is going to be used to check if `from()` contains all the required tables. +The expression templates can get in your way though, if you want to create parts of your query dynamically: +```C++ +auto e = (tab.foo == 17); +if (userWantsBar) + e = (tab.bar == 17); // won't compile +``` +You can use [dynamic select](Dynamic-Select.md), but there is an alternative, the `flatten()` method, which turns the expression into a more generic expression type (it just "remembers" whether it is a text, boolean etc). +```C++ +auto e = flatten(tab.foo == 17); +if (userWantsBar) + e = flatten(tab.bar == 17); // will compile +``` diff --git a/docs/Home.md b/docs/Home.md new file mode 100644 index 00000000..0bf6ca06 --- /dev/null +++ b/docs/Home.md @@ -0,0 +1,24 @@ +# Introduction +Lets see: +* You know C++? +* You know some SQL? +* You want to use SQL in your C++ program? +* You think C++ and SQL should play well together? +* You know which tables you want to use in a database? +* You can cope with a few template error messages in case something is wrong? + +You have come to the right place! + +sqlpp11 offers you to code SQL in C++ almost naturally. You can use tables, columns and functions. Everything has strong types which allow the compiler to help you a lot. At compile time, it will tell about most of those pesky oversight errors you can make (typos, comparing apples with oranges, forgetting tables in a select statement, etc). And it does not stop at query construction. Results have ranges, and strongly typed members, so that you can browse through results in a type-safe manner, worthy of modern C++. + +The following pages will tell you how to use it: +* [Database](Database.md) +* [Tables](Tables.md) +* [Insert](Insert.md) +* [Select](Select.md) <- You might want to read this first as an appetizer +* [Update](Update.md) +* [Remove](Remove.md) +* [Functions](Functions.md) +* [Prepared Statements](Prepared-Statements.md) +* [NULL](NULL.md) +* [New Features](New-Features.md) diff --git a/docs/Insert.md b/docs/Insert.md new file mode 100644 index 00000000..ff31b84f --- /dev/null +++ b/docs/Insert.md @@ -0,0 +1,18 @@ +Haven't found the time to document this in any detail, yet, but this is an example: + +```C++ +db(insert_into(tab).set(tab.gamma = true)); +``` + +This is how you could insert multiple rows at a time: + +```C++ +auto multi_insert = insert_into(t).columns(t.gamma, t.beta, t.delta); +multi_insert.values.add(t.gamma = true, t.beta = "cheesecake", t.delta = 1); +multi_insert.values.add(t.gamma = sqlpp::default_value, t.beta = sqlpp::default_value, + t.delta = sqlpp::default_value); +multi_insert.values.add(t.gamma = sqlpp::value_or_null(true), + t.beta = sqlpp::value_or_null("pie"), + t.delta = sqlpp::value_or_null(sqlpp::null)); +db(multi_insert); +``` \ No newline at end of file diff --git a/docs/NULL.md b/docs/NULL.md new file mode 100644 index 00000000..6b8a7d8f --- /dev/null +++ b/docs/NULL.md @@ -0,0 +1,100 @@ +# Introduction +Database NULL is a strange beast. It can be compared to anything but that comparison never returns true. It also never returns false, it returns NULL. Even + +```SQL +NULL != NULL -> NULL +NULL = NULL -> NULL +``` + +A value like that would be pretty unusual in C++. Especially since fields in a result could be either a decent value or NULL. And this can change from result row to result row. + +Also, in `where` or `having` conditions, you have to change the expression (not just parameters) if you want to NULL instead of a value: + +```SQL +a = 7 +a IS NULL +``` + +# Obtaining potential NULL values from a select +sqlpp11 can determine whether a result field can be null, based on the columns involved and the structure of your query. If in doubt (for instance due to dynamic parts), it will assume that a field can be NULL. + +You can check if a field is NULL by calling the `is_null()` method. That's easy. + +When it comes to accessing the value, there are two options, though. These can be controlled by the connection class and the columns of your tables. + +## Option 1: No conversion operator +```C++ +class connection: public sqlpp::connection +{ + public: + using _traits = ::sqlpp::make_traits<::sqlpp::no_value_t, + ::sqlpp::tag::enforce_null_result_treatment + >; +``` +If this tag is used in the connection's traits and the respective column does not override it, then there is no conversion operator for fields that can be NULL. You have to access the value through the `value()` method. +If the field is NULL, this method will throw an `sqlpp::exception`. + +## Option 2: Conversion operator, converting NULL to trivial value +If the `tag::enforce_null_result_treatment` is not used in the connection class or the respective column uses `tag::enforce_null_result_treatment`, then there is a conversion operator. Both the conversion operator and the `value()` method will not throw in case of a NULL value. Instead, the will return the trivial value for the field's type, e.g. 0 for numbers or "" for texts. + +## Alternatives: +One often discussed alternative would be boost::optional or (in the future) std::optional. There is one drawbacks (correct me if I am wrong, please): + +`optional` cannot be used for binding result values because it is unclear whether there already is a value to bind to. + +# Handling NULL in statements +When adding potential NULL values to a statement, you have two options: + +## Manually +```C++ +auto s = dynamic_select(db, all_of(tab)).from(tab).dynamic_where(); +if (i_do_have_a_decent_value_of_alpha) + s.add_where(tab.alpha == alpha); +else + s.add_where(tab.alpha.is_null()); +``` + +## tvin() +`tvin()` is a free function that can be used with `std::string` and build-in types like `int` or `float` or `bool`. `tvin` stands for Trivial Value Is NULL. It is used in combination with `operator==()`, `operator!=()` and `operator=()`. These operators will behave the way they should, e.g. + +```C++ +select(all_of(tab)).from(tab).where(tab.alpha == sqlpp::tvin(a)); +``` + +This will evaluate to + +```SQL +SELECT tab.* FROM tab WHERE alpha = 7; +-- OR +SELECT tab.* FROM tab WHERE alpha IS NULL; +``` + +Similar with insert: + +```C++ +insert_into(tab).set(tab.alpha = sqlpp::tvin(a)); +``` + +This will evaluate to + +```SQL +INSERT INTO tab (alpha) VALUES(7); +-- OR +INSERT INTO tab (alpha) VALUES(NULL); +``` + +## Using Column Type +Like to accessing values in select results, setting values can be controlled via the column type: + +```C++ +struct Alpha +{ + struct _name_t; + using _traits = sqlpp::make_traits; +}; +``` +With this tag, you do not need to use `tvin()` for operators `=`, `==`, `!=`. It is used automatically. It translates operator `!` into `IS NULL`. + +**Hint**: Please be aware that trivial_value_is_null will not work with parameters in prepared statements. \ No newline at end of file diff --git a/docs/New-Features.md b/docs/New-Features.md new file mode 100644 index 00000000..0d28b414 --- /dev/null +++ b/docs/New-Features.md @@ -0,0 +1,58 @@ +# New Features + +There are a bunch of new features, that are not fully documented yet. If you would like to contribute documentation, please let me know. + +## Preprocessor generator for columns/tables +You'll need boost 1.50 or greater to use this feature by niXman: + +```C++ +#include +SQLPP_DECLARE_TABLE( + (tab_person) + , + (id , int , SQLPP_AUTO_INCREMENT) + (name , varchar(255), SQLPP_NOT_NULL ) + (feature, int , SQLPP_NOT_NULL ) +) +``` + +See `examples/ppgen.hpp`. + +## Union +Unions are now supported. The arguments need to have the same names and types in their columns. + +```C++ +db(select(t.alpha).from(t).where(true) + .union_distinct(select(f.epsilon.as(t.alpha)).from(f).where(true))); +db(select(t.alpha).from(t).where(true) + .union_all(select(f.epsilon.as(t.alpha)).from(f).where(true))); +``` + +## With +sqlpp11 supports common table expressions: + +```C++ +auto x = sqlpp::cte(sqlpp::alias::x).as(select(all_of(t)).from(t)); + +db(with(x)(select(x.alpha).from(x).where(true))); +``` +## Custom Queries +This allows you to combine building blocks of sqlpp11 into custom queries, for instance a SELECT..INTO which is not supported yet. + +```C++ +// A custom (select ... into) with adjusted return type +// The first argument with a return type is the select, +// but the custom query is really an insert. Thus, we tell it so. +db(custom_query(select(all_of(t)).from(t), into(f)) + .with_result_type_of(insert_into(f))); +``` + +## Schema-qualified tables +sqlpp11 assumes that you're connection addresses one database, normally. But you can tell it about other databases using the `sqlpp::schema_t` and instantiating schema-qualified tables with it: + +```C++ +auto schema = db.attach("lorem_ipsum"); +auto s = schema_qualified_table(schema, TabSample{}).as(sqlpp::alias::x) +// s now represents "lorem_ipsum.tab_sample as x" +// s can be used like any other table in the code +``` diff --git a/docs/Prepared-Statements.md b/docs/Prepared-Statements.md new file mode 100644 index 00000000..53981104 --- /dev/null +++ b/docs/Prepared-Statements.md @@ -0,0 +1,48 @@ +# Introduction +Executing a statement in a database is typically done in two phases: First, the statement is prepared (parsed, compiled, optimized). Then, it is run against the database. Since statements often differ only in parameters, not in structure, many databases offer to store prepared statements. These prepared statements can then be executed repeatedly, typically with some parameters. + +sqlpp11 supports prepared statements. + +## Parameters +Currently there are two overloads to specify a parameter: + +```C++ +parameter(const ValueType&, const AliasProvider&); + +parameter(const NamedExpression&) +``` + +Value types are sqlpp::bigint, sqlpp::text, etc., Alias providers can be generated by using the SQLPP_ALIAS_PROVIDER macro, and named expressions are combinations of the former, e.g. columns of a table. + +For instance, you could use: +```C++ +SQLPP_ALIAS_PROVIDER(cheese); +parameter(sqlpp::bigint(), cheese); + +parameter(tab.id); +``` + +## Prepare and execute statements +insert, update, remove and select statements can be prepared by calling the `prepare()` method of a database connection object. + +```C++ +auto prepared_statement = db.prepare(some_statement); +``` + +You can now set the parameters and execute the prepared statement multiple times, e.g. + +```C++ +auto prepared_insert = db.prepare( + insert_into(tab).set( + tab.alpha = parameter(tab.alpha), + tab.beta = parameter(sqlpp::text(), cheese) + )); +for (const auto& input : input_values) +{ + prepared_insert.params.alpha = input.first; + prepared_insert.params.cheese = input.second; + db(prepared_insert); +} +``` + +Note: need nicer examples... diff --git a/docs/Remove.md b/docs/Remove.md new file mode 100644 index 00000000..c2251e74 --- /dev/null +++ b/docs/Remove.md @@ -0,0 +1,20 @@ +Since `delete` is keyword in C++ that has a different meaning than `delete` in SQL, sqlpp11 calls the method remove. There is no detailed documentation available yet, but here is an example that might help if you have read about [select statements](Select.md) already: + +```C++ +db(remove_from(tab).where(tab.alpha == tab.alpha + 3)); +``` + +## Removing using multiple tables as condition: + +```C++ +test_sqlpp::Users usr; +test_sqlpp::UsersForms usr_forms; +test_sqlpp::Forms form_; + +db(remove_from(usr_forms).using_(usr, form_, usr_forms).where( + usr_forms.iduser == usr.id + and usr.username == username + and usr_forms.idform == form_.id + and form_.name == form_name + )); +``` diff --git a/docs/Select.md b/docs/Select.md new file mode 100644 index 00000000..2dd82854 --- /dev/null +++ b/docs/Select.md @@ -0,0 +1,264 @@ +# Introduction +_This page explains select statements with a static structure. If you want to learn about constructing select statements at runtime, you should still read this page first and then move on to [dynamic select statements](Dynamic-Select.md)._ + +Lets assume we have a table representing + +```SQL +CREATE TABLE foo ( + id bigint, + name varchar(50), + hasFun bool +); +``` +(This is SQL for brevity, not C++, see [here](Tables.md) for details on how to define types representing the tables and columns you want to work with) + +Lets also assume we have an object `db` representing a connection to your [database](Database.md). + +# A Basic example +This shows how you can select some data from table and iterate over the results: +```C++ +for (const auto& row : db(select(foo.name, foo.hasFun) + .from(foo) + .where(foo.id > 17 and foo.name.like("%bar%")))) +{ + if (row.name.is_null()) + std::cerr << "name is null" << std::endl; + else + std::string name = row.name; // string-like fields are implicitly convertible to string + bool hasFun = row.hasFun; // bool fields are implicitly convertible to bool +} +``` +So, what's happening here? Lets ignore the gory details for a moment. Well, there is a select statement. +```C++ +select(foo.name, foo.hasFun) + .from(foo) + .where(foo.id > 17 and foo.name.like("%bar%")) +``` +It selects two columns `name` and `hasFun` from table `foo` for rows which match the criteria given in the where condition. That's about as close to _real_ SQL as it can get... + +The select expression is fed into the call operator of the connection object `db`. This method sends the select statement to the database and returns an object representing the results. In the case of select statements, the result object represents zero or more rows. + +One way of accessing the rows is to iterate over them in a range-based for loop. + +```C++ +for (const auto& row : ...) +``` +Ok, so the variable row is an object that represents a single result row. You really want to use `auto` here, because you don't want to write down the actual type. Trust me. But the wonderful thing about the `row` object is that it has appropriately named and typed members representing the columns you selected. This is one of the utterly cool parts of this library. + +# The Select Statement +## Select +The `select` method takes zero or more named expression arguments. + +Named expressions are expressions with a name. No surprise there. But what kind of expressions have a name? Table columns, for instance. In our example, that would be `foo.id`, `foo.name` and `foo.hasFun`. Most [function](Functions.md) calls also result in named expressions, like `count(foo.id)`. + +So what about unnamed expressions? Results of binary operators like `(foo.id + 17) * 4` have no name. But you can give them a name using the `as(alias)` method. The easiest way is to use a named expression as alias, for instance `((foo.id + 17) * 4).as(foo.id)`, e.g. + +```C++ +for (const auto& row : db(select(((foo.id + 17) * 4).as(foo.id)).from(tab))) +{ + std::cout << row.id << std::endl; +} +``` + +Another option is to define an alias like this: + +```C++ +SQLPP_ALIAS_PROVIDER(total); +for (const auto& row : db(select(sum(id).as(total)).as(foo.id)).from(tab))) +{ + std::cout << row.total << std::endl; +} +``` +Using aliases also comes in handy when you join tables and have several columns of the same name, because no two named expressions in a select must have the same name. So if you want to do something like + +```C++ +select(foo.id, bar.id); // compile error +``` + +One of the columns needs an alias (or you use multi-columns as shown below). +```C++ +SQLPP_ALIAS_PROVIDER(barId); +select(foo.id, bar.id.as(barId)); +``` +### Select Columns +All examples above called the `select()` function with one or more arguments, but `select()` can also be called with no arguments. In that case, the selected columns have to be added afterwards + +```C++ +sqlpp::select().columns(foo.id, foo.name); +``` + +See also [dynamic select statements](Dynamic-Select.md). + +### Select Flags +The following flags are currently supported: + +* sqlpp::all +* sqlpp::distinct + +Flags are added via the `flags()` method: + +```C++ +sqlpp::select().flags(sqlpp::all).columns(foo.id, foo.name); +``` + +or + +```C++ +select(foo.id, foo.name).flags(sqlpp::all); +``` + +The latter is shorter than the former, but the former is closer to SQL syntax and probably easier to read. + +### Sub-Select +A select statement with one column also is named expression. This means you can use one select as a sub-select column of another select. For example: +``` +for (const auto& row : db( + select(all_of(foo), + select(sum(bar.value)).from(bar).where(bar.id > foo.id)) + .from(foo))) +{ + int x = row.id; + int a = row.sum; + } +``` +The name of the sub select is the name of the one column. If required, you can rename it using `as()`, as usual. + +### Select All Columns +Statements like `SELECT * from foo` is used pretty often in SQL. sqlpp11 offers something similar: + +```C++ +select(all_of(foo)); +``` +### Multi-Columns +Sometimes, when you join tables `foo`, `bar` and `baz`, you might want to select several columns of the same name. As shown above, you could use aliases to resolve name clashes. Another option is to group columns together in multi-columns. Here is an example: + +``` +SQLPP_ALIAS_PROVIDER_GENERATOR(left); +for(const auto& row : db( + select(foo.id, + multi_column(left, foo.id, foo.name, foo.hasFun), + multi_column(foo, all_of(foo))) + .from(foo))) +{ + std::cerr << "row.left.id: " << row.left.id + << ", row.left.name: " << row.left.name + << ", row.left.hasFun: " << row.left.hasFun << std::endl; + std::cerr << "row.foo.id: " << row.foo.id + << ", row.foo.name: " << row.foo.name + << ", row.foo.hasFun: " << row.foo.hasFun << std::endl; +}; +``` +That might not be the most creative example in the world, but it shows how to use multi-columns. The first argument is an alias provider. In the cases shown above, the alias provider `left` is created using the `SQLPP_ALIAS_PROVIDER` macro. Tables also have a name and can provide an alias. + +In the result rows, the multi-columns are accessed by their name. Their members in turn are accessed by their names. + +## From +The `from` method expects one argument. The following subsections expand on the types of valid arguments: +* tables +* tables with an alias (via the `as` method) +* sub-selects with an alias +* joins + +### Tables +This is the most simple case. +```C++ +select(all_of(foo)).from(foo); +``` + +### Aliased Tables +Table aliases are useful in self-joins. +```C++ +SQLPP_ALIAS_PROVIDER(left); +SQLPP_ALIAS_PROVIDER(right); +auto l = foo.as(left); +auto r = foo.as(right); +select(all_of(l)).from(l.join(r).on(l.x == r.y)); +``` +Aliased tables might also be used to increase the readability of generated SQL code, for instance if you have very long table names. + +### Aliased Sub-Select +A select can be used as a pseudo table in another select. You just have to give it a name. +```C++ +SQLPP_ALIAS_PROVIDER(sub); +auto sub_select = select(all_of(foo)).from(foo).as(sub); +``` +The variable `sub_select` can be used as a table now. + +### Joins +You can join two tables like this: +```C++ +foo.join(bar).on(foo.id == bar.foo); +``` +If you want to join more tables, you can chain joins. +```C++ +foo.join(bar).on(foo.id == bar.foo).left_outer_join(baz).on(bar.id == baz.ref); +``` +_Hint_: Omitting the call to `on` will result in mildly horrible error messages... + +## Where +The where condition can be set via the `where` method, which takes a boolean expression argument, for instance: +```C++ +select(all_of(foo)).from(foo).where(foo.id != 17 and foo.name.like("%cake")); +``` + +## Group By +The method `group_by` takes one or more expression arguments, for instance: +```C++ +select(all_of(foo)).from(foo).group_by(foo.name); +``` + +## Having +The having condition can be set via the `having` method, just like the `where` method. + +## Order By +The `order_by` method takes one of more order expression, which are normal expression adorned with `.asc()` or `.desc()`, e.g. +```C++ +select(all_of(foo)).from(foo).order_by(foo.name.asc()); +``` + +## Limit And Offset +The methods `limit` and `offset` take a size_t argument, for instance: +```C++ +select(all_of(foo)).from(foo).limit(10u).offset(20u); +``` +## For Update +The `for_update` method modifies the query with a simplified "FOR UPDATE" clause without columns. +```C++ +select(all_of(foo)).from(foo).where(foo.id != 17).for_update(); +``` + +# Running The Statement +OK, so now we know how to create a select statement. But the statement does not really do anything unless we hand it over to the database: +```C++ +db(select(all_of(foo)).from(foo)); +``` +This call returns a result object of a pretty complex type. Thus, you would normally want to use `auto`: + +```C++ +auto result = db(select(all_of(foo)).from(foo)); +``` + +# Accessing The Results +The `result` object created by executing a `select` query is a container of result rows. + +## Range-based For Loops +Not surprisingly, you can iterate over the rows using a range-based for-loop like this: +```C++ +for (const auto& row : db(select(all_of(foo)).from(foo))) +{ + std::cerr << row.id << std::endl; + std::cerr << row.name << std::endl; +} +``` +Lovely, isn't it? The row objects have types specifically tailored for the select query you wrote. You can access their member by name, and these members have the expected type. + +## Function-based Access +If for some reason, you don't want to use range-based for-loops, you can use `front()` and `pop_front()` on the result, like this: +```C++ +while(!result.empty()) +{ + const auto& row = result.front(); + std::cerr << row.id << std::endl; + std::cerr << row.name << std::endl; + result.pop_front(); +} diff --git a/docs/Tables.md b/docs/Tables.md new file mode 100644 index 00000000..602e9ea6 --- /dev/null +++ b/docs/Tables.md @@ -0,0 +1,128 @@ +This page will tell you about how to define tables for sqlpp11, once I get around to writing it. Until then, here is an example, taken from [here](https://github.com/rbock/sqlpp11/blob/master/tests/Sample.h): + +```C++ +namespace TabSample_ +{ + struct Alpha + { + struct _name_t + { + static constexpr const char* _get_name() { return "alpha"; } + template + struct _member_t + { + T alpha; + }; + }; + using _value_type = sqlpp::bigint; + struct _column_type + { + using _must_not_insert = std::true_type; + using _must_not_update = std::true_type; + using _can_be_null = std::true_type; + using _trivial_value_is_null = std::true_type; + using _foreign_key = decltype(TabFoo::omega); + }; + }; + + struct Beta + { + struct _name_t + { + static constexpr const char* _get_name() { return "beta"; } + template + struct _member_t + { + T beta; + }; + }; + using _value_type = sqlpp::varchar; + struct _column_type + { + using _can_be_null = std::true_type; + using _trivial_value_is_null = std::true_type; + using _must_not_update = std::true_type; + }; + }; + + struct Gamma + { + struct _name_t + { + static constexpr const char* _get_name() { return "gamma"; } + template + struct _member_t + { + T gamma; + }; + }; + using _value_type = sqlpp::boolean; + struct _column_type + { + using _require_insert = std::true_type; + }; + }; +} + +struct TabSample: sqlpp::table_base_t< + TabSample, + TabSample_::Alpha, + TabSample_::Beta, + TabSample_::Gamma + > +{ + using _value_type = sqlpp::no_value_t; + struct _name_t + { + static constexpr const char* _get_name() { return "tab_sample"; } + template + struct _member_t + { + T tabSample; + }; + }; + template + void serialize_impl(std::ostream& os, Db& db) const + { + os << _name_t::_get_name(); + } +}; +``` +Not too complex, I hope? + +Not lean enough to be fun writing though, right? A sample code generator (DDL -> C++) written in python can be found [here](https://github.com/rbock/sqlpp11/blob/master/scripts/ddl2cpp). + +## Names with reserved SQL keywords +In case the SQL entity uses a reserved keyword it is required to escape the name using the target's RDBMS rule. For example: + +```SQL +SELECT "order" FROM orders; +``` +Here the column order is the same as the reserved keyword ORDER from the "ORDER BY" SQL clause. As such in for example PostgreSQL you need to put the name in quotes. Pay attention that the rules are RDBMS specific and that some RDBMS then consider the name case sensitive and that the select may fail due to incorrect case of the entity name. To achieve this you need to amend the column literal. For example: + +```C++ +namespace TabSample_ +{ + struct Order + { + struct _name_t + { + static constexpr const char* _get_name() { return "\"order\""; } + template + struct _member_t + { + T order; + }; + }; + using _value_type = sqlpp::bigint; + struct _column_type + { + using _must_not_insert = std::true_type; + using _must_not_update = std::true_type; + using _can_be_null = std::true_type; + using _trivial_value_is_null = std::true_type; + using _foreign_key = decltype(TabFoo::omega); + }; + }; +} +``` diff --git a/docs/Update.md b/docs/Update.md new file mode 100644 index 00000000..bd4c10a8 --- /dev/null +++ b/docs/Update.md @@ -0,0 +1,5 @@ +The detailed documentation is still missing here, but here is an example that might help if you have read about [select statements](Select.md). + +```C++ +db(update(tab).set(tab.gamma = false).where(tab.alpha.in(1))); +```