项目作者: ibatullin

项目描述 :
Relational algebra for C++/Qt
高级语言: C++
项目地址: git://github.com/ibatullin/ariel.git
创建时间: 2019-08-13T10:55:08Z
项目社区:https://github.com/ibatullin/ariel

开源协议:

下载


Ariel Logo

NOTE: documentation is not finished yet

Ariel is a relational algebra for C++/Qt. Ariel creates an abstract syntax tree (AST) for SQL.
It allows to write and edit queries in a semantic way as a code and not as strings.

The project is inspired by Arel

Here’s the code:

  1. Ariel::Table posts("posts");
  2. posts.select(posts["title"])
  3. .where(posts["date"] == QDate::currentDate() && !posts["deleted"])
  4. .order(posts["id"].ascending())
  5. .group(posts["category"])
  6. .skip(30)
  7. .take(50)
  8. .toString();
  9. /*
  10. * Produces:
  11. * SELECT posts.title FROM posts
  12. * WHERE posts.date = '2019-08-14' AND posts.deleted = FALSE
  13. * GROUP BY posts.category
  14. * ORDER BY posts.id ASC
  15. * LIMIT 50 OFFSET 30
  16. */

Table of Contents

Get Started

Installation

TBD

qbs

Include Ariel library to a qbs project:

  1. Project {
  2. references: [
  3. "<ariel-path>/ariel.qbs"
  4. ]
  5. CppApplication {
  6. Depends { name: "ariel" }
  7. }
  8. }

Basics

Start with table:

  1. Ariel::Table posts("posts");

Create attribute (read “column”) from the table:

  1. Ariel::Attribute id = posts["id"];
  2. Ariel::Attribute title = posts["title"];

And then use attributes in methods:

  1. posts.select(title); // SELECT posts.title FROM posts
  2. posts.select({ id, title }); // SELECT posts.id, posts.title FROM posts

Ariel supports following SQL statements: select, insert, update, delete. For each statement Ariel has tree manager.
Each manager has specific methods for its statement.

  1. Ariel::SelectManager selectManager(posts);
  2. Ariel::InsertManager insertManager(posts);
  3. Ariel::UpdateManager updateManager(posts);
  4. Ariel::DeleteManager deleteManager(posts);

Tree Manager can be created from Table:

  1. Ariel::SelectManager selectManager = posts.select(id);
  2. Ariel::InsertManager insertManager = posts.insert(id, 3);
  3. Ariel::InsertManager updateManager = posts.update(id, 3);

You don’t have to think about tree managers. For simple queries it’s more convenient to use method chaining:

  1. posts.select({ id, text })
  2. .where(title == "Welcome")
  3. .order(id.ascending())
  4. .toSql();
  5. posts.insert(id, 3)
  6. .insert(title, "Welcome")
  7. .toSql();
  8. posts.upate(id, 3)
  9. .update(title, "Welcome")
  10. .toSql();

Output options

Tree Manager has two methods for output AST as a string:

  • QString TreeManager::toString() returns SQL with bound values, it can be useful for debug purposes.
  • QString TreeManager::toSql() returns SQL with placeholders, it can be useful for creating query.

The difference between toString() method and toSql() method is shown below:

  1. QSqlDatabase::addDatabase("QSQLITE");
  2. Ariel::Table posts("posts");
  3. Ariel::SelectManager selectManager = posts.select(Ariel::Star)
  4. .where(posts["id"] == Ariel::BindValue(3));
  5. qDebug() << selectManager.toString(); // SELECT FROM posts.* WHERE posts.id = 3
  6. qDebug() << selectManager.toSql(); // SELECT FROM "posts".* WHERE "posts"."id" = ?

Ariel has an overloaded stream operator for QDebug:

  1. qDebug() << posts.select(Ariel::Star); // SELECT posts.* FROM posts

Use SqlCollector for access to bound values. Collector encapsulates both SQL string and bound values:

  1. const SqlCollector collector = selectManager.build();
  2. const QString sql = collector.value();
  3. QSqlQuery query;
  4. query.prepare(sql);
  5. for (const QVariant &bindValue : collector.bindValues())
  6. query.addBindValue(bindValue);
  7. query.exec();

NOTE: Ariel doesn’t provide interface to execute queries.

Current Status

Supported dialects

  • Common SQL

TODO (not implemented)

  • Joins
  • Top
  • Having
  • As
  • SQL functions (like COUNT, MIN, MAX)

Feel free to create issue with feature request.

Select Statement

Projections

  1. Ariel::Table posts("posts");
  2. posts.select(Ariel::Star); // SELECT posts.* FROM posts
  3. posts.select(posts["title"]); // SELECT posts.title FROM posts
  4. posts.select("text"); // SELECT posts.text FROM posts
  5. posts.select({ "title", "date" }); // SELECT posts.title, posts.date FROM posts
  6. posts.select({ text, title }); // SELECT posts.text, posts.title FROM posts

WHERE clause

