Merge lp:~dshrews/drizzle/bug660779 into lp:~drizzle-trunk/drizzle/development

Proposed by David Shrewsbury
Status: Merged
Approved by: Brian Aker
Approved revision: 1869
Merge reported by: Lee Bieber
Merged at revision: not available
Proposed branch: lp:~dshrews/drizzle/bug660779
Merge into: lp:~drizzle-trunk/drizzle/development
Diff against target: 716 lines (+512/-48)
9 files modified
drizzled/session.cc (+14/-14)
drizzled/session.h (+45/-24)
drizzled/sql_insert.cc (+9/-0)
drizzled/sql_update.cc (+22/-8)
drizzled/transaction_services.cc (+121/-2)
drizzled/transaction_services.h (+22/-0)
plugin/transaction_log/tests/r/bug660779.result (+220/-0)
plugin/transaction_log/tests/t/bug660779-master.opt (+1/-0)
plugin/transaction_log/tests/t/bug660779.test (+58/-0)
To merge this branch: bzr merge lp:~dshrews/drizzle/bug660779
Reviewer Review Type Date Requested Status
Drizzle Merge Team Pending
Review via email: mp+39375@code.launchpad.net

This proposal supersedes a proposal from 2010-10-25.

Description of the change

Provides methods in TransactionServices to undo adding records to the current Statement message when a multi-row affecting statement fails mid-execution. Call this method for UPDATEs and INSERT..SELECTs that fail.

Also cleans up some #includes in session.* and adds some comments for the st_transactions struct in session.h.

To post a comment you must log in.
Revision history for this message
Lee Bieber (kalebral-deactivatedaccount) wrote : Posted in a previous version of this proposal
Revision history for this message
David Shrewsbury (dshrews) wrote : Posted in a previous version of this proposal

Grr... working on it...

Revision history for this message
David Shrewsbury (dshrews) wrote :

