1 // Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <asiolink/io_address.h>
10 #include <exceptions/exceptions.h>
11 #include <mysql/mysql_binding.h>
12 #include <util/optional.h>
13 #include <boost/date_time/gregorian/gregorian.hpp>
14 #include <boost/date_time/posix_time/posix_time.hpp>
15 #include <gtest/gtest.h>
16 
17 using namespace isc;
18 using namespace isc::asiolink;
19 using namespace isc::data;
20 using namespace isc::db;
21 using namespace isc::util;
22 
23 namespace {
24 
25 // This test verifies that default string is returned if binding is null.
TEST(MySqlBindingTest,defaultString)26 TEST(MySqlBindingTest, defaultString) {
27     auto binding = MySqlBinding::createNull();
28     EXPECT_EQ("foo", binding->getStringOrDefault("foo"));
29 
30     binding = MySqlBinding::createString("bar");
31     ASSERT_FALSE(binding->amNull());
32     EXPECT_EQ("bar", binding->getStringOrDefault("foo"));
33 }
34 
35 // This test verifies that null binding is created for unspecified string
36 // and the string binding is created for a specified string.
TEST(MySqlBindingTest,conditionalString)37 TEST(MySqlBindingTest, conditionalString) {
38     auto binding = MySqlBinding::condCreateString(Optional<std::string>());
39     EXPECT_TRUE(binding->amNull());
40 
41     binding = MySqlBinding::condCreateString("foo");
42     ASSERT_FALSE(binding->amNull());
43     EXPECT_EQ("foo", binding->getString());
44 }
45 
46 // This test verifies that empty string is stored in the database.
TEST(MySqlBindingTest,emptyString)47 TEST(MySqlBindingTest, emptyString) {
48     auto binding = MySqlBinding::condCreateString(Optional<std::string>(""));
49     ASSERT_FALSE(binding->amNull());
50     EXPECT_TRUE(binding->getString().empty());
51 }
52 
53 // This test verifies that an error is thrown upon an attempt to use
54 // invalid accessor for a string binding.
TEST(MySqlBindingTest,stringTypeMismatch)55 TEST(MySqlBindingTest, stringTypeMismatch) {
56     auto binding = MySqlBinding::createString("foo");
57     EXPECT_NO_THROW(static_cast<void>(binding->getString()));
58 
59     EXPECT_THROW(static_cast<void>(binding->getBlob()), InvalidOperation);
60     EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
61     EXPECT_THROW(static_cast<void>(binding->getFloat()), InvalidOperation);
62     EXPECT_THROW(static_cast<void>(binding->getBool()), InvalidOperation);
63     EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
64 }
65 
66 // This test verifies that null JSON is returned if the string binding
67 // is null, JSON value is returned when string value is valid JSON and
68 // that exception is thrown if the string is not a valid JSON.
TEST(MySqlBindingTest,getJSON)69 TEST(MySqlBindingTest, getJSON) {
70     auto binding = MySqlBinding::createNull();
71     EXPECT_FALSE(binding->getJSON());
72 
73     binding = MySqlBinding::createString("{ \"foo\": \"bar\" }");
74     auto json = binding->getJSON();
75     ASSERT_TRUE(json);
76     ASSERT_EQ(Element::map, json->getType());
77     auto foo = json->get("foo");
78     ASSERT_TRUE(foo);
79     ASSERT_EQ(Element::string, foo->getType());
80     EXPECT_EQ("bar", foo->stringValue());
81 }
82 
83 // This test verifies that default blob is returned if binding is null.
TEST(MySqlBindingTest,defaultBlob)84 TEST(MySqlBindingTest, defaultBlob) {
85     std::vector<uint8_t> blob(10, 1);
86     std::vector<uint8_t> default_blob(10, 5);
87     auto binding = MySqlBinding::createNull();
88     EXPECT_EQ(default_blob, binding->getBlobOrDefault(default_blob));
89 
90     binding = MySqlBinding::createBlob(blob.begin(), blob.end());
91     EXPECT_EQ(blob, binding->getBlobOrDefault(default_blob));
92 }
93 
94 // This test verifies that an error is thrown upon an attempt to use
95 // invalid accessor for a blob binding.
TEST(MySqlBindingTest,blobTypeMismatch)96 TEST(MySqlBindingTest, blobTypeMismatch) {
97     std::vector<uint8_t> blob(10, 1);
98     auto binding = MySqlBinding::createBlob(blob.begin(), blob.end());
99     EXPECT_NO_THROW(static_cast<void>(binding->getBlob()));
100 
101     EXPECT_THROW(static_cast<void>(binding->getString()), InvalidOperation);
102     EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
103     EXPECT_THROW(static_cast<void>(binding->getFloat()), InvalidOperation);
104     EXPECT_THROW(static_cast<void>(binding->getBool()), InvalidOperation);
105     EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
106 }
107 
108 // This test verifies that default number is returned if binding is null.
TEST(MySqlBindingTest,defaultInteger)109 TEST(MySqlBindingTest, defaultInteger) {
110     auto binding = MySqlBinding::createNull();
111     ASSERT_TRUE(binding->amNull());
112     EXPECT_EQ(123, binding->getIntegerOrDefault<uint32_t>(123));
113 
114     binding = MySqlBinding::createInteger<uint32_t>(1024);
115     ASSERT_FALSE(binding->amNull());
116     EXPECT_EQ(1024, binding->getIntegerOrDefault<uint32_t>(123));
117 }
118 
119 // This test verifies that null binding is created for unspecified number
120 // and the integer binding is created for a specified number.
TEST(MySqlBindingTest,conditionalInteger)121 TEST(MySqlBindingTest, conditionalInteger) {
122     auto binding = MySqlBinding::condCreateInteger<uint16_t>(Optional<uint16_t>());
123     EXPECT_TRUE(binding->amNull());
124 
125     binding = MySqlBinding::condCreateInteger<uint16_t>(1);
126     ASSERT_FALSE(binding->amNull());
127     EXPECT_EQ(1, binding->getInteger<uint16_t>());
128 }
129 
130 // This test verifies that an error is thrown upon an attempt to use
131 // invalid accessor for an integer binding.
TEST(MySqlBindingTest,integerTypeMismatch)132 TEST(MySqlBindingTest, integerTypeMismatch) {
133     auto binding = MySqlBinding::createInteger<uint32_t>(123);
134     EXPECT_NO_THROW(static_cast<void>(binding->getInteger<uint32_t>()));
135 
136     EXPECT_THROW(static_cast<void>(binding->getString()), InvalidOperation);
137     EXPECT_THROW(static_cast<void>(binding->getBlob()), InvalidOperation);
138     EXPECT_THROW(static_cast<void>(binding->getInteger<uint8_t>()), InvalidOperation);
139     EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
140     EXPECT_THROW(static_cast<void>(binding->getFloat()), InvalidOperation);
141     EXPECT_THROW(static_cast<void>(binding->getBool()), InvalidOperation);
142     EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
143 }
144 
145 // This test verifies that null binding is created for unspecified floating
146 // point value and the float binding is created for the specified value.
TEST(MySqlBindingTest,conditionalFloat)147 TEST(MySqlBindingTest, conditionalFloat) {
148     auto binding = MySqlBinding::condCreateFloat(Optional<float>());
149     EXPECT_TRUE(binding->amNull());
150 
151     binding = MySqlBinding::condCreateFloat<float>(1.567f);
152     ASSERT_FALSE(binding->amNull());
153     EXPECT_EQ(1.567f, binding->getFloat());
154 }
155 
156 // This test verifies that an error is thrown upon an attempt to use
157 // invalid accessor for a float binding.
TEST(MySqlBindingTest,floatTypeMismatch)158 TEST(MySqlBindingTest, floatTypeMismatch) {
159     auto binding = MySqlBinding::createFloat(123.123f);
160     EXPECT_NO_THROW(static_cast<void>(binding->getFloat()));
161 
162     EXPECT_THROW(static_cast<void>(binding->getString()), InvalidOperation);
163     EXPECT_THROW(static_cast<void>(binding->getBlob()), InvalidOperation);
164     EXPECT_THROW(static_cast<void>(binding->getInteger<uint8_t>()), InvalidOperation);
165     EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
166     EXPECT_THROW(static_cast<void>(binding->getInteger<uint32_t>()), InvalidOperation);
167     EXPECT_THROW(static_cast<void>(binding->getBool()), InvalidOperation);
168     EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
169 }
170 
171 // This test verifies that null binding is created for unspecified boolean
172 // value and the uint8_t binding is created for a specified boolean
173 // value.
TEST(MySqlBindingTest,conditionalBoolean)174 TEST(MySqlBindingTest, conditionalBoolean) {
175     auto binding = MySqlBinding::condCreateBool(Optional<bool>());
176     EXPECT_TRUE(binding->amNull());
177 
178     binding = MySqlBinding::condCreateBool(false);
179     ASSERT_FALSE(binding->amNull());
180     EXPECT_FALSE(binding->getBool());
181 
182     binding = MySqlBinding::condCreateBool(true);
183     ASSERT_FALSE(binding->amNull());
184     EXPECT_TRUE(binding->getBool());
185 }
186 
187 // This test verifies that an error is thrown upon an attempt to use
188 // invalid accessor for a float binding.
TEST(MySqlBindingTest,booleanTypeMismatch)189 TEST(MySqlBindingTest, booleanTypeMismatch) {
190     auto binding = MySqlBinding::createBool(false);
191     EXPECT_NO_THROW(static_cast<void>(binding->getBool()));
192     EXPECT_NO_THROW(static_cast<void>(binding->getInteger<uint8_t>()));
193 
194     EXPECT_THROW(static_cast<void>(binding->getString()), InvalidOperation);
195     EXPECT_THROW(static_cast<void>(binding->getBlob()), InvalidOperation);
196     EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
197     EXPECT_THROW(static_cast<void>(binding->getInteger<uint32_t>()), InvalidOperation);
198     EXPECT_THROW(static_cast<void>(binding->getFloat()), InvalidOperation);
199     EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
200 }
201 
202 // This test verifies that null binding is created for unspecified address
203 // and the uint32_t binding is created for the specified address.
TEST(MySqlBindingTest,conditionalIPv4Address)204 TEST(MySqlBindingTest, conditionalIPv4Address) {
205     auto binding = MySqlBinding::condCreateIPv4Address(Optional<IOAddress>());
206     EXPECT_TRUE(binding->amNull());
207 
208     binding = MySqlBinding::condCreateIPv4Address(IOAddress("192.0.2.1"));
209     ASSERT_FALSE(binding->amNull());
210     EXPECT_EQ(0xC0000201, binding->getInteger<uint32_t>());
211 
212     EXPECT_THROW(MySqlBinding::condCreateIPv4Address(IOAddress("2001:db8:1::1")),
213                  isc::BadValue);
214 }
215 
216 // This test verifies that default timestamp is returned if binding is null.
TEST(MySqlBindingTest,defaultTimestamp)217 TEST(MySqlBindingTest, defaultTimestamp) {
218     boost::posix_time::ptime current_time = boost::posix_time::second_clock::local_time();
219     boost::posix_time::ptime past_time = current_time - boost::posix_time::hours(1);
220 
221     auto binding = MySqlBinding::createNull();
222     EXPECT_TRUE(past_time == binding->getTimestampOrDefault(past_time));
223 
224     binding = MySqlBinding::createTimestamp(current_time);
225     EXPECT_TRUE(current_time == binding->getTimestampOrDefault(past_time));
226 }
227 
228 // This test verifies that the binding preserves fractional seconds in
229 // millisecond precision.
230 /// @todo This test is disabled until we decide that the minimum
231 /// supported MySQL version has a fractional seconds precision.
TEST(MySqlBindingTest,DISABLED_millisecondTimestampPrecision)232 TEST(MySqlBindingTest, DISABLED_millisecondTimestampPrecision) {
233     // Set timestamp of 2019-01-28 01:12:10.123
234 
235     // Fractional part depends on the clock resolution.
236     long fractional = 123*(boost::posix_time::time_duration::ticks_per_second()/1000);
237     boost::posix_time::ptime
238         test_time(boost::gregorian::date(2019, boost::gregorian::Jan, 28),
239                   boost::posix_time::time_duration(1, 12, 10, fractional));
240 
241     auto binding = MySqlBinding::createTimestamp(test_time);
242 
243     boost::posix_time::ptime returned_test_time = binding->getTimestamp();
244 
245     EXPECT_EQ(returned_test_time, test_time);
246 }
247 
248 }
249 
250