Comparison operators

  1. posts.where(posts["title"] == "Welcome"); // ... WHERE title = 'Welcome'
  2. posts.where(posts["id"] < 3); // ... WHERE title < 3
  3. posts.where(posts["id"] == 3); // ... WHERE title = 3
  4. posts.where(posts["id"] != 3); // ... WHERE title <> 3
  5. posts.where(posts["deleted"] == true); // ... WHERE posts.deleted = TRUE

Logical operators

  1. auto id = posts["id"];
  2. auto date = posts["date"];
  3. posts.where(id == 1 && title == "Welcome"); // ... WHERE posts.id = 1 AND posts.title = 'Welcome'
  4. posts.where(id == 1 || title == "Welcome"); // ... WHERE posts.id = 1 OR posts.title = 'Welcome'
  5. posts.where(posts["deleted"]); // ... WHERE posts.deleted = TRUE
  6. posts.where(!posts["deleted"]); // ... WHERE posts.deleted = FALSE
  7. posts.where((id == 1 || id == 2) // ... WHERE (posts.id == 1 OR posts.id = 2)
  8. && title == Welcome); // AND posts.title = 'Welcome'
  9. posts.where((id == 1 && title == "Welcome") // ... WHERE (posts.id == 1 AND posts.title = 'Welcome')
  10. || id == 2); // OR posts.id = 2

Special operators

  1. posts.where(posts["id"].isNull()); // ... WHERE posts.id IS NULL
  2. posts.where(posts["id"].isNotNull()); // ... WHERE posts.id IS NOT NULL
  3. posts.where(posts["id"].between(1, 4)); // ... WHERE posts.id BETWEEN 1 AND 4
  4. posts.where(posts["id"].match("a%")); // ... WHERE posts.id LIKE 'a%'
  5. posts.where(posts["id"].in({ 1, 2, 3 })); // ... WHERE posts.id IN (1, 2, 3)
  6. posts.where(posts["id"].notIn({ 1, 2, 3 })); // ... WHERE posts.id NOT IN (1, 2, 3)

Logical NOT with special operators

  1. posts.where(!posts["id"].isNull()); // ... WHERE posts.id IS NOT NULL
  2. posts.where(!posts["id"].isNotNull()); // ... WHERE posts.id IS NULL
  3. posts.where(!posts["id"].between(1, 4)); // ... WHERE posts.id NOT BETWEEN 1 AND 4
  4. posts.where(!posts["id"].match("a%")); // ... WHERE posts.id NOT LIKE 'a%'
  5. posts.where(!posts["id"].in({ 1, 2, 3 })); // ... WHERE posts.id NOT IN (1, 2, 3)
  6. posts.where(!posts["id"].notIn({ 1, 2, 3 })); // ... WHERE posts.id IN (1, 2, 3)

Comparison types

  1. auto key = posts["key"];
  2. posts.where(key == "A"); // ... WHERE posts.key = 'A'
  3. posts.where(key == QString("A")); // ... WHERE posts.key = 'A'
  4. posts.where(key == 1); // ... WHERE posts.key = 1
  5. posts.where(key == QDate::currentDate()); // ... WHERE posts.key = '2019-03-03'
  6. posts.where(key == QVariant()); // ... WHERE posts.key IS NULL

Compare with an attribute

An attribute may be compared with another attribute

  1. posts.where(posts["id1"] == posts["id2"]); // ... WHERE posts.id1 = posts.id2

Compare with QStringList

  1. const QStringList titles { "wip", "todo", "tbd" };
  2. posts.where(posts["titles"] == titles); // ... WHERE posts.titles IN ('wip', 'todo', 'tbd')
  3. posts.where(posts["titles"] != titles); // ... WHERE posts.titles NOT IN ('wip', 'todo', 'tbd')

Compare with QVariantList

  1. const QVariantList values;
  2. values.append(QVariant("a"));
  3. values.append(QVariant("b"));
  4. values.append(QVariant(1));
  5. values.append(QVariant(2));
  6. posts.where(posts["key"] == values); // ... WHERE posts.key IN ('a', 'b', 1, 2)
  7. posts.where(posts["key"] != values); // ... WHERE posts.key NOT IN ('a', 'b', 1, 2)

ORDER BY clause

  1. posts.order(posts["id"].ascending()); // ... ORDER BY posts.id ASC
  2. posts.order(posts["id"].descending()); // ... ORDER BY posts.id DESC
  3. posts.order(posts["id"].ascending())
  4. .order(posts["title"].ascending()); // ... ORDER BY posts.id ASC, posts.title ASC

GROUP BY clause

  1. posts.group(posts["section"]); // ... GROUP BY posts.section
  2. posts.group("section"); // ... GROUP BY posts.section
  3. posts.group({ posts["section"], posts["category"] }); // ... GROUP BY posts.section, posts.category
  4. posts.group({ "section", "category" }); // ... GROUP BY posts.section, posts.category

