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 |