Solaris succeeded in param-build.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'drizzled/session.cc'
2--- drizzled/session.cc 2010-10-22 22:08:54 +0000
3+++ drizzled/session.cc 2010-10-26 14:47:53 +0000
4@@ -22,21 +22,21 @@
5 */
6
7 #include "config.h"
8-#include <drizzled/session.h>
9+#include "drizzled/session.h"
10 #include "drizzled/session_list.h"
11 #include <sys/stat.h>
12-#include <drizzled/error.h>
13-#include <drizzled/gettext.h>
14-#include <drizzled/query_id.h>
15-#include <drizzled/data_home.h>
16-#include <drizzled/sql_base.h>
17-#include <drizzled/lock.h>
18-#include <drizzled/item/cache.h>
19-#include <drizzled/item/float.h>
20-#include <drizzled/item/return_int.h>
21-#include <drizzled/item/empty_string.h>
22-#include <drizzled/show.h>
23-#include <drizzled/plugin/client.h>
24+#include "drizzled/error.h"
25+#include "drizzled/gettext.h"
26+#include "drizzled/query_id.h"
27+#include "drizzled/data_home.h"
28+#include "drizzled/sql_base.h"
29+#include "drizzled/lock.h"
30+#include "drizzled/item/cache.h"
31+#include "drizzled/item/float.h"
32+#include "drizzled/item/return_int.h"
33+#include "drizzled/item/empty_string.h"
34+#include "drizzled/show.h"
35+#include "drizzled/plugin/client.h"
36 #include "drizzled/plugin/scheduler.h"
37 #include "drizzled/plugin/authentication.h"
38 #include "drizzled/plugin/logging.h"
39@@ -60,7 +60,7 @@
40 #include <fcntl.h>
41 #include <algorithm>
42 #include <climits>
43-#include "boost/filesystem.hpp"
44+#include <boost/filesystem.hpp>
45
46 using namespace std;
47
48
49=== modified file 'drizzled/session.h'
50--- drizzled/session.h 2010-10-23 22:18:01 +0000
51+++ drizzled/session.h 2010-10-26 14:47:53 +0000
52@@ -21,23 +21,20 @@
53 #ifndef DRIZZLED_SESSION_H
54 #define DRIZZLED_SESSION_H
55
56-/* Classes in mysql */
57-
58 #include "drizzled/plugin.h"
59-#include <drizzled/sql_locale.h>
60+#include "drizzled/sql_locale.h"
61 #include "drizzled/resource_context.h"
62-#include <drizzled/cursor.h>
63-#include <drizzled/current_session.h>
64-#include <drizzled/sql_error.h>
65-#include <drizzled/file_exchange.h>
66-#include <drizzled/select_result_interceptor.h>
67-#include <drizzled/statistics_variables.h>
68-#include <drizzled/xid.h>
69+#include "drizzled/cursor.h"
70+#include "drizzled/current_session.h"
71+#include "drizzled/sql_error.h"
72+#include "drizzled/file_exchange.h"
73+#include "drizzled/select_result_interceptor.h"
74+#include "drizzled/statistics_variables.h"
75+#include "drizzled/xid.h"
76 #include "drizzled/query_id.h"
77 #include "drizzled/named_savepoint.h"
78 #include "drizzled/transaction_context.h"
79 #include "drizzled/util/storable.h"
80-
81 #include "drizzled/my_hash.h"
82
83 #include <netdb.h>
84@@ -47,14 +44,11 @@
85 #include <deque>
86
87 #include "drizzled/internal/getrusage.h"
88-
89-#include <drizzled/security_context.h>
90-#include <drizzled/open_tables_state.h>
91-
92-#include <drizzled/internal_error_handler.h>
93-#include <drizzled/diagnostics_area.h>
94-
95-#include <drizzled/plugin/authorization.h>
96+#include "drizzled/security_context.h"
97+#include "drizzled/open_tables_state.h"
98+#include "drizzled/internal_error_handler.h"
99+#include "drizzled/diagnostics_area.h"
100+#include "drizzled/plugin/authorization.h"
101
102 #include <boost/unordered_map.hpp>
103 #include <boost/thread/mutex.hpp>
104@@ -550,10 +544,38 @@
105 ResourceContext *getResourceContext(const plugin::MonitoredInTransaction *monitored,
106 size_t index= 0);
107
108+ /**
109+ * Structure used to manage "statement transactions" and
110+ * "normal transactions". In autocommit mode, the normal transaction is
111+ * equivalent to the statement transaction.
112+ *
113+ * Storage engines will be registered here when they participate in
114+ * a transaction. No engine is registered more than once.
115+ */
116 struct st_transactions {
117 std::deque<NamedSavepoint> savepoints;
118- TransactionContext all; ///< Trans since BEGIN WORK
119- TransactionContext stmt; ///< Trans for current statement
120+
121+ /**
122+ * The normal transaction (since BEGIN WORK).
123+ *
124+ * Contains a list of all engines that have participated in any of the
125+ * statement transactions started within the context of the normal
126+ * transaction.
127+ *
128+ * @note In autocommit mode, this is empty.
129+ */
130+ TransactionContext all;
131+
132+ /**
133+ * The statment transaction.
134+ *
135+ * Contains a list of all engines participating in the given statement.
136+ *
137+ * @note In autocommit mode, this will be used to commit/rollback the
138+ * normal transaction.
139+ */
140+ TransactionContext stmt;
141+
142 XID_STATE xid_state;
143
144 void cleanup()
145@@ -1197,9 +1219,8 @@
146 * Current implementation does not depend on that, but future changes
147 * should be done with this in mind;
148 *
149- * @param Scrambled password received from client
150- * @param Length of scrambled password
151- * @param Database name to connect to, may be NULL
152+ * @param passwd Scrambled password received from client
153+ * @param db Database name to connect to, may be NULL
154 */
155 bool checkUser(const std::string &passwd, const std::string &db);
156
157
158=== modified file 'drizzled/sql_insert.cc'
159--- drizzled/sql_insert.cc 2010-10-24 19:36:55 +0000
160+++ drizzled/sql_insert.cc 2010-10-26 14:47:53 +0000
161@@ -1276,7 +1276,16 @@
162 store_values(values);
163 session->count_cuted_fields= CHECK_FIELD_IGNORE;
164 if (session->is_error())
165+ {
166+ /*
167+ * If we fail mid-way through INSERT..SELECT, we need to remove any
168+ * records that we added to the current Statement message. We can
169+ * use session->row_count to know how many records we have already added.
170+ */
171+ TransactionServices &ts= TransactionServices::singleton();
172+ ts.removeStatementRecords(session, (session->row_count - 1));
173 return(1);
174+ }
175
176 // Release latches in case bulk insert takes a long time
177 plugin::TransactionalStorageEngine::releaseTemporaryLatches(session);
178
179=== modified file 'drizzled/sql_update.cc'
180--- drizzled/sql_update.cc 2010-10-24 01:34:39 +0000
181+++ drizzled/sql_update.cc 2010-10-26 14:47:53 +0000
182@@ -29,6 +29,7 @@
183 #include "drizzled/records.h"
184 #include "drizzled/internal/my_sys.h"
185 #include "drizzled/internal/iocache.h"
186+#include "drizzled/transaction_services.h"
187
188 #include <boost/dynamic_bitset.hpp>
189 #include <list>
190@@ -473,7 +474,20 @@
191
192 table->storeRecord();
193 if (fill_record(session, fields, values))
194+ {
195+ /*
196+ * If we updated some rows before this one failed (updated > 0),
197+ * then we will need to undo adding those records to the
198+ * replication Statement message.
199+ */
200+ if (updated > 0)
201+ {
202+ TransactionServices &ts= TransactionServices::singleton();
203+ ts.removeStatementRecords(session, updated);
204+ }
205+
206 break;
207+ }
208
209 found++;
210
211@@ -486,15 +500,15 @@
212 table->auto_increment_field_not_null= false;
213
214 if (!error || error == HA_ERR_RECORD_IS_THE_SAME)
215- {
216+ {
217 if (error != HA_ERR_RECORD_IS_THE_SAME)
218 updated++;
219 else
220 error= 0;
221- }
222- else if (! ignore ||
223+ }
224+ else if (! ignore ||
225 table->cursor->is_fatal_error(error, HA_CHECK_DUP_KEY))
226- {
227+ {
228 /*
229 If (ignore && error is ignorable) we don't have to
230 do anything; otherwise...
231@@ -505,10 +519,10 @@
232 flags|= ME_FATALERROR; /* Other handler errors are fatal */
233
234 prepare_record_for_error_message(error, table);
235- table->print_error(error,MYF(flags));
236- error= 1;
237- break;
238- }
239+ table->print_error(error,MYF(flags));
240+ error= 1;
241+ break;
242+ }
243 }
244
245 if (!--limit && using_limit)
246
247=== modified file 'drizzled/transaction_services.cc'
248--- drizzled/transaction_services.cc 2010-10-11 17:20:14 +0000
249+++ drizzled/transaction_services.cc 2010-10-26 14:47:53 +0000
250@@ -71,11 +71,13 @@
251 #include "drizzled/plugin/xa_resource_manager.h"
252 #include "drizzled/internal/my_sys.h"
253
254-using namespace std;
255-
256 #include <vector>
257 #include <algorithm>
258 #include <functional>
259+#include <google/protobuf/repeated_field.h>
260+
261+using namespace std;
262+using namespace google;
263
264 namespace drizzled
265 {
266@@ -1826,6 +1828,123 @@
267 }
268 }
269
270+
271+/**
272+ * Template for removing Statement records of different types.
273+ *
274+ * The code for removing records from different Statement message types
275+ * is identical except for the class types that are embedded within the
276+ * Statement.
277+ *
278+ * There are 3 scenarios we need to look for:
279+ * - We've been asked to remove more records than exist in the Statement
280+ * - We've been asked to remove less records than exist in the Statement
281+ * - We've been asked to remove ALL records that exist in the Statement
282+ *
283+ * If we are removing ALL records, then effectively we would be left with
284+ * an empty Statement message, so we should just remove it and clean up
285+ * message pointers in the Session object.
286+ */
287+template <class DataType, class RecordType>
288+static bool removeStatementRecordsWithType(Session *session,
289+ DataType *data,
290+ uint32_t count)
291+{
292+ uint32_t num_avail_recs= static_cast<uint32_t>(data->record_size());
293+
294+ /* If there aren't enough records to remove 'count' of them, error. */
295+ if (num_avail_recs < count)
296+ return false;
297+
298+ /*
299+ * If we are removing all of the data records, we'll just remove this
300+ * entire Statement message.
301+ */
302+ if (num_avail_recs == count)
303+ {
304+ message::Transaction *transaction= session->getTransactionMessage();
305+ protobuf::RepeatedPtrField<message::Statement> *statements= transaction->mutable_statement();
306+ statements->RemoveLast();
307+
308+ /*
309+ * Now need to set the Session Statement pointer to either the previous
310+ * Statement, or NULL if there isn't one.
311+ */
312+ if (statements->size() == 0)
313+ {
314+ session->setStatementMessage(NULL);
315+ }
316+ else
317+ {
318+ /*
319+ * There isn't a great way to get a pointer to the previous Statement
320+ * message using the RepeatedPtrField object, so we'll just get to it
321+ * using the Transaction message.
322+ */
323+ int last_stmt_idx= transaction->statement_size() - 1;
324+ session->setStatementMessage(transaction->mutable_statement(last_stmt_idx));
325+ }
326+ }
327+ /* We only need to remove 'count' records */
328+ else if (num_avail_recs > count)
329+ {
330+ protobuf::RepeatedPtrField<RecordType> *records= data->mutable_record();
331+ while (count--)
332+ records->RemoveLast();
333+ }
334+
335+ return true;
336+}
337+
338+
339+bool TransactionServices::removeStatementRecords(Session *session,
340+ uint32_t count)
341+{
342+ ReplicationServices &replication_services= ReplicationServices::singleton();
343+ if (! replication_services.isActive())
344+ return false;
345+
346+ /* Get the most current Statement */
347+ message::Statement *statement= session->getStatementMessage();
348+
349+ /* Make sure we have work to do */
350+ if (statement == NULL)
351+ return false;
352+
353+ bool retval= false;
354+
355+ switch (statement->type())
356+ {
357+ case message::Statement::INSERT:
358+ {
359+ message::InsertData *data= statement->mutable_insert_data();
360+ retval= removeStatementRecordsWithType<message::InsertData, message::InsertRecord>(session, data, count);
361+ break;
362+ }
363+
364+ case message::Statement::UPDATE:
365+ {
366+ message::UpdateData *data= statement->mutable_update_data();
367+ retval= removeStatementRecordsWithType<message::UpdateData, message::UpdateRecord>(session, data, count);
368+ break;
369+ }
370+
371+ case message::Statement::DELETE: /* not sure if this one is possible... */
372+ {
373+ message::DeleteData *data= statement->mutable_delete_data();
374+ retval= removeStatementRecordsWithType<message::DeleteData, message::DeleteRecord>(session, data, count);
375+ break;
376+ }
377+
378+ default:
379+ retval= false;
380+ break;
381+ }
382+
383+ return retval;
384+}
385+
386+
387 void TransactionServices::createTable(Session *in_session,
388 const message::Table &table)
389 {
390
391=== modified file 'drizzled/transaction_services.h'
392--- drizzled/transaction_services.h 2010-10-11 17:20:14 +0000
393+++ drizzled/transaction_services.h 2010-10-26 14:47:53 +0000
394@@ -260,6 +260,28 @@
395 * @param use_update_record If true, uses the values from the update row instead
396 */
397 void deleteRecord(Session *in_session, Table *in_table, bool use_update_record= false);
398+
399+ /**
400+ * Used to undo effects of a failed statement.
401+ *
402+ * An SQL statement, like an UPDATE, that affects multiple rows could
403+ * potentially fail mid-way through processing the rows. In such a case,
404+ * the successfully modified rows that preceeded the failing row would
405+ * have been added to the Statement message. This method is used for
406+ * rolling back that change.
407+ *
408+ * @note
409+ * This particular failure is seen on column constraint violations
410+ * during a multi-row UPDATE and a multi-row INSERT..SELECT.
411+ *
412+ * @param in_session Pointer to the Session containing the Statement
413+ * @param count The number of records to remove from Statement.
414+ *
415+ * @retval true Successfully removed 'count' records
416+ * @retval false Failure
417+ */
418+ bool removeStatementRecords(Session *in_session, uint32_t count);
419+
420 /**
421 * Creates a CreateSchema Statement GPB message and adds it
422 * to the Session's active Transaction GPB message for pushing
423
424=== added file 'plugin/transaction_log/tests/r/bug660779.result'
425--- plugin/transaction_log/tests/r/bug660779.result 1970-01-01 00:00:00 +0000
426+++ plugin/transaction_log/tests/r/bug660779.result 2010-10-26 14:47:53 +0000
427@@ -0,0 +1,220 @@
428+CREATE TABLE t1 (
429+pk INT NOT NULL AUTO_INCREMENT,
430+col_int1 INT,
431+col_int2 INT,
432+col_int_not_null INT NOT NULL,
433+PRIMARY KEY (pk));
434+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (1,1,1);
435+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (NULL,1,1);
436+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (2,1,3);
437+SET GLOBAL transaction_log_truncate_debug= true;
438+BEGIN;
439+UPDATE t1 SET col_int_not_null = col_int1 WHERE col_int2 = 1;
440+ERROR 23000: Column 'col_int_not_null' cannot be null
441+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (5,5,5);
442+COMMIT;
443+
444+We should have a Transaction with a single insert Statement
445+SELECT PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS));
446+PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS))
447+transaction_context {
448+ server_id: 1
449+ transaction_id: 1
450+ START_TIMESTAMP
451+ END_TIMESTAMP
452+}
453+statement {
454+ type: INSERT
455+ START_TIMESTAMP
456+ END_TIMESTAMP
457+ insert_header {
458+ table_metadata {
459+ schema_name: "test"
460+ table_name: "t1"
461+ }
462+ field_metadata {
463+ type: INTEGER
464+ name: "pk"
465+ }
466+ field_metadata {
467+ type: INTEGER
468+ name: "col_int1"
469+ }
470+ field_metadata {
471+ type: INTEGER
472+ name: "col_int2"
473+ }
474+ field_metadata {
475+ type: INTEGER
476+ name: "col_int_not_null"
477+ }
478+ }
479+ insert_data {
480+ segment_id: 1
481+ end_segment: true
482+ record {
483+ insert_value: "4"
484+ insert_value: "5"
485+ insert_value: "5"
486+ insert_value: "5"
487+ is_null: false
488+ is_null: false
489+ is_null: false
490+ is_null: false
491+ }
492+ }
493+}
494+
495+BEGIN;
496+UPDATE t1 SET col_int1 = (col_int1 + 1) WHERE col_int2 = 1;
497+UPDATE t1 SET col_int_not_null = col_int1 WHERE col_int2 = 1;
498+ERROR 23000: Column 'col_int_not_null' cannot be null
499+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (6,6,6);
500+COMMIT;
501+
502+We should have a Transaction with 1 update and 1 insert Statement
503+SELECT PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS));
504+PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS))
505+transaction_context {
506+ server_id: 1
507+ transaction_id: 2
508+ START_TIMESTAMP
509+ END_TIMESTAMP
510+}
511+statement {
512+ type: UPDATE
513+ START_TIMESTAMP
514+ END_TIMESTAMP
515+ update_header {
516+ table_metadata {
517+ schema_name: "test"
518+ table_name: "t1"
519+ }
520+ key_field_metadata {
521+ type: INTEGER
522+ name: "pk"
523+ }
524+ set_field_metadata {
525+ type: INTEGER
526+ name: "col_int1"
527+ }
528+ }
529+ update_data {
530+ segment_id: 1
531+ end_segment: true
532+ record {
533+ key_value: "1"
534+ after_value: "2"
535+ is_null: false
536+ }
537+ record {
538+ key_value: "3"
539+ after_value: "3"
540+ is_null: false
541+ }
542+ }
543+}
544+statement {
545+ type: INSERT
546+ START_TIMESTAMP
547+ END_TIMESTAMP
548+ insert_header {
549+ table_metadata {
550+ schema_name: "test"
551+ table_name: "t1"
552+ }
553+ field_metadata {
554+ type: INTEGER
555+ name: "pk"
556+ }
557+ field_metadata {
558+ type: INTEGER
559+ name: "col_int1"
560+ }
561+ field_metadata {
562+ type: INTEGER
563+ name: "col_int2"
564+ }
565+ field_metadata {
566+ type: INTEGER
567+ name: "col_int_not_null"
568+ }
569+ }
570+ insert_data {
571+ segment_id: 1
572+ end_segment: true
573+ record {
574+ insert_value: "5"
575+ insert_value: "6"
576+ insert_value: "6"
577+ insert_value: "6"
578+ is_null: false
579+ is_null: false
580+ is_null: false
581+ is_null: false
582+ }
583+ }
584+}
585+
586+CREATE TABLE t2 (pk INT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT);
587+INSERT INTO t2 (a) VALUES (1),(2), (NULL);
588+BEGIN;
589+INSERT INTO t1 (col_int_not_null) SELECT a FROM t2;
590+ERROR 23000: Column 'col_int_not_null' cannot be null
591+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (7,7,7);
592+COMMIT;
593+
594+We should have a Transaction with 1 insert Statement
595+SELECT PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS));
596+PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS))
597+transaction_context {
598+ server_id: 1
599+ transaction_id: 5
600+ START_TIMESTAMP
601+ END_TIMESTAMP
602+}
603+statement {
604+ type: INSERT
605+ START_TIMESTAMP
606+ END_TIMESTAMP
607+ insert_header {
608+ table_metadata {
609+ schema_name: "test"
610+ table_name: "t1"
611+ }
612+ field_metadata {
613+ type: INTEGER
614+ name: "pk"
615+ }
616+ field_metadata {
617+ type: INTEGER
618+ name: "col_int1"
619+ }
620+ field_metadata {
621+ type: INTEGER
622+ name: "col_int2"
623+ }
624+ field_metadata {
625+ type: INTEGER
626+ name: "col_int_not_null"
627+ }
628+ }
629+ insert_data {
630+ segment_id: 1
631+ end_segment: true
632+ record {
633+ insert_value: "8"
634+ insert_value: "7"
635+ insert_value: "7"
636+ insert_value: "7"
637+ is_null: false
638+ is_null: false
639+ is_null: false
640+ is_null: false
641+ }
642+ }
643+}
644+
645+DROP TABLE t1;
646+DROP TABLE t2;
647+SET GLOBAL transaction_log_truncate_debug= true;
648
649=== added file 'plugin/transaction_log/tests/t/bug660779-master.opt'
650--- plugin/transaction_log/tests/t/bug660779-master.opt 1970-01-01 00:00:00 +0000
651+++ plugin/transaction_log/tests/t/bug660779-master.opt 2010-10-26 14:47:53 +0000
652@@ -0,0 +1,1 @@
653+--transaction-log.enable --scheduler=multi_thread
654
655=== added file 'plugin/transaction_log/tests/t/bug660779.test'
656--- plugin/transaction_log/tests/t/bug660779.test 1970-01-01 00:00:00 +0000
657+++ plugin/transaction_log/tests/t/bug660779.test 2010-10-26 14:47:53 +0000
658@@ -0,0 +1,58 @@
659+CREATE TABLE t1 (
660+ pk INT NOT NULL AUTO_INCREMENT,
661+ col_int1 INT,
662+ col_int2 INT,
663+ col_int_not_null INT NOT NULL,
664+ PRIMARY KEY (pk));
665+
666+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (1,1,1);
667+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (NULL,1,1);
668+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (2,1,3);
669+
670+SET GLOBAL transaction_log_truncate_debug= true;
671+
672+--test with no previous Statement message
673+BEGIN;
674+--ERROR ER_BAD_NULL_ERROR
675+UPDATE t1 SET col_int_not_null = col_int1 WHERE col_int2 = 1;
676+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (5,5,5);
677+COMMIT;
678+
679+--echo
680+--echo We should have a Transaction with a single insert Statement
681+--replace_regex /start_timestamp: [0-9]+/START_TIMESTAMP/g /end_timestamp: [0-9]+/END_TIMESTAMP/g /creation_timestamp: [0-9]+/CREATE_TIMESTAMP/ /update_timestamp: [0-9]+/UPDATE_TIMESTAMP/
682+
683+SELECT PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS));
684+
685+BEGIN;
686+UPDATE t1 SET col_int1 = (col_int1 + 1) WHERE col_int2 = 1;
687+--ERROR ER_BAD_NULL_ERROR
688+UPDATE t1 SET col_int_not_null = col_int1 WHERE col_int2 = 1;
689+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (6,6,6);
690+COMMIT;
691+
692+--echo
693+--echo We should have a Transaction with 1 update and 1 insert Statement
694+--replace_regex /start_timestamp: [0-9]+/START_TIMESTAMP/g /end_timestamp: [0-9]+/END_TIMESTAMP/g /creation_timestamp: [0-9]+/CREATE_TIMESTAMP/ /update_timestamp: [0-9]+/UPDATE_TIMESTAMP/
695+
696+SELECT PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS));
697+
698+CREATE TABLE t2 (pk INT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT);
699+INSERT INTO t2 (a) VALUES (1),(2), (NULL);
700+
701+BEGIN;
702+--ERROR ER_BAD_NULL_ERROR
703+INSERT INTO t1 (col_int_not_null) SELECT a FROM t2;
704+INSERT INTO t1 (col_int1, col_int2, col_int_not_null) VALUES (7,7,7);
705+COMMIT;
706+
707+--echo
708+--echo We should have a Transaction with 1 insert Statement
709+--replace_regex /start_timestamp: [0-9]+/START_TIMESTAMP/g /end_timestamp: [0-9]+/END_TIMESTAMP/g /creation_timestamp: [0-9]+/CREATE_TIMESTAMP/ /update_timestamp: [0-9]+/UPDATE_TIMESTAMP/
710+
711+SELECT PRINT_TRANSACTION_MESSAGE('transaction.log',(select max(entry_offset) from DATA_DICTIONARY.TRANSACTION_LOG_TRANSACTIONS));
712+
713+
714+DROP TABLE t1;
715+DROP TABLE t2;
716+SET GLOBAL transaction_log_truncate_debug= true;