|
Rex Dieter |
adf30a |
From 9698d589e4c2b489f406fe1a823d4bb42c322f71 Mon Sep 17 00:00:00 2001
|
|
Rex Dieter |
adf30a |
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
|
|
Rex Dieter |
adf30a |
Date: Fri, 5 Dec 2014 18:21:18 +0100
|
|
Rex Dieter |
adf30a |
Subject: [PATCH 21/30] Implement support for CASE...WHEN...THEN SQL statements
|
|
Rex Dieter |
adf30a |
SELECT columns
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
CASE...WHEN...THEN is a useful construct especially for aggregation
|
|
Rex Dieter |
adf30a |
queries.
|
|
Rex Dieter |
adf30a |
---
|
|
Rex Dieter |
adf30a |
server/src/storage/query.cpp | 38 ++++++++++++++++++++++++++++++
|
|
Rex Dieter |
adf30a |
server/src/storage/query.h | 19 +++++++++++++++
|
|
Rex Dieter |
adf30a |
server/src/storage/querybuilder.cpp | 30 +++++++++++++++++++++++
|
|
Rex Dieter |
adf30a |
server/src/storage/querybuilder.h | 14 +++++++++++
|
|
Rex Dieter |
adf30a |
server/tests/unittest/querybuildertest.cpp | 38 +++++++++++++++++++++++++++++-
|
|
Rex Dieter |
adf30a |
5 files changed, 138 insertions(+), 1 deletion(-)
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
diff --git a/server/src/storage/query.cpp b/server/src/storage/query.cpp
|
|
Rex Dieter |
adf30a |
index 6fb6c6e..c938ade 100644
|
|
Rex Dieter |
adf30a |
--- a/server/src/storage/query.cpp
|
|
Rex Dieter |
adf30a |
+++ b/server/src/storage/query.cpp
|
|
Rex Dieter |
adf30a |
@@ -68,3 +68,41 @@ void Query::Condition::addCondition( const Condition &condition )
|
|
Rex Dieter |
adf30a |
{
|
|
Rex Dieter |
adf30a |
mSubConditions << condition;
|
|
Rex Dieter |
adf30a |
}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+Case::Case(const Condition &when, const QString &then, const QString &elseBranch)
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ addCondition(when, then);
|
|
Rex Dieter |
adf30a |
+ setElse(elseBranch);
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+Case::Case(const QString &column, CompareOperator op, const QVariant &value, const QString &when, const QString &elseBranch)
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ addValueCondition(column, op, value, when);
|
|
Rex Dieter |
adf30a |
+ setElse(elseBranch);
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+void Case::addCondition(const Condition &when, const QString &then)
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ mWhenThen.append(qMakePair(when, then));
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+void Case::addValueCondition(const QString &column, CompareOperator op, const QVariant &value, const QString &then)
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ Condition when;
|
|
Rex Dieter |
adf30a |
+ when.addValueCondition(column, op, value);
|
|
Rex Dieter |
adf30a |
+ addCondition(when, then);
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+void Case::addColumnCondition(const QString &column, CompareOperator op, const QString &column2, const QString &then)
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ Condition when;
|
|
Rex Dieter |
adf30a |
+ when.addColumnCondition(column, op, column2);
|
|
Rex Dieter |
adf30a |
+ addCondition(when, then);
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+void Case::setElse(const QString &elseBranch)
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ mElse = elseBranch;
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
diff --git a/server/src/storage/query.h b/server/src/storage/query.h
|
|
Rex Dieter |
adf30a |
index f4f1ac0..c8f35a7 100644
|
|
Rex Dieter |
adf30a |
--- a/server/src/storage/query.h
|
|
Rex Dieter |
adf30a |
+++ b/server/src/storage/query.h
|
|
Rex Dieter |
adf30a |
@@ -130,6 +130,25 @@ class Condition
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
}; // class Condition
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+class Case
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ friend class Akonadi::Server::QueryBuilder;
|
|
Rex Dieter |
adf30a |
+ public:
|
|
Rex Dieter |
adf30a |
+ Case(const Condition &when, const QString &then, const QString &elseBranch = QString());
|
|
Rex Dieter |
adf30a |
+ Case(const QString &column, Query::CompareOperator op, const QVariant &value, const QString &when, const QString &elseBranch = QString());
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ void addCondition(const Condition &when, const QString &then;;
|
|
Rex Dieter |
adf30a |
+ void addValueCondition(const QString &column, Query::CompareOperator op, const QVariant &value, const QString &then;;
|
|
Rex Dieter |
adf30a |
+ void addColumnCondition(const QString &column, Query::CompareOperator op, const QString &column2, const QString &then;;
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ void setElse(const QString &elseBranch);
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ private:
|
|
Rex Dieter |
adf30a |
+ QVector<QPair<Condition, QString> > mWhenThen;
|
|
Rex Dieter |
adf30a |
+ QString mElse;
|
|
Rex Dieter |
adf30a |
+};
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
} // namespace Query
|
|
Rex Dieter |
adf30a |
} // namespace Server
|
|
Rex Dieter |
adf30a |
} // namespace Akonadi
|
|
Rex Dieter |
adf30a |
diff --git a/server/src/storage/querybuilder.cpp b/server/src/storage/querybuilder.cpp
|
|
Rex Dieter |
adf30a |
index 3017867..74ed2da 100644
|
|
Rex Dieter |
adf30a |
--- a/server/src/storage/querybuilder.cpp
|
|
Rex Dieter |
adf30a |
+++ b/server/src/storage/querybuilder.cpp
|
|
Rex Dieter |
adf30a |
@@ -457,11 +457,27 @@ void QueryBuilder::addColumn( const QString &col )
|
|
Rex Dieter |
adf30a |
mColumns << col;
|
|
Rex Dieter |
adf30a |
}
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
+void QueryBuilder::addColumn( const Query::Case &caseStmt )
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ QString query;
|
|
Rex Dieter |
adf30a |
+ buildCaseStatement(&query, caseStmt);
|
|
Rex Dieter |
adf30a |
+ mColumns.append(query);
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
void QueryBuilder::addAggregation( const QString &col, const QString &aggregate )
|
|
Rex Dieter |
adf30a |
{
|
|
Rex Dieter |
adf30a |
mColumns.append( aggregate + QLatin1Char( '(' ) + col + QLatin1Char( ')' ) );
|
|
Rex Dieter |
adf30a |
}
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
+void QueryBuilder::addAggregation(const Query::Case &caseStmt, const QString &aggregate)
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ QString query(aggregate + QLatin1Char('('));
|
|
Rex Dieter |
adf30a |
+ buildCaseStatement(&query, caseStmt);
|
|
Rex Dieter |
adf30a |
+ query += QLatin1Char(')');
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ mColumns.append(query);
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
void QueryBuilder::bindValue( QString *query, const QVariant &value )
|
|
Rex Dieter |
adf30a |
{
|
|
Rex Dieter |
adf30a |
mBindValues << value;
|
|
Rex Dieter |
adf30a |
@@ -510,6 +526,20 @@ void QueryBuilder::buildWhereCondition( QString *query, const Query::Condition &
|
|
Rex Dieter |
adf30a |
}
|
|
Rex Dieter |
adf30a |
}
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
+void QueryBuilder::buildCaseStatement(QString *query, const Query::Case &caseStmt)
|
|
Rex Dieter |
adf30a |
+{
|
|
Rex Dieter |
adf30a |
+ *query += QLatin1String("CASE ");
|
|
Rex Dieter |
adf30a |
+ for (const auto whenThen : caseStmt.mWhenThen) {
|
|
Rex Dieter |
adf30a |
+ *query += QLatin1String("WHEN ");
|
|
Rex Dieter |
adf30a |
+ buildWhereCondition(query, whenThen.first); // When
|
|
Rex Dieter |
adf30a |
+ *query += QLatin1String(" THEN ") + whenThen.second; // then
|
|
Rex Dieter |
adf30a |
+ }
|
|
Rex Dieter |
adf30a |
+ if (!caseStmt.mElse.isEmpty()) {
|
|
Rex Dieter |
adf30a |
+ *query += QLatin1String(" ELSE ") + caseStmt.mElse;
|
|
Rex Dieter |
adf30a |
+ }
|
|
Rex Dieter |
adf30a |
+ *query += QLatin1String(" END");
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
void QueryBuilder::setSubQueryMode( Query::LogicOperator op, ConditionType type )
|
|
Rex Dieter |
adf30a |
{
|
|
Rex Dieter |
adf30a |
Q_ASSERT( type == WhereCondition || ( type == HavingCondition && mType == Select ) );
|
|
Rex Dieter |
adf30a |
diff --git a/server/src/storage/querybuilder.h b/server/src/storage/querybuilder.h
|
|
Rex Dieter |
adf30a |
index df7c362..0304108 100644
|
|
Rex Dieter |
adf30a |
--- a/server/src/storage/querybuilder.h
|
|
Rex Dieter |
adf30a |
+++ b/server/src/storage/querybuilder.h
|
|
Rex Dieter |
adf30a |
@@ -123,6 +123,12 @@ class QueryBuilder
|
|
Rex Dieter |
adf30a |
void addColumn( const QString &col );
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
/**
|
|
Rex Dieter |
adf30a |
+ * Adds the given case statement to a select query.
|
|
Rex Dieter |
adf30a |
+ * @param caseStmt The case statement to add.
|
|
Rex Dieter |
adf30a |
+ */
|
|
Rex Dieter |
adf30a |
+ void addColumn( const Query::Case &caseStmt );
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ /**
|
|
Rex Dieter |
adf30a |
* Adds an aggregation statement.
|
|
Rex Dieter |
adf30a |
* @param col The column to aggregate on
|
|
Rex Dieter |
adf30a |
* @param aggregate The aggregation function.
|
|
Rex Dieter |
adf30a |
@@ -130,6 +136,13 @@ class QueryBuilder
|
|
Rex Dieter |
adf30a |
void addAggregation( const QString &col, const QString &aggregate );
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
/**
|
|
Rex Dieter |
adf30a |
+ * Adds and aggregation statement with CASE
|
|
Rex Dieter |
adf30a |
+ * @param caseStmt The case statement to aggregate on
|
|
Rex Dieter |
adf30a |
+ * @param aggregate The aggregation function.
|
|
Rex Dieter |
adf30a |
+ */
|
|
Rex Dieter |
adf30a |
+ void addAggregation( const Query::Case &caseStmt, const QString &aggregate );
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ /**
|
|
Rex Dieter |
adf30a |
Add a WHERE or HAVING condition which compares a column with a given value.
|
|
Rex Dieter |
adf30a |
@param column The column that should be compared.
|
|
Rex Dieter |
adf30a |
@param op The operator used for comparison
|
|
Rex Dieter |
adf30a |
@@ -239,6 +252,7 @@ class QueryBuilder
|
|
Rex Dieter |
adf30a |
void buildQuery( QString *query );
|
|
Rex Dieter |
adf30a |
void bindValue( QString *query, const QVariant &value );
|
|
Rex Dieter |
adf30a |
void buildWhereCondition( QString *query, const Query::Condition &cond );
|
|
Rex Dieter |
adf30a |
+ void buildCaseStatement( QString *query, const Query::Case &caseStmt );
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
/**
|
|
Rex Dieter |
adf30a |
* SQLite does not support JOINs with UPDATE, so we have to convert it into
|
|
Rex Dieter |
adf30a |
diff --git a/server/tests/unittest/querybuildertest.cpp b/server/tests/unittest/querybuildertest.cpp
|
|
Rex Dieter |
adf30a |
index 92df2a2..848829d 100644
|
|
Rex Dieter |
adf30a |
--- a/server/tests/unittest/querybuildertest.cpp
|
|
Rex Dieter |
adf30a |
+++ b/server/tests/unittest/querybuildertest.cpp
|
|
Rex Dieter |
adf30a |
@@ -217,6 +217,42 @@ void QueryBuilderTest::testQueryBuilder_data()
|
|
Rex Dieter |
adf30a |
}
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
{
|
|
Rex Dieter |
adf30a |
+ /// SELECT with CASE
|
|
Rex Dieter |
adf30a |
+ QueryBuilder qbTpl = QueryBuilder("table1", QueryBuilder::Select );
|
|
Rex Dieter |
adf30a |
+ qbTpl.setDatabaseType( DbType::MySQL );
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ QueryBuilder qb = qbTpl;
|
|
Rex Dieter |
adf30a |
+ qb.addColumn( "col" );
|
|
Rex Dieter |
adf30a |
+ qb.addColumn( Query::Case( "col1", Query::Greater, 42, "1", "0" ) );
|
|
Rex Dieter |
adf30a |
+ bindVals.clear();
|
|
Rex Dieter |
adf30a |
+ bindVals << 42;
|
|
Rex Dieter |
adf30a |
+ mBuilders << qb;
|
|
Rex Dieter |
adf30a |
+ QTest::newRow( "select case simple") << mBuilders.count()
|
|
Rex Dieter |
adf30a |
+ << QString( "SELECT col, CASE WHEN ( col1 > :0 ) THEN 1 ELSE 0 END FROM table1" ) << bindVals;
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ qb = qbTpl;
|
|
Rex Dieter |
adf30a |
+ qb.addAggregation( "table1.col1", "sum" );
|
|
Rex Dieter |
adf30a |
+ qb.addAggregation( "table1.col2", "count" );
|
|
Rex Dieter |
adf30a |
+ Query::Condition cond( Query::Or );
|
|
Rex Dieter |
adf30a |
+ cond.addValueCondition( "table3.col2", Query::Equals, "value1" );
|
|
Rex Dieter |
adf30a |
+ cond.addValueCondition( "table3.col2", Query::Equals, "value2" );\
|
|
Rex Dieter |
adf30a |
+ Query::Case caseStmt( cond, "1", "0" );
|
|
Rex Dieter |
adf30a |
+ qb.addAggregation( caseStmt, "sum" );
|
|
Rex Dieter |
adf30a |
+ qb.addJoin( QueryBuilder::LeftJoin, "table2", "table1.col3", "table2.col1" );
|
|
Rex Dieter |
adf30a |
+ qb.addJoin( QueryBuilder::LeftJoin, "table3", "table2.col2", "table3.col1" );
|
|
Rex Dieter |
adf30a |
+ bindVals.clear();
|
|
Rex Dieter |
adf30a |
+ bindVals << QString("value1") << QString("value2");
|
|
Rex Dieter |
adf30a |
+ mBuilders <
|
|
Rex Dieter |
adf30a |
+ QTest::newRow( "select case, aggregation and joins" ) << mBuilders.count()
|
|
Rex Dieter |
adf30a |
+ << QString( "SELECT sum(table1.col1), count(table1.col2), sum(CASE WHEN ( table3.col2 = :0 OR table3.col2 = :1 ) THEN 1 ELSE 0 END) "
|
|
Rex Dieter |
adf30a |
+ "FROM table1 "
|
|
Rex Dieter |
adf30a |
+ "LEFT JOIN table2 ON ( table1.col3 = table2.col1 ) "
|
|
Rex Dieter |
adf30a |
+ "LEFT JOIN table3 ON ( table2.col2 = table3.col1 )")
|
|
Rex Dieter |
adf30a |
+ << bindVals;
|
|
Rex Dieter |
adf30a |
+ }
|
|
Rex Dieter |
adf30a |
+
|
|
Rex Dieter |
adf30a |
+ {
|
|
Rex Dieter |
adf30a |
/// UPDATE with INNER JOIN
|
|
Rex Dieter |
adf30a |
QueryBuilder qbTpl = QueryBuilder( "table1", QueryBuilder::Update );
|
|
Rex Dieter |
adf30a |
qbTpl.setColumnValue( "col", 42 );
|
|
Rex Dieter |
adf30a |
@@ -310,4 +346,4 @@ void QueryBuilderTest::benchQueryBuilder()
|
|
Rex Dieter |
adf30a |
}
|
|
Rex Dieter |
adf30a |
|
|
Rex Dieter |
adf30a |
QVERIFY(executed);
|
|
Rex Dieter |
adf30a |
-}
|
|
Rex Dieter |
adf30a |
\ No newline at end of file
|
|
Rex Dieter |
adf30a |
+}
|
|
Rex Dieter |
adf30a |
--
|
|
Rex Dieter |
adf30a |
2.1.0
|
|
Rex Dieter |
adf30a |
|