Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/orm/qormglobal.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2019-2022 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2022 sequality software engineering e.U. <office@sequality.at>
* Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
*
* This file is part of QtOrm library.
*
Expand Down Expand Up @@ -108,7 +108,8 @@ namespace QOrm
Read,
Update,
Delete,
Merge
Merge,
Count
};
extern Q_ORM_EXPORT QDebug operator<<(QDebug dbg, Operation operation);

Expand Down
24 changes: 19 additions & 5 deletions src/orm/qormquerybuilder.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2019 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019 sequality software engineering e.U. <office@sequality.at>
* Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
*
* This file is part of QtOrm library.
*
Expand Down Expand Up @@ -119,11 +119,11 @@ namespace QOrmPrivate
{
}

QueryBuilderHelper::QueryBuilderHelper(QueryBuilderHelper&&) = default;
QueryBuilderHelper::QueryBuilderHelper(QueryBuilderHelper&&) noexcept = default;

QueryBuilderHelper::~QueryBuilderHelper() = default;

QueryBuilderHelper& QueryBuilderHelper::operator=(QueryBuilderHelper&&) = default;
QueryBuilderHelper& QueryBuilderHelper::operator=(QueryBuilderHelper&&) noexcept = default;

void QueryBuilderHelper::setInstance(const QMetaObject& qMetaObject, QObject* instance)
{
Expand Down Expand Up @@ -169,7 +169,8 @@ namespace QOrmPrivate

return QOrmQuery{operation, *d->m_relation.mapping(), d->m_entityInstance};
}
else if (operation == QOrm::Operation::Read || operation == QOrm::Operation::Delete)
else if (operation == QOrm::Operation::Read || operation == QOrm::Operation::Delete ||
operation == QOrm::Operation::Count)
{
FoldedFilters filters = foldFilters(d->m_relation, d->m_filters);
QOrmQuery query = QOrmQuery{operation,
Expand All @@ -196,6 +197,19 @@ namespace QOrmPrivate
{
return d->m_session->execute(build(QOrm::Operation::Delete, QOrm::QueryFlags::None));
}

QOrmQueryResult<int> QueryBuilderHelper::count() const
{
QOrmQueryResult<QObject> result =
d->m_session->execute(build(QOrm::Operation::Count, QOrm::QueryFlags::None));

if (result.hasError())
{
return QOrmQueryResult<int>{result.error(), result.numRowsAffected()};
}

return QOrmQueryResult<int>{result.numRowsAffected()};
}
} // namespace QOrmPrivate

QT_END_NAMESPACE
15 changes: 8 additions & 7 deletions src/orm/qormquerybuilder.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2019 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019 sequality software engineering e.U. <office@sequality.at>
* Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
*
* This file is part of QtOrm library.
*
Expand Down Expand Up @@ -51,11 +51,11 @@ namespace QOrmPrivate
public:
QueryBuilderHelper(QOrmSession* session, const QOrmRelation& relation);
QueryBuilderHelper(const QueryBuilderHelper&) = delete;
QueryBuilderHelper(QueryBuilderHelper&&);
QueryBuilderHelper(QueryBuilderHelper&&) noexcept;
~QueryBuilderHelper();

QueryBuilderHelper& operator=(const QueryBuilderHelper&) = delete;
QueryBuilderHelper& operator=(QueryBuilderHelper&&);
QueryBuilderHelper& operator=(QueryBuilderHelper&&) noexcept;

void setInstance(const QMetaObject& qMetaObject, QObject* instance);
void addFilter(const QOrmFilter& filter);
Expand All @@ -66,10 +66,9 @@ namespace QOrmPrivate
Q_REQUIRED_RESULT
QOrmQuery build(QOrm::Operation operation, QOrm::QueryFlags flags) const;

Q_REQUIRED_RESULT
QOrmQueryResult<QObject> select(QOrm::QueryFlags flags) const;

[[nodiscard]] QOrmQueryResult<QObject> select(QOrm::QueryFlags flags) const;
[[nodiscard]] QOrmQueryResult<QObject> remove() const;
[[nodiscard]] QOrmQueryResult<int> count() const;

private:
std::unique_ptr<QueryBuilderHelperPrivate> d;
Expand Down Expand Up @@ -150,6 +149,8 @@ class QOrmQueryBuilder

[[nodiscard]] QOrmQueryResult<Projection> remove() { return m_helper.remove(); }

[[nodiscard]] QOrmQueryResult<int> count() const { return m_helper.count(); }

Q_REQUIRED_RESULT
QOrmQuery build(QOrm::Operation operation, QOrm::QueryFlags flags = QOrm::QueryFlags::None) const { return m_helper.build(operation, flags); }

Expand Down
27 changes: 25 additions & 2 deletions src/orm/qormqueryresult.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2019 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019 sequality software engineering e.U. <office@sequality.at>
* Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
*
* This file is part of QtOrm library.
*
Expand Down Expand Up @@ -240,6 +240,29 @@ class QOrmQueryResult<void> : public QtOrmPrivate::QOrmQueryResultBase<void>
}
};

