Coverage Report

Created: 2024-04-30 09:35

src/zserio/ValidationSqliteUtil.h
Line
Count
Source (jump to first uncovered line)
1
#ifndef ZSERIO_VALIDATION_SQLITE_UTIL_H_INC
2
#define ZSERIO_VALIDATION_SQLITE_UTIL_H_INC
3
4
#include <map>
5
6
#include "zserio/RebindAlloc.h"
7
#include "zserio/SqliteConnection.h"
8
#include "zserio/SqliteFinalizer.h"
9
#include "zserio/String.h"
10
#include "zserio/StringView.h"
11
12
namespace zserio
13
{
14
15
/** Sqlite utility for validation. */
16
template <typename ALLOC>
17
struct ValidationSqliteUtil
18
{
19
    using string_type = string<ALLOC>;
20
    using Statement = std::unique_ptr<sqlite3_stmt, SqliteFinalizer>;
21
22
    /**
23
     * Description of a single column.
24
     */
25
    struct ColumnDescription
26
    {
27
        string_type name; /**< Column name. */
28
        string_type type; /**< Column SQLite data type ("INTEGER", "REAL", "TEXT" or "BLOB"). */
29
        bool isNotNull; /**< Is true if the column has "NOT NULL" constraint. */
30
        bool isPrimaryKey; /**< Is true if the column is primary key. */
31
    };
32
33
    using TableSchema = std::map<string_type, ColumnDescription, std::less<string_type>,
34
            RebindAlloc<ALLOC, std::pair<const string_type, ColumnDescription>>>;
35
36
    /**
37
     * Gets number of rows in the given SQLite table.
38
     *
39
     * \param connection     Database connection to use.
40
     * \param attachedDbName Attached database name if table is relocated in different database.
41
     * \param tableName      Name of the table to get column types of.
42
     * \param allocator      Allocator to use for the query string composition.
43
     *
44
     * \return Number of rows.
45
     *
46
     * \throw SqliteException if the table does not exist.
47
     */
48
    static size_t getNumberOfTableRows(SqliteConnection& connection, StringView attachedDbName,
49
            StringView tableName, const ALLOC& allocator)
50
7
    {
51
7
        string_type sqlQuery(allocator);
52
7
        sqlQuery += "SELECT count(*) FROM ";
53
7
        if (!attachedDbName.empty())
54
1
        {
55
1
            sqlQuery += attachedDbName;
56
1
            sqlQuery += ".";
57
1
        }
58
7
        sqlQuery += tableName;
59
60
7
        Statement statement(connection.prepareStatement(sqlQuery));
61
7
        const int result = sqlite3_step(statement.get());
62
7
        if (result != SQLITE_ROW)
63
1
        {
64
1
            throw SqliteException("ValidationSqliteUtils.getNumberOfTableRows: sqlite3_step() failed: ")
65
1
                    << SqliteErrorCode(result);
66
1
        }
67
68
6
        return static_cast<size_t>(sqlite3_column_int64(statement.get(), 0));
69
7
    }
70
71
    /**
72
     * Gets a map of column names to column description for given SQLite table.
73
     *
74
     * \param connection     Database connection to use.
75
     * \param attachedDbName Attached database name if table is relocated in different database.
76
     * \param tableName      Name of the table to get column types of.
77
     * \param tableSchema    Schema to fill.
78
     * \param allocator      Allocator to use for the query string composition.
79
     */
80
    static void getTableSchema(SqliteConnection& connection, StringView attachedDbName, StringView tableName,
81
            TableSchema& tableSchema, const ALLOC& allocator)
82
5
    {
83
5
        string_type sqlQuery(allocator);
84
5
        sqlQuery += "PRAGMA ";
85
5
        if (!attachedDbName.empty())
86
1
        {
87
1
            sqlQuery += attachedDbName;
88
1
            sqlQuery += ".";
89
1
        }
90
5
        sqlQuery += "table_info(";
91
5
        sqlQuery += tableName;
92
5
        sqlQuery += ")";
93
94
5
        Statement statement(connection.prepareStatement(sqlQuery));
95
96
5
        int result = SQLITE_OK;
97
13
        while ((result = sqlite3_step(statement.get())) == SQLITE_ROW)
98
8
        {
99
8
            const unsigned char* columnNameText = sqlite3_column_text(statement.get(), 1);
100
8
            const char* columnName = static_cast<const char*>(static_cast<const void*>(columnNameText));
101
8
            const unsigned char* columnTypeText = sqlite3_column_text(statement.get(), 2);
102
8
            const char* columnType = static_cast<const char*>(static_cast<const void*>(columnTypeText));
103
8
            tableSchema.emplace(string_type(columnName, allocator),
104
8
                    ColumnDescription{
105
8
                            string_type(columnName, allocator), string_type(columnType, allocator),
106
8
                            sqlite3_column_int(statement.get(), 3) != 0, // is not null
107
8
                            sqlite3_column_int(statement.get(), 5) != 0 // is primary key
108
8
                    });
109
8
        }
110
111
5
        if (result != SQLITE_DONE)
112
0
        {
113
0
            throw SqliteException("ValidationSqliteUtils.getTableSchema: sqlite3_step() failed: ")
114
0
                    << SqliteErrorCode(result);
115
0
        }
116
5
    }
117
118
    /**
119
     * Check if the column is present in the given SQLite table. Note that this method also detect
120
     * hidden SQLite columns, which are not visible using standard PRAGMA table_info query.
121
     *
122
     * \param connection     Database connection to use.
123
     * \param attachedDbName Attached database name if table is relocated in different database.
124
     * \param tableName      Name of the table to get column types of.
125
     * \param columnName     Name of the column to check.
126
     * \param allocator      Allocator to use for the query string composition.
127
     *
128
     * \return Returns true if the column is present in the table, even if the column is hidden. Otherwise
129
     *         returns false.
130
     */
131
    static bool isColumnInTable(SqliteConnection& connection, StringView attachedDbName, StringView tableName,
132
            StringView columnName, const ALLOC& allocator)
133
7
    {
134
        // try select to check if hidden column exists
135
7
        string_type sqlQuery(allocator);
136
7
        sqlQuery += "SELECT ";
137
7
        sqlQuery += columnName;
138
7
        sqlQuery += " FROM ";
139
7
        if (!attachedDbName.empty())
140
1
        {
141
1
            sqlQuery += attachedDbName;
142
1
            sqlQuery += ".";
143
1
        }
144
7
        sqlQuery += tableName;
145
7
        sqlQuery += " LIMIT 0";
146
147
7
        try
148
7
        {
149
7
            Statement statement(connection.prepareStatement(sqlQuery));
150
7
            return sqlite3_step(statement.get()) == SQLITE_DONE;
151
7
        }
152
7
        catch (const SqliteException&)
153
7
        {
154
3
            return false;
155
3
        }
156
7
    }
157
158
    /**
159
     * Gets name of the given SQLite column type.
160
     *
161
     * \param columnType SQLite column type.
162
     *
163
     * \return SQLite column type name.
164
     */
165
    static const char* sqliteColumnTypeName(int columnType)
166
15
    {
167
15
        switch (columnType)
168
15
        {
169
5
        case SQLITE_INTEGER:
170
5
            return "INTEGER";
171
2
        case SQLITE_FLOAT:
172
2
            return "REAL";
173
2
        case SQLITE_TEXT:
174
2
            return "TEXT";
175
2
        case SQLITE_BLOB:
176
2
            return "BLOB";
177
4
        default:
178
4
            return "NULL";
179
15
        }
180
15
    }
181
};
182
183
} // namespace zserio
184
185
#endif // ZSERIO_VALIDATION_SQLITE_UTIL_H_INC