[ndb] Interface générique pour SGBD en C++17

Ecrire des requêtes pour n'importe quel SGBD en C++

a marqué ce sujet comme résolu.

Salut !

ndb est une interface générique qui permet de se connecter à n’importe quel moteur de données (SGBD ou autre) Les requêtes sont écrites en C++ et peuvent être exécutées sur différents moteurs de données en changeant un seul paramètre. Les modèles de données sont définis et accessibles compile-time . MySQL et MongoDB sont encore en cours de dev/test.

Tester : https://github.com/ads00/ndb

Features

  • Header only
  • Requêtes full C++ et génériques
  • Support de tout type de moteur de données
  • Expressions SQL générées compile-time
  • Gestion des types personnalisable
  • Accès aux champs via des structures
  • Déduction d’expressions partielles

Exemple

Database (avec macros)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ndb_table(movie,
          ndb_field(id, int, 8),
          ndb_field(name, std::string, 255)
)
ndb_table(music,
          ndb_field(id, int, 8),
          ndb_field(image, std::string, 255)
)

ndb_model(library, movie, music)

ndb_project(my_project,
            ndb_database(alpha, library, ndb::sqlite),
            ndb_database(zeta, library, ndb::mongo)
)

Requêtes

1
2
3
4
ndb::query<dbs::zeta>() << ( movie.id, movie.image ); // get
ndb::query<dbs::zeta>() << ( movie.id == a && movie.name == b ); // get all par condition
ndb::query<dbs::zeta>() + ( movie.id = 3, movie.name = "test" ); // add
ndb::query<dbs::zeta>() - ( movie.id == 3 ); // del

Résultats

1
2
3
4
5
for (auto& line : ndb::query<dbs::zeta>() << (movie.id, movie.name) )
{
    std::cout << "\nID : " << line[movie.id];
    std::cout << "\nName : " << line[movie.name];
}

Minimal

Un exemple avec une base de donnée libray en utilisant le modèle collection et une table movie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "my_database.h"

int main()
{
    const auto& movie = ndb::models::collection.movie; // alias

    ndb::initializer<ndb::sqlite> init;
    ndb::connect<dbs::library>();

    ndb::query<dbs::library>() + (movie.name = "Interstellar", movie.duration = 2.49_h) );

    return 0;
}
+5 -0

Salut,

Voici un exemple complet fonctionnel qui montre l’utilisation de type custom

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <ndb/initializer.hpp>
#include <ndb/engine/sqlite/sqlite.hpp> // engine
#include <ndb/preprocessor.hpp> // database macros
#include <ndb/function.hpp> // ndb::clear
#include <ndb/query.hpp> // query and expression

/*! Generate database entities (instances, structures etc ...)
 * Entites are generated in namespaces ndb::models, ndb::tables, ndb::databases::my_project
 * Add _ to the name tu use the type (example : ndb::tables::movie_)
 */

// database
ndb_table(movie,
           ndb_field_id,
          ndb_field(name, std::string, ndb::size<255>),
          ndb_field(duration, std::chrono::duration<double, std::ratio<3600>>)
)
ndb_table(music,
          ndb_field(id, int),
          ndb_field(image, std::string)
)
ndb_model(library, movie, music)

ndb_project(my_project,
            ndb_database(library, library, ndb::sqlite)
)

// alias
namespace dbs
{
    using library = ndb::databases::my_project::library_;
}

int main()
{
    using namespace std::chrono_literals;
    const auto& movie = ndb::models::library.movie;

    ndb::initializer<ndb::sqlite> init;
    //! connect to database library
    ndb::connect<dbs::library>();
    //! clear movie table
    ndb::clear<dbs::library>(movie);

    //! add records
    ndb::query<dbs::library>() + (movie.name = "Interstellar", movie.duration = 2.49h);
    ndb::query<dbs::library>() + (movie.name = "Watchmen", movie.duration = 3.30h);

    //! get movie with specified duration
    //! missing informations of the query are deduced compile time
    //! for SQL-like engines, the string is generated compile time
    for (auto& line : ndb::query<dbs::library>() << (movie.duration == 3.30h))
    {
        //! access fields from result using field name
        std::cout << "movie.id : " << line[movie.id] << std::endl;
        std::cout << "movie.name : " << line[movie.name] << std::endl;
        std::cout << "movie.duration : " << line[movie.duration].count() << " Hours" << std::endl;
    }
    return 0;
}
+3 -0

Salut,

Nouvelle feature (bientôt complète), les oquery (object query)

Prenons la table movie, on peut désormais récupérer un objet movie depuis une requête

La génération sera automatique demain, il suffira donc de remplacer ndb::query par ndb::oquery pour avoir un objet à la place d’une ligne.

1
2
3
4
5
6
7
8
auto [interstellar] =  ndb::oquery<dbs::library>() << (movie.id == 1);
std::cout << interstellar.id << " | " << interstellar.name << std::endl;

for (auto [id, name] : ndb::oquery<dbs::library>() << movie)
{
    std::cout << "id : " << id << std::endl;
    std::cout << "name : " << name << std::endl;
}

Ce qui donne :

1
2
3
4
5
1 | Interstellar
id : 1
name : Interstellar
id : 2
name : Watchmen

Salut,

