added QueryBuilder::updateOrInsert()

- added tests
 - updated docs
This commit is contained in:
silverqx
2022-07-26 13:17:46 +02:00
parent 8f726a0878
commit c0e91a18e1
4 changed files with 154 additions and 0 deletions
+12
View File
@@ -717,6 +717,18 @@ In addition to inserting records into the database, the query builder can also u
An `update` and `delete` are affecting statements, so they return `std::tuple<int, QSqlQuery>`.
:::
#### Update Or Insert
Sometimes you may want to update an existing record in the database or create it if no matching record exists. In this scenario, the `updateOrInsert` method may be used. The `updateOrInsert` method accepts two arguments: a vector of conditions by which to find the record, and a vector of column and value pairs indicating the columns to be updated.
The `updateOrInsert` method will attempt to locate a matching database record using the first argument's column and value pairs. If the record exists, it will be updated with the values in the second argument. If the record can not be found, a new record will be inserted with the merged attributes of both arguments:
DB::table("users")
->updateOrInsert(
{{"email", "john@example.com"}, {"name", "John"}},
{{"votes", 2}}
);
### Increment & Decrement {#increment-and-decrement}
The query builder also provides convenient methods for incrementing or decrementing the value of a given column. Both of these methods accept at least one argument: the column to modify. A second argument may be provided to specify the amount by which the column should be incremented or decremented:
+4
View File
@@ -119,6 +119,10 @@ namespace Orm::Query
/*! Update records in the database. */
std::tuple<int, QSqlQuery>
update(const QVector<UpdateItem> &values);
/*! Insert or update a record matching the attributes, and fill it with values. */
std::tuple<int, std::optional<QSqlQuery>>
updateOrInsert(const QVector<WhereItem> &attributes,
const QVector<UpdateItem> &values);
/*! Delete records from the database. */
std::tuple<int, QSqlQuery> deleteRow();
+33
View File
@@ -222,6 +222,39 @@ Builder::update(const QVector<UpdateItem> &values)
values)));
}
namespace
{
/*! Merge attributes and values for the updateOrInsert() method. */
const auto mergeValuesForInsert = [](const QVector<WhereItem> &attributes,
const QVector<UpdateItem> &values)
{
QVariantMap result;
for (const auto &attribute : attributes)
/* Can not contain expression in the column name, throws
the std::bad_variant_access exception. */
result.insert(std::get<QString>(attribute.column), attribute.value);
for (const auto &value : values)
result.insert(value.column, value.value);
return result;
};
} // namespace
std::tuple<int, std::optional<QSqlQuery>>
Builder::updateOrInsert(const QVector<WhereItem> &attributes,
const QVector<UpdateItem> &values)
{
if (!where(attributes).exists())
return {-1, insert(mergeValuesForInsert(attributes, values))};
if (values.isEmpty())
return {0, std::nullopt};
return limit(1).update(values);
}
std::tuple<int, QSqlQuery> Builder::deleteRow()
{
return remove();
@@ -44,6 +44,9 @@ private Q_SLOTS:
void implode_EmptyResult() const;
void implode_QualifiedColumnOrKey() const;
void updateOrInsert() const;
void updateOrInsert_EmptyValues() const;
void count() const;
void count_Distinct() const;
void min_Aggregate() const;
@@ -399,6 +402,108 @@ void tst_QueryBuilder::implode_QualifiedColumnOrKey() const
}
}
void tst_QueryBuilder::updateOrInsert() const
{
QFETCH_GLOBAL(QString, connection);
// update
{
auto [affected, query] = createQuery(connection)->from("user_phones").
updateOrInsert(
{{"user_id", 3}, {"number", "905111999"}},
{{"number", "905333999"}});
QVERIFY(query);
QVERIFY(!query->isValid() && !query->isSelect() && query->isActive());
QCOMPARE(affected, 1);
// validate
auto count = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.count();
QCOMPARE(count, 1);
auto number = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.value("number");
QCOMPARE(number, QString("905333999"));
}
// remove
{
auto [affected, query] = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.remove();
QVERIFY(!query.isValid() && !query.isSelect() && query.isActive());
QCOMPARE(affected, 1);
// validate
QVERIFY(createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.doesntExist());
}
// insert (also restore db)
{
auto [affected, query] = createQuery(connection)->from("user_phones").
updateOrInsert(
{{"user_id", 3}, {"number", "905000000"}},
{{"number", "905111999"}});
QVERIFY(query);
QVERIFY(!query->isValid() && !query->isSelect() && query->isActive());
QCOMPARE(affected, -1);
// validate
auto count = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.count();
QCOMPARE(count, 1);
auto number = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.value("number");
QCOMPARE(number, QString("905111999"));
}
}
void tst_QueryBuilder::updateOrInsert_EmptyValues() const
{
QFETCH_GLOBAL(QString, connection);
// validate
auto count = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.count();
QCOMPARE(count, 1);
// Get a current ID, it can not change
auto expectedId = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.value(ID).value<quint64>();
QVERIFY(expectedId >= 3);
// main operation
auto [affected, query] = createQuery(connection)->from("user_phones").
updateOrInsert(
{{"user_id", 3}, {"number", "905111999"}},
{});
QVERIFY(!query);
QCOMPARE(affected, 0);
// validate
count = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.count();
QCOMPARE(count, 1);
// ID is still the same
auto id = createQuery(connection)->from("user_phones")
.whereEq("user_id", 3)
.value(ID).value<quint64>();
QCOMPARE(id, expectedId);
}
void tst_QueryBuilder::count() const
{
QFETCH_GLOBAL(QString, connection);