Limit, Offset

  1. posts.take(10); // SELECT posts.* FROM posts LIMIT 10
  2. posts.skip(10); // SELECT posts.* FROM posts OFFSET 10
  3. posts.take(20).skip(10); // SELECT posts.* FROM posts LIMIT 20 OFFSET 10

Compound Select Statements

Ariel supports compound select statements UNION, UNION ALL, INTERSECT, EXCEPT.

Create two select statements:

  1. Ariel::Table posts("posts");
  2. auto select1 = posts.select(Ariel::Star)
  3. .where(posts["title"] == "Welcome");
  4. auto select2 = posts.select(Ariel::Star)
  5. .where(posts["date"] == QDate::currentDate());

Union

  1. SelectManager::distinctUnion(select1, select2);
  2. // or
  3. select1.distinctUnion(select2);
  4. /* Produces:
  5. * SELECT posts.* FROM posts WHERE posts.title = "Welcome"
  6. * UNION
  7. * SELECT posts.* FROM posts WHERE posts.date = '2019-04-14'
  8. */

Union all

  1. SelectManager::unionAll(select1, select2);
  2. // or
  3. select1.unionAll(select2);
  4. /* Produces:
  5. * SELECT posts.* FROM posts WHERE posts.title = "Welcome"
  6. * UNION ALL
  7. * SELECT posts.* FROM posts WHERE posts.date = '2019-04-14'
  8. */

Intersect

  1. SelectManager::intersect(select1, select2);
  2. // or
  3. select1.instersect(select2);
  4. /* Produces:
  5. * SELECT posts.* FROM posts WHERE posts.title = "Welcome"
  6. * INTERSECT
  7. * SELECT posts.* FROM posts WHERE posts.date = '2019-04-14'
  8. */

Except

  1. SelectManager::except(select1, select2);
  2. // or
  3. select1.except(select2);
  4. /* Produces:
  5. * SELECT posts.* FROM posts WHERE posts.title = "Welcome"
  6. * EXCEPT
  7. * SELECT posts.* FROM posts WHERE posts.date = '2019-04-14'
  8. */

Insert Statement

Insert via table

  1. Ariel::Table posts("posts");
  2. posts.insert(posts["title"], "Welcome")
  3. .insert(posts["date"], QDate::currentDate());
  4. // INSERT INTO posts (title, date) VALUES ('Welcome', '2019-08-14')

Insert via InsertManager

  1. Ariel::Table posts("posts");
  2. Ariel::InsertManager insertManager(posts);
  3. insertManager.insert(posts["title"], "Welcome");
  4. insertManager.insert(posts["date"], QDate::currentDate());
  5. qDebug() << insertManager.toString();
  6. // INSERT INTO posts (title, date) VALUES ('Welcome', '2019-08-14')

Insert without relation (table):

  1. Ariel::Table posts("posts");
  2. Ariel::Attribute title = posts["title"];
  3. Ariel::InsertManager insertManager;
  4. insertManager.insert(title, "Welcome"); // Relation deduce from an attribute
  5. qDebug() << insertManager.toString(); // INSERT INTO posts (title) VALUES ('Welcome')

Insert into select statement:

  1. Ariel::Table posts("posts");
  2. Ariel::Attribute title = posts["title"];
  3. Ariel::SelectManager selectStatement = posts.select(title)
  4. .where(title == "Welcome");
  5. Ariel::InsertManager insertManager(posts);
  6. insertManager.column(title) // INSERT INTO posts
  7. .select(selectStatement); // SELECT posts.title WHERE posts.title = 'Welcome'

Update Statement

Update via table

  1. Ariel::Table posts("posts");
  2. posts.update(posts["id"], 3); // UPDATE posts SET posts.id = 3
  3. posts.update(posts["id"], 3)
  4. .update(posts["title"], "Welcome"); // UPDATE posts SET posts.id = 3, title = 'Welcome'
  5. posts.update(posts["title"], "Welcome") // UPDATE posts SET posts.title = 'Welcome'
  6. .where(posts["id"] == 3) // WHERE posts.id = 3
  7. .order(posts["date"].ascending()) // ORDER BY ASC
  8. .take(3); // LIMIT 3

Delete Statement

  1. Ariel::Table posts("posts");
  2. posts.deleteWhere(posts["id"] == 3); // DELETE FROM posts WHERE posts.id = 3
  3. posts.deleteWhere(posts["id"] == 3)).take(3); // DELETE FROM posts WHERE posts.id = 3 LIMIT 3

Setup Ariel

TBD

Extend Ariel

TBD

Copy AST

TBD

SQLITE

  1. Ariel::Table posts("posts");
  2. auto selectManager = posts.select(Ariel::Star)
  3. .where(posts["deleted"] == true);
  4. selectManager.toSql(); // SELECT posts.* FROM posts WHERE posts.deleted = TRUE
  5. QSqlDatabase::addDatabase("QSQLITE");
  6. selectManager.toSql(); // SELECT "posts".* FROM posts WHERE "posts"."deleted" = 1