template<>
class QOrmQueryResult<int> : public QtOrmPrivate::QOrmQueryResultBase<int>
{
using Base = QOrmQueryResultBase<int>;

public:
explicit QOrmQueryResult(int value)
: Base{QOrmError{QOrm::ErrorType::None, QString{}}, QVariant{}, 0}
, m_value{value}
{
}

explicit QOrmQueryResult(const QOrmError& error, int numRowsAffected)
: Base{error, QVariant{}, numRowsAffected}
{
}

[[nodiscard]] int value() const { return m_value; }

private:
int m_value{0};
};

QT_END_NAMESPACE

#endif
28 changes: 26 additions & 2 deletions src/orm/qormsqliteprovider.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2020-2024 Dmitriy Purgin <dpurgin@gmail.com>
* Copyright (C) 2019-2024 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2024 sequality software engineering e.U. <office@sequality.at>
* Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
*
* This file is part of QtOrm library.
*
Expand Down Expand Up @@ -103,6 +103,7 @@ class QOrmSqliteProviderPrivate
QOrmQueryResult<QObject> merge(const QOrmQuery& query);
QOrmQueryResult<QObject> remove(const QOrmQuery& query,
QOrmEntityInstanceCache& entityInstanceCache);
QOrmQueryResult<QObject> count(const QOrmQuery& query);

[[nodiscard]] bool foreignKeysEnabled();
[[nodiscard]] QOrmError setForeignKeysEnabled(bool enabled);
Expand Down Expand Up @@ -981,6 +982,26 @@ QOrmQueryResult<QObject> QOrmSqliteProviderPrivate::remove(
return QOrmQueryResult<QObject>{resultSet, sqlQuery.numRowsAffected()};
}

QOrmQueryResult<QObject> QOrmSqliteProviderPrivate::count(const QOrmQuery& query)
{
Q_ASSERT(query.operation() == QOrm::Operation::Count);

auto [statement, boundParameters] = m_statementGenerator.generate(query);

QSqlQuery sqlQuery = prepareAndExecute(statement, boundParameters);

if (sqlQuery.lastError().type() != QSqlError::NoError || !sqlQuery.next())
{
return QOrmQueryResult<QObject>{QOrmError{QOrm::ErrorType::Provider,
sqlQuery.lastError().text()},
sqlQuery.numRowsAffected()};
}

int count = sqlQuery.value(0).toInt();

return QOrmQueryResult{QVector<QObject*>{}, count};
}

