Coverage Report

Created: 2025-06-26 12:50

test/zserio/ValidationSqliteUtilTest.cpp
Line
Count
Source
1
#include <memory>
2
3
#include "gtest/gtest.h"
4
#include "zserio/SqliteConnection.h"
5
#include "zserio/SqliteFinalizer.h"
6
#include "zserio/ValidationSqliteUtil.h"
7
8
namespace zserio
9
{
10
11
class ValidationSqliteUtilTest : public ::testing::Test
12
{
13
public:
14
    ValidationSqliteUtilTest() :
15
            connection(createConnection())
16
4
    {}
17
18
protected:
19
    using allocator_type = std::allocator<uint8_t>;
20
    using Util = ValidationSqliteUtil<allocator_type>;
21
22
    void insertRows(const std::string& tableName, uint32_t startId, uint32_t numRows)
23
3
    {
24
3
        Util::Statement statement(connection.prepareStatement("INSERT INTO " + tableName + " VALUES (?, ?)"));
25
26
24
        for (uint32_t i = 0; i < numRows; 
++i21
)
27
21
        {
28
21
            sqlite3_bind_int(statement.get(), 1, static_cast<int>(startId + i));
29
21
            sqlite3_bind_int(statement.get(), 2, static_cast<int>(i * i));
30
21
            ASSERT_EQ(SQLITE_DONE, sqlite3_step(statement.get()));
31
21
            sqlite3_reset(statement.get());
32
21
        }
33
3
    }
34
35
    using TiedColumn = std::tuple<Util::string_type, Util::string_type, bool, bool>;
36
37
    TiedColumn tieColumn(const Util::ColumnDescription& columnDescription)
38
8
    {
39
8
        return std::tie(columnDescription.name, columnDescription.type, columnDescription.isNotNull,
40
8
                columnDescription.isPrimaryKey);
41
8
    }
42
43
    SqliteConnection connection;
44
45
private:
46
    sqlite3* createConnection()
47
4
    {
48
4
        sqlite3* db = nullptr;
49
4
        int result = sqlite3_open(IN_MEMORY_DATABASE, &db);
50
4
        EXPECT_EQ(SQLITE_OK, result);
51
4
        return db;
52
4
    }
53
54
    static const char* const IN_MEMORY_DATABASE;
55
};
56
57
const char* const ValidationSqliteUtilTest::IN_MEMORY_DATABASE = ":memory:";
58
59
TEST_F(ValidationSqliteUtilTest, getNumberOfTableRows)
60
1
{
61
1
    ASSERT_THROW(Util::getNumberOfTableRows(connection, ""_sv, "test"_sv, allocator_type()), SqliteException);
62
63
1
    ASSERT_THROW(Util::getNumberOfTableRows(connection, "NONEXISTING"_sv, "test"_sv, allocator_type()),
64
1
            SqliteException);
65
66
1
    ASSERT_THROW(
67
1
            {
68
1
                try
69
1
                {
70
1
                    Util::getNumberOfTableRows(
71
1
                            connection, ""_sv, "(SELECT load_extension('unknown'))"_sv, allocator_type());
72
1
                }
73
1
                catch (const SqliteException& e)
74
1
                {
75
1
                    ASSERT_STREQ("ValidationSqliteUtils.getNumberOfTableRows: sqlite3_step() failed: SQL logic "
76
1
                                 "error",
77
1
                            e.what());
78
1
                    throw;
79
1
                }
80
1
            },
81
1
            SqliteException);
82
83
1
    connection.executeUpdate("CREATE TABLE test(id INTEGER PRIMARY KEY NOT NULL, value INTEGER NOT NULL)");
84
1
    ASSERT_EQ(0, Util::getNumberOfTableRows(connection, ""_sv, "test"_sv, allocator_type()));
85
86
1
    insertRows("test", 0, 1);
87
1
    ASSERT_EQ(1, Util::getNumberOfTableRows(connection, ""_sv, "test"_sv, allocator_type()));
88
89
1
    insertRows("test", 1, 10);
90
1
    ASSERT_EQ(11, Util::getNumberOfTableRows(connection, ""_sv, "test"_sv, allocator_type()));
91
92
1
    insertRows("test", 11, 10);
93
1
    ASSERT_EQ(21, Util::getNumberOfTableRows(connection, ""_sv, "test"_sv, allocator_type()));
94
1
}
95
96
TEST_F(ValidationSqliteUtilTest, getTableSchema)
97
1
{
98
1
    Util::TableSchema schema;
99
1
    Util::getTableSchema(connection, ""_sv, "test"_sv, schema, allocator_type());
100
1
    ASSERT_TRUE(schema.empty());
101
102
1
    ASSERT_THROW(Util::getTableSchema(connection, "NONEXISTING"_sv, "test"_sv, schema, allocator_type()),
103
1
            SqliteException);
104
105
1
    connection.executeUpdate("CREATE TABLE test1(id INTEGER PRIMARY KEY NOT NULL, value INTEGER NOT NULL)");
106
1
    Util::getTableSchema(connection, ""_sv, "test1"_sv, schema, allocator_type());
107
1
    ASSERT_EQ(2, schema.size());
108
1
    auto search = schema.find("id");
109
1
    ASSERT_TRUE(search != schema.end());
110
1
    ASSERT_EQ("id", search->first);
111
1
    ASSERT_EQ(std::make_tuple("id", "INTEGER", true, true), tieColumn(search->second));
112
1
    search = schema.find("value");
113
1
    ASSERT_TRUE(search != schema.end());
114
1
    ASSERT_EQ("value", search->first);
115
1
    ASSERT_EQ(std::make_tuple("value", "INTEGER", true, false), tieColumn(search->second));
116
117
1
    schema.clear();
118
1
    connection.executeUpdate(
119
1
            "CREATE TABLE test2(id INTEGER PRIMARY KEY NOT NULL, text TEXT NOT NULL, "
120
1
            "field BLOB)");
121
1
    Util::getTableSchema(connection, ""_sv, "test2"_sv, schema, allocator_type());
122
1
    ASSERT_EQ(3, schema.size());
123
1
    search = schema.find("id");
124
1
    ASSERT_TRUE(search != schema.end());
125
1
    ASSERT_EQ("id", search->first);
126
1
    ASSERT_EQ(std::make_tuple("id", "INTEGER", true, true), tieColumn(search->second));
127
1
    search = schema.find("text");
128
1
    ASSERT_TRUE(search != schema.end());
129
1
    ASSERT_EQ("text", search->first);
130
1
    ASSERT_EQ(std::make_tuple("text", "TEXT", true, false), tieColumn(search->second));
131
1
    search = schema.find("field");
132
1
    ASSERT_TRUE(search != schema.end());
133
1
    ASSERT_EQ("field", search->first);
134
1
    ASSERT_EQ(std::make_tuple("field", "BLOB", false, false), tieColumn(search->second));
135
136
    // multiple primary keys
137
1
    schema.clear();
138
1
    connection.executeUpdate(
139
1
            "CREATE TABLE test3(name TEXT, surname TEXT NOT NULL, field BLOB, "
140
1
            "PRIMARY KEY(name, surname))");
141
1
    Util::getTableSchema(connection, ""_sv, "test3"_sv, schema, allocator_type());
142
1
    ASSERT_EQ(3, schema.size());
143
1
    search = schema.find("name");
144
1
    ASSERT_TRUE(search != schema.end());
145
1
    ASSERT_EQ("name", search->first);
146
1
    ASSERT_EQ(std::make_tuple("name", "TEXT", false, true), tieColumn(search->second));
147
1
    search = schema.find("surname");
148
1
    ASSERT_TRUE(search != schema.end());
149
1
    ASSERT_EQ("surname", search->first);
150
1
    ASSERT_EQ(std::make_tuple("surname", "TEXT", true, true), tieColumn(search->second));
151
1
    search = schema.find("field");
152
1
    ASSERT_TRUE(search != schema.end());
153
1
    ASSERT_EQ("field", search->first);
154
1
    ASSERT_EQ(std::make_tuple("field", "BLOB", false, false), tieColumn(search->second));
155
1
}
156
157
TEST_F(ValidationSqliteUtilTest, isColumnInTable)
158
1
{
159
1
    ASSERT_FALSE(Util::isColumnInTable(connection, ""_sv, "test"_sv, "hidden"_sv, allocator_type()));
160
161
1
    ASSERT_FALSE(Util::isColumnInTable(connection, "NONEXISTING"_sv, "test"_sv, "hidden"_sv, allocator_type()));
162
163
1
    connection.executeUpdate("CREATE TABLE test1(id INTEGER PRIMARY KEY NOT NULL, value INTEGER NOT NULL)");
164
1
    ASSERT_FALSE(Util::isColumnInTable(connection, ""_sv, "test1"_sv, "hidden"_sv, allocator_type()));
165
1
    ASSERT_TRUE(Util::isColumnInTable(connection, ""_sv, "test1"_sv, "id"_sv, allocator_type()));
166
1
    ASSERT_TRUE(Util::isColumnInTable(connection, ""_sv, "test1"_sv, "value"_sv, allocator_type()));
167
168
1
    connection.executeUpdate("CREATE TABLE test2(id INTEGER PRIMARY KEY NOT NULL, text HIDDEN TEXT)");
169
1
    ASSERT_TRUE(Util::isColumnInTable(connection, ""_sv, "test2"_sv, "id"_sv, allocator_type()));
170
1
    ASSERT_TRUE(Util::isColumnInTable(connection, ""_sv, "test2"_sv, "text"_sv, allocator_type()));
171
1
}
172
173
TEST_F(ValidationSqliteUtilTest, sqliteColumnTypeName)
174
1
{
175
    // this test also verifies that sqlite3_column_type works as we except since we need to use it
176
    // in validateType* in generated sources for zserio SqlTables
177
178
1
    const char* tableName = "sqliteColumnTypeTable";
179
1
    connection.executeUpdate(std::string("CREATE TABLE ") + tableName +
180
1
            "(id INTEGER PRIMARY KEY, "
181
1
            "integerCol INTEGER, realCol REAL, textCol TEXT, blobCol BLOB)");
182
183
1
    connection.executeUpdate(std::string("INSERT INTO ") + tableName + " VALUES (0, NULL, NULL, NULL, NULL)");
184
1
    connection.executeUpdate(std::string("INSERT INTO ") + tableName + " VALUES (1, 13, 1.3, 'STRING', x'00')");
185
1
    connection.executeUpdate(std::string("INSERT INTO ") + tableName + " VALUES (2, 1.3, 'STRING', x'00', 13)");
186
187
1
    Util::Statement stmt(connection.prepareStatement(std::string("SELECT * from ") + tableName));
188
189
    // first row check NULL values
190
1
    ASSERT_EQ(SQLITE_ROW, sqlite3_step(stmt.get()));
191
1
    int type = sqlite3_column_type(stmt.get(), 0);
192
1
    ASSERT_EQ(SQLITE_INTEGER, type);
193
1
    ASSERT_STREQ("INTEGER", Util::sqliteColumnTypeName(type));
194
1
    type = sqlite3_column_type(stmt.get(), 1);
195
1
    ASSERT_EQ(SQLITE_NULL, type);
196
1
    ASSERT_STREQ("NULL", Util::sqliteColumnTypeName(type));
197
1
    type = sqlite3_column_type(stmt.get(), 2);
198
1
    ASSERT_EQ(SQLITE_NULL, type);
199
1
    ASSERT_STREQ("NULL", Util::sqliteColumnTypeName(type));
200
1
    type = sqlite3_column_type(stmt.get(), 3);
201
1
    ASSERT_EQ(SQLITE_NULL, type);
202
1
    ASSERT_STREQ("NULL", Util::sqliteColumnTypeName(type));
203
1
    type = sqlite3_column_type(stmt.get(), 4);
204
1
    ASSERT_EQ(SQLITE_NULL, type);
205
1
    ASSERT_STREQ("NULL", Util::sqliteColumnTypeName(type));
206
207
    // second row checks correct values
208
1
    ASSERT_EQ(SQLITE_ROW, sqlite3_step(stmt.get()));
209
1
    type = sqlite3_column_type(stmt.get(), 0);
210
1
    ASSERT_EQ(SQLITE_INTEGER, type);
211
1
    ASSERT_STREQ("INTEGER", Util::sqliteColumnTypeName(type));
212
1
    type = sqlite3_column_type(stmt.get(), 1);
213
1
    ASSERT_EQ(SQLITE_INTEGER, type);
214
1
    ASSERT_STREQ("INTEGER", Util::sqliteColumnTypeName(type));
215
1
    type = sqlite3_column_type(stmt.get(), 2);
216
1
    ASSERT_EQ(SQLITE_FLOAT, type);
217
1
    ASSERT_STREQ("REAL", Util::sqliteColumnTypeName(type));
218
1
    type = sqlite3_column_type(stmt.get(), 3);
219
1
    ASSERT_EQ(SQLITE_TEXT, type);
220
1
    ASSERT_STREQ("TEXT", Util::sqliteColumnTypeName(type));
221
1
    type = sqlite3_column_type(stmt.get(), 4);
222
1
    ASSERT_EQ(SQLITE_BLOB, type);
223
1
    ASSERT_STREQ("BLOB", Util::sqliteColumnTypeName(type));
224
225
    // third row checks types mismatch - i.e. checks dynamic typing in SQLite
226
1
    ASSERT_EQ(SQLITE_ROW, sqlite3_step(stmt.get()));
227
1
    type = sqlite3_column_type(stmt.get(), 0);
228
1
    ASSERT_EQ(SQLITE_INTEGER, type);
229
1
    ASSERT_STREQ("INTEGER", Util::sqliteColumnTypeName(type));
230
1
    type = sqlite3_column_type(stmt.get(), 1);
231
1
    ASSERT_EQ(SQLITE_FLOAT, type);
232
1
    ASSERT_STREQ("REAL", Util::sqliteColumnTypeName(type));
233
1
    type = sqlite3_column_type(stmt.get(), 2);
234
1
    ASSERT_EQ(SQLITE_TEXT, type);
235
1
    ASSERT_STREQ("TEXT", Util::sqliteColumnTypeName(type));
236
1
    type = sqlite3_column_type(stmt.get(), 3);
237
1
    ASSERT_EQ(SQLITE_BLOB, type);
238
1
    ASSERT_STREQ("BLOB", Util::sqliteColumnTypeName(type));
239
1
    type = sqlite3_column_type(stmt.get(), 4);
240
1
    ASSERT_EQ(SQLITE_INTEGER, type);
241
1
    ASSERT_STREQ("INTEGER", Util::sqliteColumnTypeName(type));
242
1
}
243
244
} // namespace zserio