Skip to content

Commit

Permalink
Update the documentation for NULL #568
Browse files Browse the repository at this point in the history
  • Loading branch information
rbock committed Apr 13, 2024
1 parent 637afd0 commit ac9d0b4
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 69 deletions.
87 changes: 21 additions & 66 deletions docs/NULL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,86 +15,41 @@ 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.
The library tries to make the usage of NULL reasonably safe in C++:

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.
# Assigning NULL in INSERT or UPDATE
In assignments in `insert` or `update` statements, NULL can be represented by `sqlpp::null`.

## 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
>;
db(insert_into(t).set(t.gamma = sqlpp::null);
```
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.
Note that you cannot assign `sqlpp::null` to columns that are not nullable.
# Handling NULL in statements
When adding potential NULL values to a statement, you have two options:
# Comparing to NULL
Columns that can be NULL can be compared to NULL using the `is_null` and `is_not_null` member functions.
## 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());
const auto s = select(all_of(tab)).from(tab).where(tab.alpha == 17 and tab.beta.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
# 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.

```SQL
INSERT INTO tab (alpha) VALUES(7);
-- OR
INSERT INTO tab (alpha) VALUES(NULL);
```
You can check if a field is NULL by calling the `is_null()` method.

## Using Column Type
Like to accessing values in select results, setting values can be controlled via the column type:
The value can be accessed by the `.value()` method. It will return a default constructed value in case `is_null() == true`.

```C++
struct Alpha
for (const auto& row : db(select(all_of(tab)).from(tab).unconditionally()))
{
struct _name_t;
using _traits = sqlpp::make_traits<sqlpp::bigint,
// ...
sqlpp::tag::trivial_value_is_null>;
};
if (not row.alpha.is_null())
{
const auto a = row.alpha.value();
}
}
```
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.
# Future:
We might switch to `std::optional` to represent nullable columns or values.

3 changes: 0 additions & 3 deletions include/sqlpp11/result_field_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,9 @@

namespace sqlpp
{
SQLPP_PORTABLE_STATIC_ASSERT(assert_result_field_value_is_safe_t, "result field value needs to be checked for NULL");

template <typename Db, typename FieldSpec, typename StorageType = typename value_type_of<FieldSpec>::_cpp_value_type>
struct result_field_base
{
using _db_t = Db;
using _field_spec_t = FieldSpec;
using _alias_t = typename FieldSpec::_alias_t;
using _cpp_value_type = typename value_type_of<FieldSpec>::_cpp_value_type;
Expand Down
4 changes: 4 additions & 0 deletions tests/core/usage/Interpret.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,11 @@ int Interpret(int, char* [])
// select alias
serialize(select(t.alpha).from(t).where(t.beta > "kaesekuchen").as(t.gamma), printer).str();

// Comparison to null
static_assert(sqlpp::can_be_null_t<decltype(t.alpha)>::value, "expected alpha can be null");
static_assert(not sqlpp::can_be_null_t<decltype(f.delta)>::value, "expected delta cannot be null");
serialize(t.alpha.is_null(), printer).str();
serialize(f.delta.is_null(), printer).str();

// join
serialize(t.inner_join(t.as(t.alpha)).on(t.beta == t.as(t.alpha).beta), printer).str();
Expand Down

0 comments on commit ac9d0b4

Please sign in to comment.