bool QOrmSqliteProviderPrivate::foreignKeysEnabled()
{
QSqlQuery query{m_database};
Expand Down Expand Up @@ -1208,6 +1229,9 @@ QOrmQueryResult<QObject> QOrmSqliteProvider::execute(const QOrmQuery& query,
case QOrm::Operation::Delete:
return d->remove(query, entityInstanceCache);

case QOrm::Operation::Count:
return d->count(query);

case QOrm::Operation::Merge:
Q_ORM_UNEXPECTED_STATE;
}
Expand Down
17 changes: 13 additions & 4 deletions src/orm/qormsqlitestatementgenerator_p.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2019-2022 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2022 sequality software engineering e.U. <office@sequality.at>
* Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
*
* This file is part of QtOrm library.
*
Expand Down Expand Up @@ -102,6 +102,7 @@ QString QOrmSqliteStatementGenerator::generate(const QOrmQuery& query, QVariantM
boundParameters);

case QOrm::Operation::Read:
case QOrm::Operation::Count:
return generateSelectStatement(query, boundParameters);

case QOrm::Operation::Delete:
Expand Down Expand Up @@ -211,9 +212,17 @@ QString QOrmSqliteStatementGenerator::generateUpdateStatement(const QOrmMetadata
QString QOrmSqliteStatementGenerator::generateSelectStatement(const QOrmQuery& query,
QVariantMap& boundParameters)
{
Q_ASSERT(query.operation() == QOrm::Operation::Read);
Q_ASSERT(query.operation() == QOrm::Operation::Read ||
query.operation() == QOrm::Operation::Count);

QStringList parts = {"SELECT *", generateFromClause(query.relation(), boundParameters)};
QString projection = query.operation() == QOrm::Operation::Read
? QString{"*"}
: QString{"COUNT(*) AS %1"}.arg(escapeIdentifier("count"));
;

QStringList parts = {"SELECT",
projection,
generateFromClause(query.relation(), boundParameters)};

if (query.expressionFilter().has_value())
parts += generateWhereClause(*query.expressionFilter(), boundParameters);
Expand Down
36 changes: 33 additions & 3 deletions tests/auto/qormsession/tst_ormsession.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2020-2021 Dmitriy Purgin <dpurgin@gmail.com>
* Copyright (C) 2019-2022 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2022 sequality software engineering e.U. <office@sequality.at>
* Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
*
* This file is part of QtOrm library.
*
Expand Down Expand Up @@ -65,6 +65,9 @@ private slots:
void testSelectWithLimitOffset();
void testSelectWithOverwriteCachedInstances();

void testCount();
void testCountWithFilter();

void testMergeFailsWithInconsistentReferences();
void testMergeOfExistingUncachedEntitiesWithExplicitIdsUpdates();
void testMergeNewEntitiesNoAutogeneratedIds();
Expand Down Expand Up @@ -543,6 +546,33 @@ void SqliteSessionTest::testSelectWithOverwriteCachedInstances()
QVERIFY(!session.entityInstanceCache()->isModified(upperAustria));
}

void SqliteSessionTest::testCount()
{
QOrmSession session;
session.merge(new Province(QString::fromUtf8("Oberösterreich")),
new Province(QString::fromUtf8("Niederösterreich")),
new Province(QString::fromUtf8("Salzburg")));

auto result = session.from<Province>().count();

QCOMPARE(result.error().type(), QOrm::ErrorType::None);
QCOMPARE(result.value(), 3);
}

void SqliteSessionTest::testCountWithFilter()
{
QOrmSession session;
session.merge(new Province(QString::fromUtf8("Oberösterreich")),
new Province(QString::fromUtf8("Niederösterreich")),
new Province(QString::fromUtf8("Salzburg")));

auto result =
session.from<Province>().filter(Q_ORM_CLASS_PROPERTY(name).contains("österreich")).count();

QCOMPARE(result.error().type(), QOrm::ErrorType::None);
QCOMPARE(result.value(), 2);
}

void SqliteSessionTest::testMergeFailsWithInconsistentReferences()
{
QOrmSession session;
Expand Down Expand Up @@ -953,7 +983,7 @@ void SqliteSessionTest::testRemoveInstance()
Province* upperAustria = new Province{QString::fromUtf8("Oberösterreich")};
QVERIFY(session.merge(upperAustria));
QVERIFY(session.remove(upperAustria));
QVERIFY(session.from<Province>().select().toVector().empty());
QVERIFY(session.from<Province>().select().toVector().empty());
}

void SqliteSessionTest::testRemoveWithFilter()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2020-2021 Dmitriy Purgin <dpurgin@gmail.com>
* Copyright (C) 2019-2022 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2022 sequality software engineering e.U. <office@sequality.at>
* Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
* Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
*
* This file is part of QtOrm library.
*
Expand Down Expand Up @@ -78,6 +78,8 @@ private slots:
void testSelectWithNamespace();
void testLimitOffset();
void testLimitOffset_data();

void testCount();
};

void SqliteStatementGenerator::init()
Expand Down Expand Up @@ -550,6 +552,28 @@ void SqliteStatementGenerator::testLimitOffset_data()
<< QVariant{} << QVariant{42} << "LIMIT :limit OFFSET :offset";
}

void SqliteStatementGenerator::testCount()
{
QOrmMetadataCache cache;

QOrmRelation relation{cache.get<Town>()};
QOrmMetadata projection{cache.get<Town>()};

QOrmQuery query{QOrm::Operation::Count,
relation,
projection,
std::nullopt,
std::nullopt,
{},
QOrm::QueryFlags::None};
QMap<QString, QVariant> boundParameters;
QString actual = QOrmSqliteStatementGenerator{}.generate(query, boundParameters).simplified();
QString expected{R"(SELECT COUNT(*) AS "count" FROM "Town")"};

QCOMPARE(actual, expected);
QVERIFY(boundParameters.empty());
}

QTEST_APPLESS_MAIN(SqliteStatementGenerator)

#include "tst_sqlitestatementgenerator.moc"
Loading