summaryrefslogtreecommitdiff
path: root/src/database/table.hpp
blob: f2f70bbc53a91b59fd17e6a2c707f9b72ad68781 (plain)
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
134
135
136
137
#pragma once

#include <database/select_query.hpp>
#include <database/type_to_sql.hpp>
#include <logger/logger.hpp>
#include <database/row.hpp>

#include <algorithm>
#include <string>
#include <set>

using namespace std::string_literals;

std::set<std::string> get_all_columns_from_table(sqlite3* db, const std::string& table_name);

template <typename ColumnType>
void add_column_to_table(sqlite3* db, const std::string& table_name)
{
  const std::string name = ColumnType::name;
  std::string query{"ALTER TABLE "s + table_name + " ADD " + ColumnType::name + " " + TypeToSQLType<typename ColumnType::real_type>::type};
  log_debug(query);
  char* error;
  const auto result = sqlite3_exec(db, query.data(), nullptr, nullptr, &error);
  if (result != SQLITE_OK)
    {
      log_error("Error adding column ", name, " to table ", table_name, ": ",  error);
      sqlite3_free(error);
    }
}


template <typename ColumnType, decltype(ColumnType::options) = nullptr>
void append_option(std::string& s)
{
  s += " "s + ColumnType::options;
}

template <typename>
void append_option(...)
{ }

template <typename... T>
class Table
{
  static_assert(sizeof...(T) > 0, "Table cannot be empty");
  using ColumnTypes = std::tuple<T...>;

 public:
  using RowType = Row<T...>;

  Table(std::string name):
      name(std::move(name))
  {}

  void upgrade(sqlite3* db)
  {
    const auto existing_columns = get_all_columns_from_table(db, this->name);
    add_column_if_not_exists(db, existing_columns);
  }

  void create(sqlite3* db)
  {
    std::string res{"CREATE TABLE IF NOT EXISTS "};
    res += this->name;
    res += " (\n";
    this->add_column_create(res);
    res += ")";

    log_debug(res);

    char* error;
    const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error);
    log_debug("result: ", +result);
    if (result != SQLITE_OK)
      {
        log_error("Error executing query: ", error);
        sqlite3_free(error);
      }
  }

  RowType row()
  {
    return {this->name};
  }

  SelectQuery<T...> select()
  {
    SelectQuery<T...> select(this->name);
    return select;
  }

  const std::string& get_name() const
  {
    return this->name;
  }

 private:

  template <std::size_t N=0>
  typename std::enable_if<N < sizeof...(T), void>::type
  add_column_if_not_exists(sqlite3* db, const std::set<std::string>& existing_columns)
  {
    using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type;
    if (existing_columns.count(ColumnType::name) != 1)
      {
        add_column_to_table<ColumnType>(db, this->name);
      }
    add_column_if_not_exists<N+1>(db, existing_columns);
  }
  template <std::size_t N=0>
  typename std::enable_if<N == sizeof...(T), void>::type
  add_column_if_not_exists(sqlite3*, const std::set<std::string>&)
  {}

  template <std::size_t N=0>
  typename std::enable_if<N < sizeof...(T), void>::type
  add_column_create(std::string& str)
  {
    using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type;
    using RealType = typename ColumnType::real_type;
    str += ColumnType::name;
    str += " ";
    str += TypeToSQLType<RealType>::type;
    append_option<ColumnType>(str);
    if (N != sizeof...(T) - 1)
      str += ",";
    str += "\n";

    add_column_create<N+1>(str);
  }
  template <std::size_t N=0>
  typename std::enable_if<N == sizeof...(T), void>::type
  add_column_create(std::string&)
  { }

  const std::string name;
};