Je suis en train de refaire la partie expressions dans le but d’ajouter les alias, les requêtes préparées, les formes d’expressions et les requêtes dynamiques.

Je cherche quelqu’un qui serait intéressé par l’implémentation de l’interface pour un moteur de stockage utilisant des expressions différentes de SQL comme bson ou même un moteur qui effectue les opérations de l’expression directement sur le moteur (un tuple engine par exemple)

Si ça vous intéresse, contactez moi !

Salut,

Quelques news concernant la prochaine version.

La syntaxe va légèrement changer pour devenir plus explicite (tout en restant générique) et permettre la construction de requêtes dynamiques et préparées.

La déduction sera mise de côté, c’est une feature qui demande plus d’opérations à la compilation (ce qui peut gêner les gens qui n’ont pas de supercalculateur) et qui demande plus de temps de maintenance (principalement à cause de msvc).

La feature sera peut-être rajouter plus tard mais de manière optionnelle.

J’ajouterai à la place plus de fonctions utilitaires permettant d’effectuer des requêtes standards.

constexpr auto expr = ndb::statement << ndb::del << ndb::source(movie) << ndb::filter(movie.id == 3);

ndb::dynamic_statement<db> stmt;
stmt << ndb::get(movie.id, movie.name) << ndb::source(movie) << ndb::filter;
for (int i = 0; i < 3; ++i)
{
    stmt << (movie.id == 3);

    if (i < 3 - 1) stmt << ndb::logical_and;
}
std::cout << "\ndyn output : " << stmt.native();

Salut,

Il est désormais possible d’ajouter des options à la création des tables.

ndb_table(movie
    , ndb_field_id
    , ndb_field(name, std::string, ndb::size<255>, ndb::option<ndb::field_option::unique>)
    , ndb_field(duration, std::chrono::duration<double, std::ratio<3600>>)
    , ndb_option(ndb::table_option::unique<name_, duration_>)

La gestion des relations se fera par une interface orientée graph mais permettra de gérer n’importe quel type de SGBD.

Ca devrait ressembler à ça, je ne sais pas encore si le style Cypher sera implémenté.

// cypher
ndb::query<dbs::library>() << (ndb::get() << ndb::relation(user - user.arcs.possess -> music || user - user.arcs.like - music) << ndb::filter(user.name == "ads"s))

// fonctionnel
ndb::query<dbs::library>() << (ndb::get() << ndb::relation(ndb::arc(user, music, user.arcs.possess) || ndb::edge(user, music, user.arcs.like)) << ndb::filter(user.name == "ads"s))

Salut,

  • Les requêtes préparées sont prêtes !
    ndb_prepare(get_movie) << (ndb::get()
        << ndb::source(movie)
        << ndb::filter(movie.duration == _ || movie.name == _));
  • La limite du nombre de champs a été levées.
  • On peut définir un type de retour pour les expressions
  • Les alias sont prêts !
ndb_alias(count_id, ndb::count(movie.id));
ndb_alias(count_name, ndb::count(movie.name));

auto q = (
ndb::get(
    movie.id, movie.name, movie.duration
    , count_id
    , count_name)
    << ndb::source(movie)
);
...

std::cout << "count_id : " << line[count_id] << std::endl;

Salut,

La gestion des objets a été ajoutée, ça ressemble à ça

::movie interstellar{ "params" };
interstellar.name = "Interstellar";
interstellar.duration = 2.49h;
ndb::store(interstellar);

::movie movie{"params"};
ndb::load(movie, interstellar.oid);
movie.display();
movie.edit();
ndb::store(movie);

auto edited_movie = ndb::make<::movie>(movie.oid, "params");
edited_movie.display();

ndb::unload(interstellar);
interstellar.display();

L’exemple complet se trouve ici : https://github.com/ads00/ndb/blob/dev/example/object.cpp

Salut, J’ai ajouté un système de variables persistantes qui permettent par exemple de créer des sauvegardes de configuration.

struct browser : ndb::persistent_group
{
    using persistent_group::persistent_group;

    ndb::persistent<std::string> home{ this, "home", "www.google.fr" };

    struct : ndb::persistent_group
    {
        using persistent_group::persistent_group;

        ndb::persistent<bool> auto_load{ this, "auto_load", false };
        ndb::persistent<int> history_size{ this, "history_size", 10 };
    } tabs{ this, "tabs" };
};

int main()
{
    ndb::initializer<ndb::sqlite> init;

    ::browser browser{ "browser" };

    std::cout << "\n" << browser.home.path() << " : " << browser.home.get();
    std::cout << "\n" << browser.tabs.auto_load.path() << " : " << browser.tabs.auto_load.get();
    std::cout << "\n" << browser.tabs.history_size.path() << " : " << browser.tabs.history_size.get();

    browser.home = "https://zestedesavoir.com";
    browser.tabs.auto_load = true;
    browser.tabs.history_size = 200;

    return 0;
}

1ère exécution :

browser.home : www.google.fr
browser.tabs.auto_load : 0
browser.tabs.history_size : 10

Exécutions suivantes :

browser.home : https://zestedesavoir.com
browser.tabs.auto_load : 1
browser.tabs.history_size : 200

Exemple complet : https://github.com/ads00/ndb/blob/dev/example/persistent.cpp

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte