summary refs log tree commit diff stats
path: root/lib/query.h
blob: 0f490ed41a8683cd22e96b42a7bd26dfcf7ad32d (plain) (blame)
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#ifndef QUERY_H_7CC5284C
#define QUERY_H_7CC5284C

#include <vector>
#include <stdexcept>
#include <string>
#include <list>
#include <sqlite3.h>
#include <iostream>
#include "statement.h"
#include "binding.h"
#include "order.h"

namespace verbly {

  class database_error : public std::logic_error {
  public:

    database_error(std::string msg, std::string sqlMsg) : std::logic_error(msg + " (" + sqlMsg + ")")
    {
    }
  };

  template <typename Object>
  class query {
  public:

    query(const database& db, sqlite3* ppdb, filter queryFilter, order sortOrder, int limit) : db_(&db)
    {
      if ((sortOrder.getType() == order::type::field)
        && (sortOrder.getSortField().getObject() != Object::objectType))
      {
        throw std::invalid_argument("Can only sort query by a field in the result table");
      }

      statement stmt(Object::objectType, std::move(queryFilter));

      std::string queryString = stmt.getQueryString(Object::select, std::move(sortOrder), limit);
      std::list<binding> bindings = stmt.getBindings();

      if (sqlite3_prepare_v2(ppdb, queryString.c_str(), queryString.length(), &ppstmt_, NULL) != SQLITE_OK)
      {
        std::string errorMsg = sqlite3_errmsg(ppdb);
        sqlite3_finalize(ppstmt_);

        throw database_error("Error preparing query", errorMsg);
      }

      int i = 1;
      for (const binding& value : bindings)
      {
        switch (value.getType())
        {
          case binding::type::integer:
          {
            if (sqlite3_bind_int(ppstmt_, i, value.getInteger()) != SQLITE_OK)
            {
              std::string errorMsg = sqlite3_errmsg(ppdb);
              sqlite3_finalize(ppstmt_);

              throw database_error("Error binding value to query", errorMsg);
            }

            break;
          }

          case binding::type::string:
          {
            if (sqlite3_bind_text(ppstmt_, i, value.getString().c_str(), value.getString().length(), SQLITE_TRANSIENT) != SQLITE_OK)
            {
              std::string errorMsg = sqlite3_errmsg(ppdb);
              sqlite3_finalize(ppstmt_);

              throw database_error("Error binding value to query", errorMsg);
            }

            break;
          }

          case binding::type::invalid:
          {
            throw std::logic_error("Cannot use invalid bindings");
          }

          case binding::type::field:
          {
            throw std::logic_error("Compare field binding made it past statement generation");
          }
        }

        i++;
      }
    }

    ~query()
    {
      sqlite3_finalize(ppstmt_);
    }

    std::vector<Object> all() const
    {
      std::vector<Object> result;

      while (sqlite3_step(ppstmt_) == SQLITE_ROW)
      {
        result.emplace_back(*db_, ppstmt_);
      }

      sqlite3_reset(ppstmt_);

      return result;
    }

    Object first() const
    {
      std::vector<Object> results = all();
      if (!results.empty())
      {
        return results.front();
      } else {
        throw std::logic_error("query returned empty dataset");
      }
    }

  private:
    const database* db_;
    sqlite3_stmt* ppstmt_;

  };

};

#endif /* end of include guard: QUERY_H_7CC5284C */