1 /*
2  * Copyright (c) 2015, 2021, Oracle and/or its affiliates.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License, version 2.0,
6  * as published by the Free Software Foundation.
7  *
8  * This program is also distributed with certain software (including
9  * but not limited to OpenSSL) that is licensed under separate terms,
10  * as designated in a particular file or component or in included license
11  * documentation.  The authors of MySQL hereby grant you an additional
12  * permission to link the program and your derivative works with the
13  * separately licensed software that they have included with MySQL.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License, version 2.0, for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23  * 02110-1301  USA
24  */
25 
26 #include "expect.h"
27 #include "xpl_error.h"
28 #include "ngs_common/protocol_protobuf.h"
29 //#include "expect_gtid.h"
30 
31 using namespace xpl;
32 
33 // :docnote:
34 // NO_ERROR means "enable exceptions", meaning any error that happens inside a block
35 // will cause all subsequent statements to fail until the matching close is found.
36 //
37 // Nesting behaviour:
38 //
39 // Case 1: No_error
40 //
41 // open(NO_ERROR) - ok
42 //   stmt1 - ok
43 //   stmt2 - error
44 //   stmt3 - fail
45 // close() - fail
46 //
47 // Case 2: Plain
48 //
49 // open() - ok
50 //   stmt1 - ok
51 //   stmt2 - error
52 //   stmt3 - ok
53 // close() - ok
54 //
55 // Case 3: No_error nested within no_error
56 //
57 // 3.1: error in outer block fails the whole thing
58 // open(NO_ERROR) - ok
59 //   stmt1 - error
60 //   open(NO_ERROR) - fail
61 //     stmt2 - fail
62 //   close() - fail
63 //   stmt3 - fail
64 // close() - fail
65 //
66 // 3.2: error propagates up and fails the outer block
67 // open(NO_ERROR) - ok
68 //   stmt1 - ok
69 //   open(NO_ERROR) - ok
70 //     stmt2 - error
71 //   close() - fail
72 //   stmt3 - fail
73 // close() - fail
74 //
75 // Case 4: Plain nested within no_error
76 //
77 // 4.1: same as 3.1
78 // open(NO_ERROR) - ok
79 //   stmt1 - error
80 //   open() - fail
81 //     stmt2 - fail
82 //   close() - fail
83 //   stmt3 - fail
84 // close() - fail
85 //
86 // 4.2: plain block effectively "catches" the error and prevents it from failing the outer block
87 // open(NO_ERROR) - ok
88 //   stmt1 - ok
89 //   open() - ok
90 //     stmt2 - error
91 //   close() - ok
92 //   stmt3 - ok
93 // close() - ok
94 //
95 // Case 5: No_error nested within Plain
96 //
97 // 5.1: trivial
98 // open() - ok
99 //   stmt1 - error
100 //   open(NO_ERROR) - ok
101 //     stmt2 - ok
102 //   close() - ok
103 //   stmt3 - ok
104 // close() - ok
105 //
106 // 5.2: error propagates up, but is ignored by the outer block
107 // open() - ok
108 //   stmt1 - ok
109 //   open(NO_ERROR) - ok
110 //     stmt2 - error
111 //   close() - fail
112 //   stmt3 - ok
113 // close() - ok
114 //
115 // Case 6: Plain nested within plain: trivial, behaves like a flat plain block
116 //
117 
118 
119 static const unsigned EXPECT_NO_ERROR = 1;
120 //static const int EXPECT_SCHEMA_VERSION = 2; // not supported yet
121 //static const int EXPECT_GTID_EXECUTED_CONTAINS = 3;
122 //static const int EXPECT_GTID_WAIT_LESS_THAN = 4;
123 
124 
125 
126 // if (pre_client_stmt(msgid))
127 // {
128 //   error = ExecuteCommand()
129 //   post_client_stmt(msgid, error)
130 // }
131 
132 
Expectation(const Expectation & other)133 Expectation::Expectation(const Expectation &other)
134 : m_failed(other.m_failed), m_fail_on_error(other.m_fail_on_error), m_gtid_wait_less_than(0) // this is instance specific data, don't copy it
135 {
136   for (std::list<Expect_condition*>::const_iterator cond = other.m_conditions.begin();
137        cond != other.m_conditions.end(); ++cond)
138   {
139     m_conditions.push_back((*cond)->copy());
140   }
141 }
142 
143 
swap(Expectation & one,Expectation & other)144 void Expectation::swap(Expectation &one, Expectation &other)
145 {
146   using std::swap;
147   swap(one.m_failed, other.m_failed);
148   swap(one.m_fail_on_error, other.m_fail_on_error);
149   swap(one.m_conditions, other.m_conditions);
150 }
151 
152 
operator =(const Expectation & other)153 Expectation &Expectation::operator =(const Expectation &other)
154 {
155   Expectation tmp(other);
156   swap(*this, tmp);
157   return *this;
158 }
159 
160 
~Expectation()161 Expectation::~Expectation()
162 {
163   for (std::list<Expect_condition*>::iterator cond = m_conditions.begin();
164        cond != m_conditions.end(); ++cond)
165     delete *cond;
166 }
167 
168 
check()169 ngs::Error_code Expectation::check()
170 {
171   for (std::list<Expect_condition*>::const_iterator cond = m_conditions.begin();
172        cond != m_conditions.end(); ++cond)
173   {
174     ngs::Error_code error((*cond)->check());
175     if (error)
176       return error;
177   }
178   return ngs::Error_code();
179 }
180 
181 
unset(uint32_t key)182 void Expectation::unset(uint32_t key)
183 {
184   if (key == EXPECT_NO_ERROR)
185   {
186     m_fail_on_error = false;
187     return;
188   }
189 
190   for (std::list<Expect_condition*>::iterator cond = m_conditions.begin();
191        cond != m_conditions.end(); ++cond)
192   {
193     if ((*cond)->key() == key)
194     {
195       delete *cond;
196       m_conditions.erase(cond);
197       break;
198     }
199   }
200 }
201 
202 
add_condition(Expect_condition * cond)203 void Expectation::add_condition(Expect_condition *cond)
204 {
205   m_conditions.push_back(cond);
206 }
207 
208 
set(uint32_t key,const std::string & value)209 ngs::Error_code Expectation::set(uint32_t key, const std::string &value)
210 {
211   switch (key)
212   {
213     case EXPECT_NO_ERROR:
214       if (value == "1" || value.empty())
215         m_fail_on_error = true;
216       else if (value == "0")
217         m_fail_on_error = false;
218       else
219         return ngs::Error_code(ER_X_EXPECT_BAD_CONDITION_VALUE, "Invalid value '"+value+"' for expectation no_error");
220       break;
221 /*
222     case EXPECT_GTID_EXECUTED_CONTAINS:
223     {
224       Expect_gtid *gtid;
225       add_condition(gtid = new Expect_gtid(value));
226       gtid->set_key(key);
227       // timeout was set first
228       if (m_gtid_wait_less_than > 0)
229         gtid->set_timeout(m_gtid_wait_less_than);
230       break;
231     }
232     case EXPECT_GTID_WAIT_LESS_THAN:
233       m_gtid_wait_less_than = ngs::stoi(value);
234       for (std::list<Expect_condition*>::iterator cond = m_conditions.begin();
235            cond != m_conditions.end(); ++cond)
236       {
237         if ((*cond)->key() == EXPECT_GTID_EXECUTED_CONTAINS)
238         {
239           static_cast<Expect_gtid*>(*cond)->set_timeout(m_gtid_wait_less_than);
240           break;
241         }
242       }
243       break;
244 */
245     default:
246       return ngs::Error_code(ER_X_EXPECT_BAD_CONDITION, "Unknown condition key");
247   }
248   return ngs::Error_code();
249 }
250 
251 
252 
253 
Expectation_stack()254 Expectation_stack::Expectation_stack()
255 {
256   m_expect_stack.reserve(4);
257 }
258 
259 
open(const Mysqlx::Expect::Open & open)260 ngs::Error_code Expectation_stack::open(const Mysqlx::Expect::Open &open)
261 {
262   ngs::Error_code error;
263   Expectation expect;
264 
265   // if we're in a failed expect block, push an empty failed expectation to the stack
266   // so that it can be popped when the matching close is seen.
267   // No other evaluations are done in a failed state.
268   if (!m_expect_stack.empty())
269   {
270     if (m_expect_stack.back().failed())
271     {
272       expect.set_failed(m_expect_stack.back().failed_condition());
273       m_expect_stack.push_back(expect);
274       return ngs::Error_code(ER_X_EXPECT_FAILED, "Expectation failed: "+expect.failed_condition());
275     }
276 
277     if (open.op() == Mysqlx::Expect::Open::EXPECT_CTX_COPY_PREV)
278       expect = m_expect_stack.back();
279   }
280 
281   for (int i = 0; i < open.cond_size(); i++)
282   {
283     const Mysqlx::Expect::Open::Condition &cond(open.cond(i));
284     switch (cond.op())
285     {
286       case Mysqlx::Expect::Open::Condition::EXPECT_OP_SET:
287         if (!cond.has_condition_value())
288           error = expect.set(cond.condition_key(), "");
289         else
290           error = expect.set(cond.condition_key(), cond.condition_value());
291         break;
292       case Mysqlx::Expect::Open::Condition::EXPECT_OP_UNSET:
293         expect.unset(cond.condition_key());
294         break;
295     }
296     if (error)
297       return error;
298   }
299   // we need to add the expectation block even if an error occurred,
300   // otherwise we'll get mismatched open/close blocks
301   // on_error should get called afterwards with this error, which should fail the rest of the block
302   m_expect_stack.push_back(expect);
303 
304   // now check for the expected conditions
305   // this may block if a blocking condition is found
306   if (!error)
307     error = expect.check();
308 
309   return error;
310 }
311 
312 
close()313 ngs::Error_code Expectation_stack::close()
314 {
315   if (m_expect_stack.empty())
316     return ngs::Error_code(ER_X_EXPECT_NOT_OPEN, "Expect block currently not open");
317 
318   if (m_expect_stack.back().failed())
319   {
320     std::string cond = m_expect_stack.back().failed_condition();
321     m_expect_stack.pop_back();
322     return ngs::Error_code(ER_X_EXPECT_FAILED, "Expectation failed: " + cond);
323   }
324 
325   m_expect_stack.pop_back();
326 
327   return ngs::Error_code();
328 }
329 
330 
331 // called before executing client statements
pre_client_stmt(int8_t msgid)332 ngs::Error_code Expectation_stack::pre_client_stmt(int8_t msgid)
333 {
334   if (!m_expect_stack.empty())
335   {
336     if (m_expect_stack.back().failed())
337     {
338       // special handling for nested expect blocks
339       // if a block open or close arrives in a failed state, we let it through
340       // so that they can be pushed/popped on the stack and properly accounted for
341       if (msgid != Mysqlx::ClientMessages::EXPECT_OPEN && msgid != Mysqlx::ClientMessages::EXPECT_CLOSE)
342         return ngs::Error_code(ER_X_EXPECT_FAILED, "Expectation failed: " + m_expect_stack.back().failed_condition());
343     }
344   }
345   return ngs::Error_code();
346 }
347 
348 
349 // called after executing client statements
post_client_stmt(int8_t msgid,const ngs::Error_code & error)350 void Expectation_stack::post_client_stmt(int8_t msgid, const ngs::Error_code &error)
351 {
352   if (error && !m_expect_stack.empty() && m_expect_stack.back().fail_on_error())
353     m_expect_stack.back().set_failed("no_error");
354 }
355