diff --git a/include/mysql/plugin_audit.h.pp b/include/mysql/plugin_audit.h.pp
index 6122aa4029a..4ede09a0d41 100644
--- a/include/mysql/plugin_audit.h.pp
+++ b/include/mysql/plugin_audit.h.pp
@@ -314,22 +314,22 @@ struct st_mysql_const_lex_string
 };
 typedef struct st_mysql_const_lex_string MYSQL_CONST_LEX_STRING;
 extern struct thd_alloc_service_st {
-  void *(*thd_alloc_func)(THD*, size_t);
-  void *(*thd_calloc_func)(THD*, size_t);
-  char *(*thd_strdup_func)(THD*, const char *);
-  char *(*thd_strmake_func)(THD*, const char *, size_t);
-  void *(*thd_memdup_func)(THD*, const void*, size_t);
-  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(THD*,
+  void *(*thd_alloc_func)(const THD*, size_t);
+  void *(*thd_calloc_func)(const THD*, size_t);
+  char *(*thd_strdup_func)(const THD*, const char *);
+  char *(*thd_strmake_func)(const THD*, const char *, size_t);
+  void *(*thd_memdup_func)(const THD*, const void*, size_t);
+  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(const THD*,
                                         MYSQL_CONST_LEX_STRING *,
                                         const char *, size_t, int);
 } *thd_alloc_service;
-void *thd_alloc(THD* thd, size_t size);
-void *thd_calloc(THD* thd, size_t size);
-char *thd_strdup(THD* thd, const char *str);
-char *thd_strmake(THD* thd, const char *str, size_t size);
-void *thd_memdup(THD* thd, const void* str, size_t size);
+void *thd_alloc(const THD* thd, size_t size);
+void *thd_calloc(const THD* thd, size_t size);
+char *thd_strdup(const THD* thd, const char *str);
+char *thd_strmake(const THD* thd, const char *str, size_t size);
+void *thd_memdup(const THD* thd, const void* str, size_t size);
 MYSQL_CONST_LEX_STRING
-*thd_make_lex_string(THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
+*thd_make_lex_string(const THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
                      const char *str, size_t size,
                      int allocate_lex_string);
 }
diff --git a/include/mysql/plugin_auth.h.pp b/include/mysql/plugin_auth.h.pp
index 4f42e693a16..ab32177fbb0 100644
--- a/include/mysql/plugin_auth.h.pp
+++ b/include/mysql/plugin_auth.h.pp
@@ -314,22 +314,22 @@ struct st_mysql_const_lex_string
 };
 typedef struct st_mysql_const_lex_string MYSQL_CONST_LEX_STRING;
 extern struct thd_alloc_service_st {
-  void *(*thd_alloc_func)(THD*, size_t);
-  void *(*thd_calloc_func)(THD*, size_t);
-  char *(*thd_strdup_func)(THD*, const char *);
-  char *(*thd_strmake_func)(THD*, const char *, size_t);
-  void *(*thd_memdup_func)(THD*, const void*, size_t);
-  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(THD*,
+  void *(*thd_alloc_func)(const THD*, size_t);
+  void *(*thd_calloc_func)(const THD*, size_t);
+  char *(*thd_strdup_func)(const THD*, const char *);
+  char *(*thd_strmake_func)(const THD*, const char *, size_t);
+  void *(*thd_memdup_func)(const THD*, const void*, size_t);
+  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(const THD*,
                                         MYSQL_CONST_LEX_STRING *,
                                         const char *, size_t, int);
 } *thd_alloc_service;
-void *thd_alloc(THD* thd, size_t size);
-void *thd_calloc(THD* thd, size_t size);
-char *thd_strdup(THD* thd, const char *str);
-char *thd_strmake(THD* thd, const char *str, size_t size);
-void *thd_memdup(THD* thd, const void* str, size_t size);
+void *thd_alloc(const THD* thd, size_t size);
+void *thd_calloc(const THD* thd, size_t size);
+char *thd_strdup(const THD* thd, const char *str);
+char *thd_strmake(const THD* thd, const char *str, size_t size);
+void *thd_memdup(const THD* thd, const void* str, size_t size);
 MYSQL_CONST_LEX_STRING
-*thd_make_lex_string(THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
+*thd_make_lex_string(const THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
                      const char *str, size_t size,
                      int allocate_lex_string);
 }
diff --git a/include/mysql/plugin_data_type.h.pp b/include/mysql/plugin_data_type.h.pp
index d8788f5d4e0..34b8dadd75e 100644
--- a/include/mysql/plugin_data_type.h.pp
+++ b/include/mysql/plugin_data_type.h.pp
@@ -314,22 +314,22 @@ struct st_mysql_const_lex_string
 };
 typedef struct st_mysql_const_lex_string MYSQL_CONST_LEX_STRING;
 extern struct thd_alloc_service_st {
-  void *(*thd_alloc_func)(THD*, size_t);
-  void *(*thd_calloc_func)(THD*, size_t);
-  char *(*thd_strdup_func)(THD*, const char *);
-  char *(*thd_strmake_func)(THD*, const char *, size_t);
-  void *(*thd_memdup_func)(THD*, const void*, size_t);
-  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(THD*,
+  void *(*thd_alloc_func)(const THD*, size_t);
+  void *(*thd_calloc_func)(const THD*, size_t);
+  char *(*thd_strdup_func)(const THD*, const char *);
+  char *(*thd_strmake_func)(const THD*, const char *, size_t);
+  void *(*thd_memdup_func)(const THD*, const void*, size_t);
+  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(const THD*,
                                         MYSQL_CONST_LEX_STRING *,
                                         const char *, size_t, int);
 } *thd_alloc_service;
-void *thd_alloc(THD* thd, size_t size);
-void *thd_calloc(THD* thd, size_t size);
-char *thd_strdup(THD* thd, const char *str);
-char *thd_strmake(THD* thd, const char *str, size_t size);
-void *thd_memdup(THD* thd, const void* str, size_t size);
+void *thd_alloc(const THD* thd, size_t size);
+void *thd_calloc(const THD* thd, size_t size);
+char *thd_strdup(const THD* thd, const char *str);
+char *thd_strmake(const THD* thd, const char *str, size_t size);
+void *thd_memdup(const THD* thd, const void* str, size_t size);
 MYSQL_CONST_LEX_STRING
-*thd_make_lex_string(THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
+*thd_make_lex_string(const THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
                      const char *str, size_t size,
                      int allocate_lex_string);
 }
diff --git a/include/mysql/plugin_encryption.h.pp b/include/mysql/plugin_encryption.h.pp
index 5624c6d6f61..449ee46d585 100644
--- a/include/mysql/plugin_encryption.h.pp
+++ b/include/mysql/plugin_encryption.h.pp
@@ -314,22 +314,22 @@ struct st_mysql_const_lex_string
 };
 typedef struct st_mysql_const_lex_string MYSQL_CONST_LEX_STRING;
 extern struct thd_alloc_service_st {
-  void *(*thd_alloc_func)(THD*, size_t);
-  void *(*thd_calloc_func)(THD*, size_t);
-  char *(*thd_strdup_func)(THD*, const char *);
-  char *(*thd_strmake_func)(THD*, const char *, size_t);
-  void *(*thd_memdup_func)(THD*, const void*, size_t);
-  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(THD*,
+  void *(*thd_alloc_func)(const THD*, size_t);
+  void *(*thd_calloc_func)(const THD*, size_t);
+  char *(*thd_strdup_func)(const THD*, const char *);
+  char *(*thd_strmake_func)(const THD*, const char *, size_t);
+  void *(*thd_memdup_func)(const THD*, const void*, size_t);
+  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(const THD*,
                                         MYSQL_CONST_LEX_STRING *,
                                         const char *, size_t, int);
 } *thd_alloc_service;
-void *thd_alloc(THD* thd, size_t size);
-void *thd_calloc(THD* thd, size_t size);
-char *thd_strdup(THD* thd, const char *str);
-char *thd_strmake(THD* thd, const char *str, size_t size);
-void *thd_memdup(THD* thd, const void* str, size_t size);
+void *thd_alloc(const THD* thd, size_t size);
+void *thd_calloc(const THD* thd, size_t size);
+char *thd_strdup(const THD* thd, const char *str);
+char *thd_strmake(const THD* thd, const char *str, size_t size);
+void *thd_memdup(const THD* thd, const void* str, size_t size);
 MYSQL_CONST_LEX_STRING
-*thd_make_lex_string(THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
+*thd_make_lex_string(const THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
                      const char *str, size_t size,
                      int allocate_lex_string);
 }
diff --git a/include/mysql/plugin_ftparser.h.pp b/include/mysql/plugin_ftparser.h.pp
index 46f4e6fc1fe..42c3e093150 100644
--- a/include/mysql/plugin_ftparser.h.pp
+++ b/include/mysql/plugin_ftparser.h.pp
@@ -314,22 +314,22 @@ struct st_mysql_const_lex_string
 };
 typedef struct st_mysql_const_lex_string MYSQL_CONST_LEX_STRING;
 extern struct thd_alloc_service_st {
-  void *(*thd_alloc_func)(THD*, size_t);
-  void *(*thd_calloc_func)(THD*, size_t);
-  char *(*thd_strdup_func)(THD*, const char *);
-  char *(*thd_strmake_func)(THD*, const char *, size_t);
-  void *(*thd_memdup_func)(THD*, const void*, size_t);
-  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(THD*,
+  void *(*thd_alloc_func)(const THD*, size_t);
+  void *(*thd_calloc_func)(const THD*, size_t);
+  char *(*thd_strdup_func)(const THD*, const char *);
+  char *(*thd_strmake_func)(const THD*, const char *, size_t);
+  void *(*thd_memdup_func)(const THD*, const void*, size_t);
+  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(const THD*,
                                         MYSQL_CONST_LEX_STRING *,
                                         const char *, size_t, int);
 } *thd_alloc_service;
-void *thd_alloc(THD* thd, size_t size);
-void *thd_calloc(THD* thd, size_t size);
-char *thd_strdup(THD* thd, const char *str);
-char *thd_strmake(THD* thd, const char *str, size_t size);
-void *thd_memdup(THD* thd, const void* str, size_t size);
+void *thd_alloc(const THD* thd, size_t size);
+void *thd_calloc(const THD* thd, size_t size);
+char *thd_strdup(const THD* thd, const char *str);
+char *thd_strmake(const THD* thd, const char *str, size_t size);
+void *thd_memdup(const THD* thd, const void* str, size_t size);
 MYSQL_CONST_LEX_STRING
-*thd_make_lex_string(THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
+*thd_make_lex_string(const THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
                      const char *str, size_t size,
                      int allocate_lex_string);
 }
diff --git a/include/mysql/plugin_function.h.pp b/include/mysql/plugin_function.h.pp
index 750f94cbf5d..2e78d23ae2e 100644
--- a/include/mysql/plugin_function.h.pp
+++ b/include/mysql/plugin_function.h.pp
@@ -314,22 +314,22 @@ struct st_mysql_const_lex_string
 };
 typedef struct st_mysql_const_lex_string MYSQL_CONST_LEX_STRING;
 extern struct thd_alloc_service_st {
-  void *(*thd_alloc_func)(THD*, size_t);
-  void *(*thd_calloc_func)(THD*, size_t);
-  char *(*thd_strdup_func)(THD*, const char *);
-  char *(*thd_strmake_func)(THD*, const char *, size_t);
-  void *(*thd_memdup_func)(THD*, const void*, size_t);
-  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(THD*,
+  void *(*thd_alloc_func)(const THD*, size_t);
+  void *(*thd_calloc_func)(const THD*, size_t);
+  char *(*thd_strdup_func)(const THD*, const char *);
+  char *(*thd_strmake_func)(const THD*, const char *, size_t);
+  void *(*thd_memdup_func)(const THD*, const void*, size_t);
+  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(const THD*,
                                         MYSQL_CONST_LEX_STRING *,
                                         const char *, size_t, int);
 } *thd_alloc_service;
-void *thd_alloc(THD* thd, size_t size);
-void *thd_calloc(THD* thd, size_t size);
-char *thd_strdup(THD* thd, const char *str);
-char *thd_strmake(THD* thd, const char *str, size_t size);
-void *thd_memdup(THD* thd, const void* str, size_t size);
+void *thd_alloc(const THD* thd, size_t size);
+void *thd_calloc(const THD* thd, size_t size);
+char *thd_strdup(const THD* thd, const char *str);
+char *thd_strmake(const THD* thd, const char *str, size_t size);
+void *thd_memdup(const THD* thd, const void* str, size_t size);
 MYSQL_CONST_LEX_STRING
-*thd_make_lex_string(THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
+*thd_make_lex_string(const THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
                      const char *str, size_t size,
                      int allocate_lex_string);
 }
diff --git a/include/mysql/plugin_password_validation.h.pp b/include/mysql/plugin_password_validation.h.pp
index 376df1dd330..138422e2b3e 100644
--- a/include/mysql/plugin_password_validation.h.pp
+++ b/include/mysql/plugin_password_validation.h.pp
@@ -314,22 +314,22 @@ struct st_mysql_const_lex_string
 };
 typedef struct st_mysql_const_lex_string MYSQL_CONST_LEX_STRING;
 extern struct thd_alloc_service_st {
-  void *(*thd_alloc_func)(THD*, size_t);
-  void *(*thd_calloc_func)(THD*, size_t);
-  char *(*thd_strdup_func)(THD*, const char *);
-  char *(*thd_strmake_func)(THD*, const char *, size_t);
-  void *(*thd_memdup_func)(THD*, const void*, size_t);
-  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(THD*,
+  void *(*thd_alloc_func)(const THD*, size_t);
+  void *(*thd_calloc_func)(const THD*, size_t);
+  char *(*thd_strdup_func)(const THD*, const char *);
+  char *(*thd_strmake_func)(const THD*, const char *, size_t);
+  void *(*thd_memdup_func)(const THD*, const void*, size_t);
+  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(const THD*,
                                         MYSQL_CONST_LEX_STRING *,
                                         const char *, size_t, int);
 } *thd_alloc_service;
-void *thd_alloc(THD* thd, size_t size);
-void *thd_calloc(THD* thd, size_t size);
-char *thd_strdup(THD* thd, const char *str);
-char *thd_strmake(THD* thd, const char *str, size_t size);
-void *thd_memdup(THD* thd, const void* str, size_t size);
+void *thd_alloc(const THD* thd, size_t size);
+void *thd_calloc(const THD* thd, size_t size);
+char *thd_strdup(const THD* thd, const char *str);
+char *thd_strmake(const THD* thd, const char *str, size_t size);
+void *thd_memdup(const THD* thd, const void* str, size_t size);
 MYSQL_CONST_LEX_STRING
-*thd_make_lex_string(THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
+*thd_make_lex_string(const THD* thd, MYSQL_CONST_LEX_STRING *lex_str,
                      const char *str, size_t size,
                      int allocate_lex_string);
 }
diff --git a/include/mysql/service_thd_alloc.h b/include/mysql/service_thd_alloc.h
index 0be4687b8e6..e85a8e8c4e0 100644
--- a/include/mysql/service_thd_alloc.h
+++ b/include/mysql/service_thd_alloc.h
@@ -50,12 +50,12 @@ struct st_mysql_const_lex_string
 typedef struct st_mysql_const_lex_string MYSQL_CONST_LEX_STRING;
 
 extern struct thd_alloc_service_st {
-  void *(*thd_alloc_func)(MYSQL_THD, size_t);
-  void *(*thd_calloc_func)(MYSQL_THD, size_t);
-  char *(*thd_strdup_func)(MYSQL_THD, const char *);
-  char *(*thd_strmake_func)(MYSQL_THD, const char *, size_t);
-  void *(*thd_memdup_func)(MYSQL_THD, const void*, size_t);
-  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(MYSQL_THD,
+  void *(*thd_alloc_func)(const MYSQL_THD, size_t);
+  void *(*thd_calloc_func)(const MYSQL_THD, size_t);
+  char *(*thd_strdup_func)(const MYSQL_THD, const char *);
+  char *(*thd_strmake_func)(const MYSQL_THD, const char *, size_t);
+  void *(*thd_memdup_func)(const MYSQL_THD, const void*, size_t);
+  MYSQL_CONST_LEX_STRING *(*thd_make_lex_string_func)(const MYSQL_THD,
                                         MYSQL_CONST_LEX_STRING *,
                                         const char *, size_t, int);
 } *thd_alloc_service;
@@ -92,23 +92,23 @@ extern struct thd_alloc_service_st {
 
   @see alloc_root()
 */
-void *thd_alloc(MYSQL_THD thd, size_t size);
+void *thd_alloc(const MYSQL_THD thd, size_t size);
 /**
   @see thd_alloc()
 */
-void *thd_calloc(MYSQL_THD thd, size_t size);
+void *thd_calloc(const MYSQL_THD thd, size_t size);
 /**
   @see thd_alloc()
 */
-char *thd_strdup(MYSQL_THD thd, const char *str);
+char *thd_strdup(const MYSQL_THD thd, const char *str);
 /**
   @see thd_alloc()
 */
-char *thd_strmake(MYSQL_THD thd, const char *str, size_t size);
+char *thd_strmake(const MYSQL_THD thd, const char *str, size_t size);
 /**
   @see thd_alloc()
 */
-void *thd_memdup(MYSQL_THD thd, const void* str, size_t size);
+void *thd_memdup(const MYSQL_THD thd, const void* str, size_t size);
 
 /**
   Create a LEX_STRING in this connection's local memory pool
@@ -124,7 +124,7 @@ void *thd_memdup(MYSQL_THD thd, const void* str, size_t size);
   @see thd_alloc()
 */
 MYSQL_CONST_LEX_STRING
-*thd_make_lex_string(MYSQL_THD thd, MYSQL_CONST_LEX_STRING *lex_str,
+*thd_make_lex_string(const MYSQL_THD thd, MYSQL_CONST_LEX_STRING *lex_str,
                      const char *str, size_t size,
                      int allocate_lex_string);
 
diff --git a/include/thr_lock.h b/include/thr_lock.h
index d918d6d74f4..443d707bd2b 100644
--- a/include/thr_lock.h
+++ b/include/thr_lock.h
@@ -173,8 +173,6 @@ void thr_print_locks(void);		/* For debugging */
 my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data,
                                      enum thr_lock_type new_lock_type,
                                      ulong lock_wait_timeout);
-void    thr_downgrade_write_lock(THR_LOCK_DATA *data,
-                                 enum thr_lock_type new_lock_type);
 my_bool thr_reschedule_write_lock(THR_LOCK_DATA *data,
                                   ulong lock_wait_timeout);
 void thr_set_lock_wait_callback(void (*before_wait)(void),
diff --git a/mysql-test/include/binlog_combinations.combinations b/mysql-test/include/binlog_combinations.combinations
new file mode 100644
index 00000000000..5bd64366c9d
--- /dev/null
+++ b/mysql-test/include/binlog_combinations.combinations
@@ -0,0 +1,3 @@
+[nobinlog]
+[binlog]
+--log-bin=master-bin
diff --git a/mysql-test/include/binlog_combinations.inc b/mysql-test/include/binlog_combinations.inc
new file mode 100644
index 00000000000..b9fff3411b3
--- /dev/null
+++ b/mysql-test/include/binlog_combinations.inc
@@ -0,0 +1,3 @@
+#
+# Adds standalone and binlog combinations
+#
diff --git a/mysql-test/main/alter_table.result b/mysql-test/main/alter_table.result
index 1508bb9d34a..637fba37f1c 100644
--- a/mysql-test/main/alter_table.result
+++ b/mysql-test/main/alter_table.result
@@ -1660,8 +1660,8 @@ ALTER TABLE t1 ADD INDEX i1(b);
 affected rows: 2
 info: Records: 2  Duplicates: 0  Warnings: 0
 ALTER TABLE t1 ADD INDEX i2(b), ALGORITHM= DEFAULT;
-affected rows: 2
-info: Records: 2  Duplicates: 0  Warnings: 1
+affected rows: 0
+info: Records: 0  Duplicates: 0  Warnings: 1
 Warnings:
 Note	1831	Duplicate index `i2`. This is deprecated and will be disallowed in a future release
 ALTER TABLE t1 ADD INDEX i3(b), ALGORITHM= COPY;
@@ -1734,7 +1734,6 @@ info: Records: 0  Duplicates: 0  Warnings: 1
 Warnings:
 Note	1831	Duplicate index `i3`. This is deprecated and will be disallowed in a future release
 ALTER TABLE t1 ADD INDEX i4(b), ALGORITHM= COPY, LOCK= NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED
 ALTER TABLE t1 ADD INDEX i5(b), ALGORITHM= COPY, LOCK= SHARED;
 affected rows: 2
 info: Records: 2  Duplicates: 0  Warnings: 1
@@ -1752,9 +1751,6 @@ ERROR 0A000: LOCK=NONE/SHARED is not supported for this operation. Try LOCK=EXCL
 ALTER TABLE m1 ENABLE KEYS, ALGORITHM= INPLACE, LOCK= EXCLUSIVE;
 affected rows: 0
 ALTER TABLE m1 ENABLE KEYS, ALGORITHM= COPY, LOCK= NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED
-ALTER ONLINE TABLE m1 ADD COLUMN c int;
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
 ALTER TABLE m1 ENABLE KEYS, ALGORITHM= COPY, LOCK= SHARED;
 affected rows: 2
 info: Records: 2  Duplicates: 0  Warnings: 0
diff --git a/mysql-test/main/alter_table.test b/mysql-test/main/alter_table.test
index ebf10008f48..0b545601c0d 100644
--- a/mysql-test/main/alter_table.test
+++ b/mysql-test/main/alter_table.test
@@ -1517,8 +1517,13 @@ ALTER TABLE t1 DROP INDEX i1, DROP INDEX i2, DROP INDEX i3, DROP INDEX i4;
 ALTER TABLE t1 ADD INDEX i1(b), ALGORITHM= INPLACE, LOCK= NONE;
 ALTER TABLE t1 ADD INDEX i2(b), ALGORITHM= INPLACE, LOCK= SHARED;
 ALTER TABLE t1 ADD INDEX i3(b), ALGORITHM= INPLACE, LOCK= EXCLUSIVE;
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+--disable_info
+--disable_warnings
+--error 0,ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+# COPY/NONE works now, but still an error in embedded
 ALTER TABLE t1 ADD INDEX i4(b), ALGORITHM= COPY, LOCK= NONE;
+--enable_warnings
+--enable_info
 ALTER TABLE t1 ADD INDEX i5(b), ALGORITHM= COPY, LOCK= SHARED;
 ALTER TABLE t1 ADD INDEX i6(b), ALGORITHM= COPY, LOCK= EXCLUSIVE;
 
@@ -1527,10 +1532,11 @@ ALTER TABLE m1 ENABLE KEYS, ALGORITHM= INPLACE, LOCK= NONE;
 --error ER_ALTER_OPERATION_NOT_SUPPORTED
 ALTER TABLE m1 ENABLE KEYS, ALGORITHM= INPLACE, LOCK= SHARED;
 ALTER TABLE m1 ENABLE KEYS, ALGORITHM= INPLACE, LOCK= EXCLUSIVE;
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+--disable_info
+--error 0,ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+# COPY/NONE works now, but still an error in embedded
 ALTER TABLE m1 ENABLE KEYS, ALGORITHM= COPY, LOCK= NONE;
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-ALTER ONLINE TABLE m1 ADD COLUMN c int;
+--enable_info
 # This works because the lock will be SNW for the copy phase.
 # It will still require exclusive lock for actually enabling keys.
 ALTER TABLE m1 ENABLE KEYS, ALGORITHM= COPY, LOCK= SHARED;
diff --git a/mysql-test/main/alter_table_lock.result b/mysql-test/main/alter_table_lock.result
index 620fca23315..5a787055478 100644
--- a/mysql-test/main/alter_table_lock.result
+++ b/mysql-test/main/alter_table_lock.result
@@ -14,4 +14,49 @@ ALTER TABLE t1 CHANGE COLUMN IF EXISTS b c INT;
 SET SESSION max_session_mem_used = @max_session_mem_used_save;
 UNLOCK TABLES;
 DROP TABLE t1;
+#
 # End of 10.5 tests
+#
+#
+# MDEV-28943 Online alter fails under LOCK TABLE with ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+#
+create table t1 (f int) engine=innodb;
+insert t1 values (1);
+alter table t1 force, algorithm=copy, lock=none;
+alter table t1 force, algorithm=inplace, lock=none;
+alter table t1 force, algorithm=copy, lock=shared;
+alter table t1 force, algorithm=inplace, lock=shared;
+alter table t1 force, algorithm=copy, lock=exclusive;
+alter table t1 force, algorithm=inplace, lock=exclusive;
+lock table t1 write;
+connect con1, localhost, root;
+select count(*) as 'must be 0' from t1;
+connection default;
+alter table t1 force, algorithm=copy, lock=none;
+alter table t1 force, algorithm=inplace, lock=none;
+alter table t1 force, algorithm=copy, lock=shared;
+alter table t1 force, algorithm=inplace, lock=shared;
+alter table t1 force, algorithm=copy, lock=exclusive;
+alter table t1 force, algorithm=inplace, lock=exclusive;
+delete from t1;
+unlock tables;
+connection con1;
+must be 0
+0
+connection default;
+drop table t1;
+#
+# MDEV-29056 Replica SQL thread stops with 1846 error on ALTER ONLINE after LOCK WRITE
+#
+create table t1 (c varchar(1), key (c)) engine=innodb;
+insert into t1 (c) values ('g') ;
+alter table t1 add fulltext key(c), algorithm=inplace;
+alter online table t1 add column s blob not null, algorithm=inplace;
+ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
+lock table t1 write;
+alter online table t1 add column s blob not null, algorithm=inplace;
+ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
+drop table t1;
+#
+# End of 10.11 tests
+#
diff --git a/mysql-test/main/alter_table_lock.test b/mysql-test/main/alter_table_lock.test
index bd26c1ac7d0..1ed267e56d1 100644
--- a/mysql-test/main/alter_table_lock.test
+++ b/mysql-test/main/alter_table_lock.test
@@ -1,4 +1,6 @@
 --source include/not_msan.inc
+--source include/not_embedded.inc
+--source include/have_innodb.inc
 
 --echo #
 --echo # MDEV-23836: Assertion `! is_set() || m_can_overwrite_status' in
@@ -26,4 +28,54 @@ SET SESSION max_session_mem_used = @max_session_mem_used_save;
 UNLOCK TABLES;
 DROP TABLE t1;
 
+--echo #
 --echo # End of 10.5 tests
+--echo #
+
+--echo #
+--echo # MDEV-28943 Online alter fails under LOCK TABLE with ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+--echo #
+
+# test that any lock=xxx works under LOCK TABLES
+# and that it is ignored, does not actually downgrade the lock
+create table t1 (f int) engine=innodb;
+insert t1 values (1);
+alter table t1 force, algorithm=copy, lock=none;
+alter table t1 force, algorithm=inplace, lock=none;
+alter table t1 force, algorithm=copy, lock=shared;
+alter table t1 force, algorithm=inplace, lock=shared;
+alter table t1 force, algorithm=copy, lock=exclusive;
+alter table t1 force, algorithm=inplace, lock=exclusive;
+lock table t1 write;
+connect con1, localhost, root;
+--send select count(*) as 'must be 0' from t1
+--connection default
+alter table t1 force, algorithm=copy, lock=none;
+alter table t1 force, algorithm=inplace, lock=none;
+alter table t1 force, algorithm=copy, lock=shared;
+alter table t1 force, algorithm=inplace, lock=shared;
+alter table t1 force, algorithm=copy, lock=exclusive;
+alter table t1 force, algorithm=inplace, lock=exclusive;
+delete from t1;
+unlock tables;
+--connection con1
+--reap
+--connection default
+drop table t1;
+
+--echo #
+--echo # MDEV-29056 Replica SQL thread stops with 1846 error on ALTER ONLINE after LOCK WRITE
+--echo #
+create table t1 (c varchar(1), key (c)) engine=innodb;
+insert into t1 (c) values ('g') ;
+alter table t1 add fulltext key(c), algorithm=inplace;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter online table t1 add column s blob not null, algorithm=inplace;
+lock table t1 write;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter online table t1 add column s blob not null, algorithm=inplace;
+drop table t1;
+
+--echo #
+--echo # End of 10.11 tests
+--echo #
diff --git a/mysql-test/main/alter_table_locknone.result b/mysql-test/main/alter_table_locknone.result
index 4d0e0f09f71..e6cc47eddf6 100644
--- a/mysql-test/main/alter_table_locknone.result
+++ b/mysql-test/main/alter_table_locknone.result
@@ -53,31 +53,11 @@ t1	CREATE TABLE `t1` (
   PRIMARY KEY (`a`)
 ) ENGINE=Aria DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci PAGE_CHECKSUM=1 COMMENT='new comment'
 alter online table t1 page_checksum=1;
-alter online table t1 page_checksum=0;
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
 drop table t1;
 create table t1 (a int not null primary key, b int, c varchar(80), e enum('a','b'));
 insert into t1 (a) values (1),(2),(3);
-alter online table t1 drop column b, add b int;
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
-alter online table t1 modify b bigint;
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
-alter online table t1 modify e enum('c','a','b');
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
-alter online table t1 modify c varchar(50);
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
-alter online table t1 modify c varchar(100);
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
-alter online table t1 add f int;
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
-alter online table t1 engine=memory;
-ERROR 0A000: LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED
 alter online table t1 rename to t2;
 ERROR 0A000: LOCK=NONE/SHARED is not supported for this operation. Try LOCK=EXCLUSIVE
-alter online table t1 checksum=1;
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
-alter online table t1 add constraint check (b > 0);
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
 alter table t1 engine=innodb;
 alter table t1 add index (b);
 alter online table t1 add index c (c);
diff --git a/mysql-test/main/alter_table_locknone.test b/mysql-test/main/alter_table_locknone.test
index 75efd0ce301..9588ec6678c 100644
--- a/mysql-test/main/alter_table_locknone.test
+++ b/mysql-test/main/alter_table_locknone.test
@@ -33,7 +33,6 @@ drop table t1;
 #
 create temporary table t1 (a int not null primary key, b int, c varchar(80), e enum('a','b'));
 insert into t1 (a) values (1),(2),(3);
-
 alter online table t1 modify b int default 5, alter c set default 'X';
 alter online table t1 change b new_name int;
 alter online table t1 modify e enum('a','b','c');
@@ -54,37 +53,13 @@ alter online table t1 modify e enum('a','b','c');
 alter online table t1 comment "new comment";
 show create table t1;
 alter online table t1 page_checksum=1;
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 page_checksum=0;
 drop table t1;
 
-#
-# Test of things that is not possible to do online
-#
-
 create table t1 (a int not null primary key, b int, c varchar(80), e enum('a','b'));
 insert into t1 (a) values (1),(2),(3);
 
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 drop column b, add b int;
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 modify b bigint;
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 modify e enum('c','a','b');
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 modify c varchar(50);
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 modify c varchar(100);
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 add f int;
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-alter online table t1 engine=memory;
 --error ER_ALTER_OPERATION_NOT_SUPPORTED
 alter online table t1 rename to t2;
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 checksum=1;
---error ER_ALTER_OPERATION_NOT_SUPPORTED
-alter online table t1 add constraint check (b > 0);
 
 alter table t1 engine=innodb;
 alter table t1 add index (b);
diff --git a/mysql-test/main/alter_table_locknone_notembedded.result b/mysql-test/main/alter_table_locknone_notembedded.result
new file mode 100644
index 00000000000..a3cbac0d66a
--- /dev/null
+++ b/mysql-test/main/alter_table_locknone_notembedded.result
@@ -0,0 +1,25 @@
+create table t1 (a int not null primary key, b int, c varchar(80), e enum('a','b')) engine=aria;
+insert into t1 (a) values (1),(2),(3);
+alter online table t1 page_checksum=1;
+alter online table t1 page_checksum=0;
+alter online table t1 drop column b, add b int;
+alter online table t1 modify b bigint;
+alter online table t1 modify e enum('c','a','b');
+alter online table t1 modify c varchar(50);
+alter online table t1 modify c varchar(100);
+alter online table t1 add f int;
+alter online table t1 engine=memory;
+alter online table t1 checksum=1;
+alter online table t1 add constraint check (b > 0);
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` int(11) NOT NULL,
+  `c` varchar(100) DEFAULT NULL,
+  `e` enum('c','a','b') DEFAULT NULL,
+  `b` bigint(20) DEFAULT NULL,
+  `f` int(11) DEFAULT NULL,
+  PRIMARY KEY (`a`),
+  CONSTRAINT `CONSTRAINT_1` CHECK (`b` > 0)
+) ENGINE=MEMORY DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci CHECKSUM=1
+drop table t1;
diff --git a/mysql-test/main/alter_table_locknone_notembedded.test b/mysql-test/main/alter_table_locknone_notembedded.test
new file mode 100644
index 00000000000..00649740ca4
--- /dev/null
+++ b/mysql-test/main/alter_table_locknone_notembedded.test
@@ -0,0 +1,22 @@
+source include/not_embedded.inc;
+#
+# Test of ALTER ONLINE TABLE syntax
+# (same as LOCK=NONE)
+#
+
+create table t1 (a int not null primary key, b int, c varchar(80), e enum('a','b')) engine=aria;
+insert into t1 (a) values (1),(2),(3);
+alter online table t1 page_checksum=1;
+alter online table t1 page_checksum=0;
+alter online table t1 drop column b, add b int;
+alter online table t1 modify b bigint;
+alter online table t1 modify e enum('c','a','b');
+alter online table t1 modify c varchar(50);
+alter online table t1 modify c varchar(100);
+alter online table t1 add f int;
+alter online table t1 engine=memory;
+alter online table t1 checksum=1;
+alter online table t1 add constraint check (b > 0);
+
+show create table t1;
+drop table t1;
diff --git a/mysql-test/main/alter_table_online.result b/mysql-test/main/alter_table_online.result
new file mode 100644
index 00000000000..7928222a428
--- /dev/null
+++ b/mysql-test/main/alter_table_online.result
@@ -0,0 +1,240 @@
+#
+# alter ignore cannot be done online
+#
+create table t (a int);
+alter ignore table t add primary key (a), algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: ALTER IGNORE TABLE. Try LOCK=SHARED
+drop table t;
+#
+# MDEV-28771 Assertion `table->in_use&&tdc->flushed' failed after ALTER
+#
+create table t (a char(1));
+insert into t values ('a'),('b');
+select * from t join t as t2 join t as t3;
+a	a	a
+a	a	a
+b	a	a
+a	b	a
+b	b	a
+a	a	b
+b	a	b
+a	b	b
+b	b	b
+alter table t modify a int;
+ERROR 22007: Truncated incorrect INTEGER value: 'a'
+select * from t;
+a
+a
+b
+drop table t;
+create table t (c double precision key,c2 char,c3 year);
+insert into t values (7,3,1);
+select a from t where a=all (select a from t where b=2 union select a from t where b=2);
+ERROR 42S22: Unknown column 'a' in 'field list'
+insert into t values (3,1,1);
+alter table t change c c date,add key(c);
+ERROR 22007: Incorrect date value: '7' for column `test`.`t`.`c` at row 1
+select * from t;
+c	c2	c3
+7	3	2001
+3	1	2001
+drop table t;
+set sql_mode='';
+create table t (c char unique,c2 int,stamp timestamp);
+insert into t values (1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5);
+Warnings:
+Warning	1265	Data truncated for column 'stamp' at row 1
+Warning	1265	Data truncated for column 'stamp' at row 2
+Warning	1265	Data truncated for column 'stamp' at row 3
+Warning	1265	Data truncated for column 'stamp' at row 4
+Warning	1265	Data truncated for column 'stamp' at row 5
+update t set c=(select * from t) where c in (select * from t);
+ERROR 21000: Operand should contain 1 column(s)
+alter table t modify c date;
+ERROR 23000: Duplicate entry '0000-00-00' for key 'c'
+select * from t;
+c	c2	stamp
+1	1	0000-00-00 00:00:00
+2	2	0000-00-00 00:00:00
+3	3	0000-00-00 00:00:00
+4	4	0000-00-00 00:00:00
+5	5	0000-00-00 00:00:00
+drop table t;
+set sql_mode=default;
+#
+# MDEV-28944 XA assertions failing in binlog_rollback and binlog_commit
+#
+CREATE TABLE t (a INT) ENGINE=MyISAM;
+INSERT INTO t VALUES (1);
+connect  con1,localhost,root,,test;
+XA START 'xid';
+SELECT * FROM t;
+a
+1
+connection default;
+ALTER TABLE t NOWAIT ADD KEY (a);
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+connection con1;
+UPDATE t SET a = 2;
+XA END 'xid';
+XA COMMIT 'xid' ONE PHASE;
+DROP TABLE t;
+disconnect con1;
+connection default;
+#
+# MDEV-29068 Cascade foreign key updates do not apply in online alter
+#
+create table t1 (a int primary key) engine=InnoDB;
+insert into t1 values (1),(2),(3);
+create table t2 (b int, foreign key (b)
+references t1 (a)
+on update cascade) engine=InnoDB;
+insert into t2 values (1),(2),(3);
+alter table t2 add c int, algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: ON UPDATE CASCADE. Try LOCK=SHARED
+alter table t2 add c int, algorithm=inplace, lock=none;
+create or replace table t2 (b int, foreign key (b)
+references t1 (a)
+on delete set null) engine=InnoDB;
+alter table t2 add c int, algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: ON DELETE SET NULL. Try LOCK=SHARED
+alter table t2 add c int, algorithm=inplace, lock=none;
+create or replace table t2 (b int, foreign key (b)
+references t1 (a)
+on delete no action) engine=InnoDB;
+insert into t2 values (1),(2),(3);
+alter table t2 add c int, algorithm=copy, lock=none;
+create or replace table t2 (b int, foreign key (b)
+references t1 (a)
+on update restrict) engine=InnoDB;
+insert into t2 values (1),(2),(3);
+alter table t2 add c int, algorithm=copy, lock=none;
+drop table t2, t1;
+create table t1 (a int primary key, b int unique) engine=InnoDB;
+insert into t1 values (1, 1),(2, 2),(3, 3);
+create table t2 (a int references t1 (a),
+b int references t1 (b) on update cascade) engine=InnoDB;
+insert into t2 values (1, 1),(2, 2);
+alter table t2 add c int, algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: ON UPDATE CASCADE. Try LOCK=SHARED
+alter table t2 add c int, algorithm=copy;
+alter table t2 add d int, algorithm=inplace;
+drop table t2, t1;
+#
+# MDEV-30891 Assertion `!table->versioned(VERS_TRX_ID)' failed
+# in Write_rows_log_event::binlog_row_logging_function
+#
+set system_versioning_alter_history= keep;
+create table t1 (id int,
+row_start bigint unsigned generated always as row start,
+row_end bigint unsigned generated always as row end,
+period for system_time (row_start, row_end))
+engine=innodb with system versioning;
+alter table t1 add c int, algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: BIGINT GENERATED ALWAYS AS ROW_START. Try LOCK=SHARED
+alter table t1 add c int, algorithm=inplace;
+alter table t1 add d int, lock=none;
+set system_versioning_alter_history= default;
+drop table t1;
+#
+# MDEV-31058 ER_KEY_NOT_FOUND upon concurrent CHANGE column autoinc
+# and DML
+#
+create table t (a serial, b int) engine=innodb;
+alter table t drop a, modify b serial, algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: CHANGE COLUMN ... AUTO_INCREMENT. Try LOCK=SHARED
+set statement sql_mode= NO_AUTO_VALUE_ON_ZERO for
+alter table t drop a, modify b serial, algorithm=copy, lock=none;
+create or replace table t (a serial, b int) engine=innodb;
+show create table t;
+Table	Create Table
+t	CREATE TABLE `t` (
+  `a` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `b` int(11) DEFAULT NULL,
+  UNIQUE KEY `a` (`a`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+# a is unique in the old table, but is shrunk in the new one.
+# Only unsafe approach is fine because of possible collisions.
+alter table t modify a int, modify b serial, algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: CHANGE COLUMN ... AUTO_INCREMENT. Try LOCK=SHARED
+#
+# Check that we treat autoinc columns correctly modify old autoinc is
+# fine, adding new autoinc for existed column is unsafe.
+#
+create or replace table t (a serial) engine=innodb;
+alter table t change a b serial, algorithm=copy, lock=none;
+Warnings:
+Note	1831	Duplicate index `b`. This is deprecated and will be disallowed in a future release
+# Shrinking the autoinc field is considered safe.
+# ER_WARN_DATA_OUT_OF_RANGE should be emitted otherwise.
+alter table t change b b int auto_increment primary key,
+algorithm=copy, lock=none;
+alter table t add c int default(0), drop primary key, drop key a;
+# key `b` is still there
+show create table t;
+Table	Create Table
+t	CREATE TABLE `t` (
+  `b` int(11) NOT NULL AUTO_INCREMENT,
+  `c` int(11) DEFAULT 0,
+  UNIQUE KEY `b` (`b`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+alter table t drop b, change c c serial, algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: CHANGE COLUMN ... AUTO_INCREMENT. Try LOCK=SHARED
+# Check existed unique keys.
+create or replace table t(a int, b int not null, c int not null, d int);
+# No unique in the old table;
+alter table t add unique(b, c), modify d int auto_increment, add key(d),
+algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: CHANGE COLUMN ... AUTO_INCREMENT. Try LOCK=SHARED
+alter table t add unique(a, b);
+# Unique in the old table has nulls;
+alter table t modify d int auto_increment, add key(d),
+algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: CHANGE COLUMN ... AUTO_INCREMENT. Try LOCK=SHARED
+alter table t add unique(b, c);
+# Change unique's column;
+alter table t change b x bigint, modify d int auto_increment, add key(d),
+algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: CHANGE COLUMN ... AUTO_INCREMENT. Try LOCK=SHARED
+# Finally good. Simple renames with a type unchanged will not affect
+# the result. Also NOT NULL -> NULL transform is fine.
+alter table t modify d int auto_increment, add key(d),
+change b x int null,
+algorithm=copy, lock=none;
+drop table t;
+# MDEV-31172 Server crash or ASAN errors in online_alter_check_autoinc
+create table t (a int, b int, c char(8), key(a,b,c));
+alter table t modify c int auto_increment key, algorithm=copy;
+drop table t;
+# MDEV-31601 Some ALTER TABLE .. fail when they worked before, and with
+# a wrong error message
+create table t (a int) engine=aria;
+insert into t values (1),(2);
+alter table t algorithm=nocopy, order by a;
+ERROR 0A000: ALGORITHM=NOCOPY is not supported for this operation. Try ALGORITHM=COPY
+alter table t engine=myisam, algorithm=inplace;
+ERROR 0A000: ALGORITHM=INPLACE is not supported for this operation. Try ALGORITHM=COPY
+drop table t;
+create temporary table t (f int);
+alter table t force, algorithm=instant;
+ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=COPY
+drop table t;
+create sequence s engine=MyISAM;
+alter table s engine=Aria, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: SEQUENCE. Try LOCK=SHARED
+alter table s engine=Aria;
+drop sequence  s;
+# MDEV-31631 Adding auto-increment column to a table with history online
+# behaves differently from non-online
+create sequence s;
+create table t1(a int, x int NULL default(nextval(s)));
+alter table t1 add b int default (nextval(s)), lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: Function or expression 'NEXTVAL()' cannot be used in the DEFAULT clause of `b`. Try LOCK=SHARED
+alter table t1 add b int primary key auto_increment, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: ADD COLUMN ... AUTO_INCREMENT. Try LOCK=SHARED
+create table t2(a int, b int NULL default(nextval(s)));
+alter table t2 modify b int not null default (nextval(s)), lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: Function or expression 'NEXTVAL()' cannot be used in the DEFAULT clause of `b`. Try LOCK=SHARED
+drop table t2;
+drop table t1;
+drop sequence s;
diff --git a/mysql-test/main/alter_table_online.test b/mysql-test/main/alter_table_online.test
new file mode 100644
index 00000000000..35e01054890
--- /dev/null
+++ b/mysql-test/main/alter_table_online.test
@@ -0,0 +1,248 @@
+--source include/binlog_combinations.inc
+--source include/have_innodb.inc
+--source include/not_embedded.inc
+
+--echo #
+--echo # alter ignore cannot be done online
+--echo #
+create table t (a int);
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter ignore table t add primary key (a), algorithm=copy, lock=none;
+drop table t;
+
+--echo #
+--echo # MDEV-28771 Assertion `table->in_use&&tdc->flushed' failed after ALTER
+--echo #
+
+create table t (a char(1));
+insert into t values ('a'),('b');
+select * from t join t as t2 join t as t3;
+--error ER_TRUNCATED_WRONG_VALUE
+alter table t modify a int;
+select * from t;
+drop table t;
+
+create table t (c double precision key,c2 char,c3 year);
+insert into t values (7,3,1);
+--error ER_BAD_FIELD_ERROR
+select a from t where a=all (select a from t where b=2 union select a from t where b=2);
+insert into t values (3,1,1);
+--error ER_TRUNCATED_WRONG_VALUE
+alter table t change c c date,add key(c);
+select * from t;
+drop table t;
+
+set sql_mode='';
+create table t (c char unique,c2 int,stamp timestamp);
+insert into t values (1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5);
+--error ER_OPERAND_COLUMNS
+update t set c=(select * from t) where c in (select * from t);
+--error ER_DUP_ENTRY
+alter table t modify c date;
+select * from t;
+drop table t;
+set sql_mode=default;
+
+--echo #
+--echo # MDEV-28944 XA assertions failing in binlog_rollback and binlog_commit
+--echo #
+--disable_view_protocol
+CREATE TABLE t (a INT) ENGINE=MyISAM;
+INSERT INTO t VALUES (1);
+
+--connect (con1,localhost,root,,test)
+XA START 'xid';
+SELECT * FROM t;
+
+--connection default
+--error ER_LOCK_WAIT_TIMEOUT
+ALTER TABLE t NOWAIT ADD KEY (a);
+
+--connection con1
+UPDATE t SET a = 2;
+XA END 'xid';
+XA COMMIT 'xid' ONE PHASE;
+
+DROP TABLE t;
+--disconnect con1
+--connection default
+--enable_view_protocol
+
+--echo #
+--echo # MDEV-29068 Cascade foreign key updates do not apply in online alter
+--echo #
+create table t1 (a int primary key) engine=InnoDB;
+insert into t1 values (1),(2),(3);
+create table t2 (b int, foreign key (b)
+                        references t1 (a)
+                        on update cascade) engine=InnoDB;
+insert into t2 values (1),(2),(3);
+
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t2 add c int, algorithm=copy, lock=none;
+alter table t2 add c int, algorithm=inplace, lock=none;
+
+create or replace table t2 (b int, foreign key (b)
+                            references t1 (a)
+                            on delete set null) engine=InnoDB;
+
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t2 add c int, algorithm=copy, lock=none;
+alter table t2 add c int, algorithm=inplace, lock=none;
+
+create or replace table t2 (b int, foreign key (b)
+                            references t1 (a)
+                            on delete no action) engine=InnoDB;
+
+insert into t2 values (1),(2),(3);
+alter table t2 add c int, algorithm=copy, lock=none;
+
+create or replace table t2 (b int, foreign key (b)
+                            references t1 (a)
+                            on update restrict) engine=InnoDB;
+
+insert into t2 values (1),(2),(3);
+alter table t2 add c int, algorithm=copy, lock=none;
+drop table t2, t1;
+
+create table t1 (a int primary key, b int unique) engine=InnoDB;
+insert into t1 values (1, 1),(2, 2),(3, 3);
+create table t2 (a int references t1 (a),
+                 b int references t1 (b) on update cascade) engine=InnoDB;
+insert into t2 values (1, 1),(2, 2);
+
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t2 add c int, algorithm=copy, lock=none;
+alter table t2 add c int, algorithm=copy;
+alter table t2 add d int, algorithm=inplace;
+# Cleanup
+drop table t2, t1;
+
+--echo #
+--echo # MDEV-30891 Assertion `!table->versioned(VERS_TRX_ID)' failed
+--echo # in Write_rows_log_event::binlog_row_logging_function
+--echo #
+set system_versioning_alter_history= keep;
+create table t1 (id int,
+                 row_start bigint unsigned generated always as row start,
+                 row_end bigint unsigned generated always as row end,
+                 period for system_time (row_start, row_end))
+                engine=innodb with system versioning;
+
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t1 add c int, algorithm=copy, lock=none;
+alter table t1 add c int, algorithm=inplace;
+alter table t1 add d int, lock=none;
+
+set system_versioning_alter_history= default;
+drop table t1;
+
+--echo #
+--echo # MDEV-31058 ER_KEY_NOT_FOUND upon concurrent CHANGE column autoinc
+--echo # and DML
+--echo #
+create table t (a serial, b int) engine=innodb;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t drop a, modify b serial, algorithm=copy, lock=none;
+
+set statement sql_mode= NO_AUTO_VALUE_ON_ZERO for
+alter table t drop a, modify b serial, algorithm=copy, lock=none;
+
+create or replace table t (a serial, b int) engine=innodb;
+show create table t;
+--echo # a is unique in the old table, but is shrunk in the new one.
+--echo # Only unsafe approach is fine because of possible collisions.
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t modify a int, modify b serial, algorithm=copy, lock=none;
+
+--echo #
+--echo # Check that we treat autoinc columns correctly modify old autoinc is
+--echo # fine, adding new autoinc for existed column is unsafe.
+--echo #
+create or replace table t (a serial) engine=innodb;
+
+alter table t change a b serial, algorithm=copy, lock=none;
+
+--echo # Shrinking the autoinc field is considered safe.
+--echo # ER_WARN_DATA_OUT_OF_RANGE should be emitted otherwise.
+alter table t change b b int auto_increment primary key,
+              algorithm=copy, lock=none;
+
+alter table t add c int default(0), drop primary key, drop key a;
+--echo # key `b` is still there
+show create table t;
+
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t drop b, change c c serial, algorithm=copy, lock=none;
+
+--echo # Check existed unique keys.
+create or replace table t(a int, b int not null, c int not null, d int);
+
+--echo # No unique in the old table;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t add unique(b, c), modify d int auto_increment, add key(d),
+              algorithm=copy, lock=none;
+
+alter table t add unique(a, b);
+--echo # Unique in the old table has nulls;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t modify d int auto_increment, add key(d),
+              algorithm=copy, lock=none;
+
+alter table t add unique(b, c);
+--echo # Change unique's column;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t change b x bigint, modify d int auto_increment, add key(d),
+              algorithm=copy, lock=none;
+
+--echo # Finally good. Simple renames with a type unchanged will not affect
+--echo # the result. Also NOT NULL -> NULL transform is fine.
+alter table t modify d int auto_increment, add key(d),
+              change b x int null,
+              algorithm=copy, lock=none;
+
+drop table t;
+
+--echo # MDEV-31172 Server crash or ASAN errors in online_alter_check_autoinc
+create table t (a int, b int, c char(8), key(a,b,c));
+alter table t modify c int auto_increment key, algorithm=copy;
+drop table t;
+
+--echo # MDEV-31601 Some ALTER TABLE .. fail when they worked before, and with
+--echo # a wrong error message
+create table t (a int) engine=aria;
+insert into t values (1),(2);
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+alter table t algorithm=nocopy, order by a;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+alter table t engine=myisam, algorithm=inplace;
+drop table t;
+
+create temporary table t (f int);
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+alter table t force, algorithm=instant;
+drop table t;
+
+create sequence s engine=MyISAM;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table s engine=Aria, lock=none;
+alter table s engine=Aria;
+drop sequence  s;
+
+
+--echo # MDEV-31631 Adding auto-increment column to a table with history online
+--echo # behaves differently from non-online
+create sequence s;
+create table t1(a int, x int NULL default(nextval(s)));
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t1 add b int default (nextval(s)), lock=none;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t1 add b int primary key auto_increment, lock=none;
+
+create table t2(a int, b int NULL default(nextval(s)));
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t2 modify b int not null default (nextval(s)), lock=none;
+
+drop table t2;
+drop table t1;
+drop sequence s;
diff --git a/mysql-test/main/alter_table_online_debug.result b/mysql-test/main/alter_table_online_debug.result
new file mode 100644
index 00000000000..81fe415cf6f
--- /dev/null
+++ b/mysql-test/main/alter_table_online_debug.result
@@ -0,0 +1,1463 @@
+set default_storage_engine= innodb;
+connect  con2, localhost, root,,;
+connection default;
+#
+# Test insert
+#
+# Insert and add column
+create or replace table t1 (a int);
+insert t1 values (5);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+connection con2;
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+connection default;
+affected rows: 4
+info: Records: 4  Duplicates: 0  Warnings: 0
+select * from t1;
+a	b
+5	NULL
+123	NULL
+456	NULL
+789	NULL
+# Insert, error
+create or replace table t1 (a int);
+insert t1 values (5), (5);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 nowait add unique (a), algorithm= copy, lock= none;
+connection con2;
+start transaction;
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+connection default;
+ERROR 23000: Duplicate entry '5' for key 'a'
+connection con2;
+commit;
+connection default;
+select variable_value into @otd from information_schema.session_status where variable_name='Opened_table_definitions';
+select * from t1;
+a
+5
+5
+123
+456
+789
+select variable_value-@otd from information_schema.session_status where variable_name='Opened_table_definitions';
+variable_value-@otd
+1
+# long transaction and add column
+create or replace table t1 (a int);
+insert t1 values (5);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 nowait add b int NULL, algorithm= copy, lock= none;
+connection con2;
+start transaction;
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+connection default;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+select * from t1;
+a
+5
+connection con2;
+rollback;
+connection default;
+# Insert and add NOT NULL column without default value
+create or replace table t1 (a int);
+insert t1 values (5);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add b int NOT NULL, algorithm= copy, lock= none;
+connection con2;
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+5	0
+123	0
+456	0
+789	0
+# Insert and add a column with a default value
+create or replace table t1 (a int);
+insert t1 values (5);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add b int NOT NULL default (222), algorithm= copy, lock= none;
+connection con2;
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+5	222
+123	222
+456	222
+789	222
+#
+# Test update
+#
+# Update and add a column
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add c int default(1),
+algorithm= copy, lock= none;
+connection con2;
+update t1 set b= 55 where a = 1;
+set debug_sync= 'now SIGNAL end';
+connection default;
+affected rows: 2
+info: Records: 2  Duplicates: 0  Warnings: 0
+select * from t1;
+a	b	c
+1	55	1
+3	44	1
+# Update and add a column in the middle
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add c int default(1) after a,
+algorithm= copy, lock= none;
+connection con2;
+update t1 set b= 55 where a = 1;
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	c	b
+1	1	55
+3	1	44
+#
+# Test primary key change
+#
+# Drop key, add key
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 drop primary key, add primary key(b),
+algorithm= copy, lock= none;
+connection con2;
+update t1 set b= 55 where a = 1;
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+3	44
+1	55
+# Drop key, add key. Two updates
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 11);
+insert t1 values (2, 22);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 drop primary key, add primary key(b),
+algorithm= copy, lock= none;
+connection con2;
+update t1 set b= 33 where a = 1;
+update t1 set b= 44 where a = 2;
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+1	33
+2	44
+#
+# Various tests, see below
+#
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 11);
+insert t1 values (2, 22);
+insert t1 values (3, 33);
+insert t1 values (4, 44);
+insert t1 values (5, 55);
+insert t1 values (6, 66);
+insert t1 values (7, 77);
+insert t1 values (8, 88);
+insert t1 values (9, 99);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 drop primary key, add primary key(b),
+algorithm= copy, lock= none;
+connection con2;
+# Two updates
+update t1 set b= 1001 where a = 1;
+update t1 set b= 2002 where a = 2;
+# Two updates in transaction
+set autocommit = 0;
+start transaction;
+update t1 set b= 3003 where a = 3;
+update t1 set b= 4004 where a = 4;
+commit;
+set autocommit = 1;
+# Second update is rolled back
+update t1 set b= 5005 where a = 5;
+set autocommit = 0;
+start transaction;
+update t1 set b= 6006 where a = 6;
+rollback;
+set autocommit = 1;
+# Second execution in transaction fails
+set autocommit = 0;
+start transaction;
+update t1 set b= 7007 where a = 7;
+update t1 set a= 8, b= 8008 where a = 8 or a = 9 order by a;
+ERROR 23000: Duplicate entry '8' for key 'PRIMARY'
+commit;
+set autocommit = 1;
+select * from t1;
+a	b
+1	1001
+2	2002
+3	3003
+4	4004
+5	5005
+6	66
+7	7007
+8	88
+9	99
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+1	1001
+2	2002
+3	3003
+4	4004
+5	5005
+6	66
+7	7007
+8	88
+9	99
+#
+# MYISAM. Only Inserts can be tested.
+# (everything else is a table lock disallowing concurrent reads)
+#
+create or replace table t1 (a int) engine=myisam;
+insert t1 values (5);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+connection con2;
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+5	NULL
+123	NULL
+456	NULL
+789	NULL
+# MYISAM + error
+create or replace table t1 (a int primary key) engine=myisam;
+insert t1 values (5);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+connection con2;
+insert into t1 values (1),(2),(3),(4),(5),(6);
+ERROR 23000: Duplicate entry '5' for key 'PRIMARY'
+select * from t1;
+a
+1
+2
+3
+4
+5
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+5	NULL
+1	NULL
+2	NULL
+3	NULL
+4	NULL
+# Aria + error
+set @@binlog_format=row;
+create or replace table t1 (a int primary key) engine=aria;
+insert t1 values (5);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+connection con2;
+insert into t1 values (1),(2),(3),(4),(5),(6);
+ERROR 23000: Duplicate entry '5' for key 'PRIMARY'
+select * from t1;
+a
+1
+2
+3
+4
+5
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+5	NULL
+1	NULL
+2	NULL
+3	NULL
+4	NULL
+set @@binlog_format=default;
+# Test incompatible changes
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 drop primary key, add primary key(b),
+algorithm= copy, lock= none;
+connection con2;
+update t1 set b= 44 where a = 1;
+set debug_sync= 'now SIGNAL end';
+connection default;
+ERROR 23000: Duplicate entry '44' for key 'PRIMARY'
+select * from t1;
+a	b
+1	44
+3	44
+# Test log read after EXCLUSIVE lock
+# Transaction is started before ALTER, and UPDATE is made.
+# Then more UPDATEs.
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 11);
+insert t1 values (2, 22);
+insert t1 values (3, 33);
+insert t1 values (4, 44);
+insert t1 values (5, 55);
+set debug_sync= 'alter_table_online_before_lock SIGNAL locking WAIT_FOR end';
+set debug_sync= 'alter_table_online_downgraded SIGNAL downgraded';
+alter table t1 drop primary key, add primary key(b),
+algorithm= copy, lock= none;
+connection con2;
+begin;
+set debug_sync= 'now WAIT_FOR downgraded';
+update t1 set b= 111 where a = 1;
+set debug_sync= 'now WAIT_FOR locking';
+set debug_sync= 'now SIGNAL end';
+update t1 set b= 222 where a = 2;
+update t1 set b= 333 where a = 3;
+update t1 set b= 444 where a = 4;
+commit;
+update t1 set b= 555 where a = 5;
+connection default;
+select * from t1;
+a	b
+1	111
+2	222
+3	333
+4	444
+5	555
+#
+# Test progress report.
+#
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 11);
+insert t1 values (2, 22);
+insert t1 values (3, 33);
+insert t1 values (4, 44);
+set debug_sync= 'alter_table_online_before_lock SIGNAL locking WAIT_FOR end';
+set debug_sync= 'alter_table_online_downgraded SIGNAL downgraded'
+                                             ' WAIT_FOR start_replication';
+set debug_sync= 'alter_table_online_progress SIGNAL applied WAIT_FOR proceed'
+                                           ' EXECUTE 9';
+alter table t1 drop primary key, add primary key(b),
+algorithm= copy, lock= none;
+connection con2;
+set debug_sync= 'now WAIT_FOR downgraded';
+update t1 set b= 111 where a = 1;
+insert t1 values (5, 55);
+update t1 set b= 555 where a = 5;
+insert t1 values (6, 66);
+update t1 set b= 666 where a = 6;
+set debug_sync= 'now SIGNAL start_replication';
+# First signal is for log description event.
+set debug_sync= 'now WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+3	53.390
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+3	63.559
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+3	71.610
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+3	81.780
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+3	89.831
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+3	100.000
+set debug_sync= 'now SIGNAL proceed WAIT_FOR locking';
+begin;
+update t1 set b= 222 where a = 2;
+update t1 set b= 333 where a = 3;
+update t1 set b= 444 where a = 4;
+commit;
+set debug_sync= 'now SIGNAL end WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+4	33.333
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+4	66.667
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+stage	progress
+4	100.000
+set debug_sync= 'now SIGNAL proceed';
+connection default;
+select * from t1;
+a	b
+1	111
+2	222
+3	333
+4	444
+5	555
+6	666
+#
+# Test system versioning
+#
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+connection con2;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+set timestamp = 1;
+alter table t1 add system versioning,
+algorithm= copy, lock= none;
+connection con2;
+set timestamp = 2;
+update t1 set b= 55 where a = 1;
+set timestamp = 3;
+insert into t1 values (6, 77);
+set debug_sync= 'now SIGNAL end';
+connection default;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` int(11) NOT NULL,
+  `b` int(11) DEFAULT NULL,
+  PRIMARY KEY (`a`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci WITH SYSTEM VERSIONING
+select *, UNIX_TIMESTAMP(row_start), UNIX_TIMESTAMP(row_end) from t1 for system_time all;
+a	b	UNIX_TIMESTAMP(row_start)	UNIX_TIMESTAMP(row_end)
+1	55	1.000000	2147483647.999999
+3	44	1.000000	2147483647.999999
+6	77	1.000000	2147483647.999999
+alter table t1 drop system versioning, algorithm= copy, lock= none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: DROP SYSTEM VERSIONING. Try LOCK=SHARED
+#
+# Test ROLLBACK TO SAVEPOINT
+#
+create or replace table t1 (a int);
+insert t1 values (1), (2);
+create or replace table t2 (a int);
+insert t2 values (1), (2);
+connection con2;
+begin;
+update t2 set a= 222 where a = 2;
+savepoint savie;
+update t2 set a= 111 where a = 1;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+connection con2;
+update t1 set a= 123 where a = 1;
+savepoint whoopsie;
+rollback to savepoint savie;
+commit;
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+1	NULL
+2	NULL
+select * from t2;
+a
+1
+222
+create or replace table t1 (a int);
+insert t1 values (1), (2);
+create or replace table t2 (a int);
+insert t2 values (1), (2);
+create or replace table t3 (a int) engine=myisam;
+insert t3 values (1);
+connection con2;
+begin;
+update t2 set a= 222 where a = 2;
+savepoint savie;
+update t2 set a= 111 where a = 1;
+set debug_sync= 'now WAIT_FOR ended';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+connection con2;
+update t1 set a= 222 where a = 2;
+savepoint whoopsie;
+update t1 set a= 123 where a = 1;
+insert t3 values (2);
+select * from t1;
+a
+123
+222
+rollback to savepoint whoopsie;
+Warnings:
+Warning	1196	Some non-transactional changed tables couldn't be rolled back
+select * from t1;
+a
+1
+222
+select * from t3;
+a
+1
+2
+commit;
+set debug_sync= 'now SIGNAL end';
+connection default;
+select * from t1;
+a	b
+1	NULL
+222	NULL
+select * from t2;
+a
+111
+222
+select * from t3;
+a
+1
+2
+# Cleanup
+set debug_sync= 'reset';
+drop table t1;
+drop table t2;
+drop table t3;
+create table t1 (a char(6), b int) engine=innodb;
+insert t1 values ('abcde1',1),('abcde2',2);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set sql_mode='';
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 modify a char(4), algorithm=copy, lock=none;
+connection default;
+update t1 set b=b+10 where a='abcde2';
+select * from t1;
+a	b
+abcde1	1
+abcde2	12
+set debug_sync= 'now signal goforit';
+connection con2;
+Warnings:
+Warning	1265	Data truncated for column 'a' at row 1
+Warning	1265	Data truncated for column 'a' at row 2
+Warning	1265	Data truncated for column 'a' at row 3
+set sql_mode=default;
+connection default;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` char(4) DEFAULT NULL,
+  `b` int(11) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+select * from t1;
+a	b
+abcd	1
+abcd	12
+drop table t1;
+set debug_sync= 'reset';
+#
+# MDEV-28930 ALTER TABLE Deadlocks with parallel TL_WRITE
+#
+create table t1(a int) engine=myisam select 1;
+set debug_sync='alter_table_online_before_lock SIGNAL ready WAIT_FOR go_for_locking';
+alter table t1 force;
+connection con2;
+set debug_sync='now WAIT_FOR ready';
+set debug_sync='thr_multi_lock_before_thr_lock SIGNAL go_for_locking';
+update t1 set a=2;
+connection default;
+set debug_sync='alter_table_online_before_lock SIGNAL ready WAIT_FOR go_for_locking';
+alter table mysql.global_priv force;
+connection con2;
+set debug_sync='now WAIT_FOR ready';
+set debug_sync='thr_multi_lock_before_thr_lock SIGNAL go_for_locking';
+create user user1@localhost;
+connection default;
+set debug_sync=reset;
+drop user user1@localhost;
+drop table t1;
+#
+# MDEV-28959 Online alter ignores strict table mode
+#
+create table t1 (a int);
+insert into t1 values (1),(2),(3);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set sql_mode='STRICT_TRANS_TABLES,STRICT_ALL_TABLES';
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 modify a int not null, algorithm=copy, lock=none;
+connection default;
+insert into t1 values (null),(null);
+set debug_sync= 'now signal goforit';
+connection con2;
+ERROR 01000: Data truncated for column 'a' at row 4
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` int(11) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+select * from t1;
+a
+1
+2
+3
+NULL
+NULL
+set sql_mode=default;
+connection default;
+drop table t1;
+set debug_sync= reset;
+#
+# MDEV-28967 Assertion `marked_for_write_or_computed()' failed in Field_new_decimal::store_value / online_alter_read_from_binlog`
+#
+create  table t1 (a decimal(8,2), b varchar(8));
+insert into t1 (b) values ('x');
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 force, algorithm=copy, lock=none;
+connection default;
+insert t1 (b) values ('k');
+insert t1 (b) values ('m');
+set debug_sync= 'now signal goforit';
+connection con2;
+connection default;
+drop table t1;
+set debug_sync= reset;
+#
+# MDEV-29021 ALTER TABLE fails when a stored virtual column is dropped and added
+#
+create table t1 (a char(9), b char(9) as (a) stored);
+insert into t1(a) values ('foobar');
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set sql_mode='';
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 drop b, add b char(3) as (a) stored, algorithm=copy, lock=none;
+connection default;
+update t1 set a = 'foobarqux';
+set debug_sync= 'now signal goforit';
+connection con2;
+Warnings:
+Warning	1265	Data truncated for column 'b' at row 1
+Warning	1265	Data truncated for column 'b' at row 2
+set sql_mode=default;
+connection default;
+drop table t1;
+set debug_sync= reset;
+# (duplicate) MDEV-29007 Assertion `marked_for_write_or_computed()'
+# failed upon online ADD COLUMN .. FIRST
+create table t (a int);
+insert into t values (1),(2);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t add c int first, algorithm=copy, lock=none;
+connection default;
+insert into t values (3);
+set debug_sync= 'now signal goforit';
+connection con2;
+connection default;
+drop table t;
+set debug_sync= reset;
+# UNIQUE blob duplicates are not ignored.
+create table t1 (b blob);
+insert into t1 values ('foo'),('bar');
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 add unique(b), algorithm=copy, lock=none;
+connection default;
+insert into t1 values ('qux'),('foo');
+set debug_sync= 'now signal goforit';
+connection con2;
+ERROR 23000: Duplicate entry 'foo' for key 'b'
+select * from t1;
+b
+foo
+bar
+qux
+foo
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `b` blob DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+connection default;
+drop table t1;
+set debug_sync= reset;
+#
+# DELETE with added virtual column
+#
+CREATE TABLE t1 (a CHAR(3), b CHAR(3) AS (a));
+INSERT INTO t1 (a) VALUES ('foo'),('bar');
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+ALTER TABLE t1 ADD c INT, ALGORITHM=COPY, LOCK=NONE;
+connection default;
+DELETE FROM t1;
+set debug_sync= 'now signal goforit';
+connection con2;
+select * from t1;
+a	b	c
+connection default;
+DROP TABLE t1;
+set debug_sync= reset;
+#
+# Do not ignore sql_mode when replicating
+#
+create table t1 (a int);
+insert into t1 values (1);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set sql_mode='STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO';
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 add b int as (1/a) stored, algorithm=copy, lock=none;
+connection default;
+update t1 set a= 0 where a=1;
+set debug_sync= 'now signal goforit';
+connection con2;
+ERROR 22012: Division by 0
+set sql_mode= default;
+connection default;
+drop table t1;
+set debug_sync= reset;
+#
+# MDEV-29067 Online alter ignores check constraint violation
+#
+## CHECK, INSERT
+create table t1 (a int);
+insert t1 values (1),(2);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 add check (a<10), algorithm=copy, lock=none;
+connection default;
+insert t1 values (11),(12);
+set debug_sync= 'now signal goforit';
+connection con2;
+ERROR 23000: CONSTRAINT `CONSTRAINT_1` failed for `test`.`t1`
+connection default;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` int(11) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+select * from t1;
+a
+1
+2
+11
+12
+drop table t1;
+## DEFAULT, INSERT
+create table t1 (a int);
+insert t1 values (1),(2);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 add b int default(a+10), algorithm=copy, lock=none;
+connection default;
+insert t1 values (11),(12);
+set debug_sync= 'now signal goforit';
+connection con2;
+connection default;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` int(11) DEFAULT NULL,
+  `b` int(11) DEFAULT (`a` + 10)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+select * from t1;
+a	b
+1	11
+2	12
+11	21
+12	22
+drop table t1;
+set debug_sync= 'reset';
+## CHECK, UPDATE
+create table t1 (a int) engine=innodb;
+insert t1 values (1),(2),(3),(4);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 add check (a<10), algorithm=copy, lock=none;
+connection default;
+update t1 set a=a+10 where a > 2;
+set debug_sync= 'now signal goforit';
+connection con2;
+ERROR 23000: CONSTRAINT `CONSTRAINT_1` failed for `test`.`t1`
+connection default;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` int(11) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+select * from t1;
+a
+1
+2
+13
+14
+drop table t1;
+## DEFAULT, UPDATE
+create table t1 (a int) engine=innodb;
+insert t1 values (1),(2),(3),(4);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 add b int default(a), algorithm=copy, lock=none;
+connection default;
+update t1 set a=a+10 where a > 2;
+insert t1 values(5);
+update t1 set a=a+10 where a = 5;
+set debug_sync= 'now signal goforit';
+connection con2;
+connection default;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` int(11) DEFAULT NULL,
+  `b` int(11) DEFAULT `a`
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+select * from t1;
+a	b
+1	1
+2	2
+13	13
+14	14
+15	15
+drop table t1;
+set debug_sync= 'reset';
+## VCOL + CHECK
+create table t1 (a int) engine=innodb;
+insert t1 values (1),(2),(3),(4);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 add b int as (a), add check(b=a), algorithm=copy, lock=none;
+connection default;
+update t1 set a=a+10 where a > 2;
+insert t1 values(5);
+update t1 set a=a+10 where a = 5;
+set debug_sync= 'now signal goforit';
+connection con2;
+connection default;
+show create table t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `a` int(11) DEFAULT NULL,
+  `b` int(11) GENERATED ALWAYS AS (`a`) VIRTUAL,
+  CONSTRAINT `CONSTRAINT_1` CHECK (`b` = `a`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+select * from t1;
+a	b
+1	1
+2	2
+13	13
+14	14
+15	15
+drop table t1;
+set debug_sync= 'reset';
+#
+# MDEV-29013 ER_KEY_NOT_FOUND/lock timeout upon online alter
+# with long unique indexes
+#
+create table t1 (b text not null, unique(b));
+insert into t1 values ('foo'),('bar');
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t1 add c int, algorithm=copy, lock=none;
+connection default;
+delete from t1;
+set debug_sync= 'now signal goforit';
+connection con2;
+connection default;
+drop table t1;
+set debug_sync= reset;
+###
+create table t1 (a text, unique(a)) engine=innodb;
+create table t2 (b text, unique(b)) engine=innodb;
+insert into t2 values (null),(null);
+set debug_sync= 'now wait_for downgraded';
+connection con2;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t2 add column c int, algorithm=copy, lock=none;
+connection default;
+delete from t2;
+set debug_sync= 'now signal goforit';
+connection con2;
+connection default;
+alter table t2 force;
+alter table t1 force;
+drop table t1, t2;
+set debug_sync= reset;
+#
+# MDEV-29038 XA assertions failing in binlog_rollback and binlog_commit
+#
+create table t (a int) engine=innodb;
+insert into t values (1);
+xa begin 'xid';
+set debug_sync= 'now wait_for downgraded';
+connect  con1,localhost,root,,test;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t force, algorithm=copy, lock=none;
+connection default;
+insert into t values (2);
+set debug_sync= 'now signal goforit';
+xa end 'xid';
+xa rollback 'xid';
+xa begin 'xid';
+connection con1;
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t force, algorithm=copy, lock=none;
+connection default;
+set debug_sync= 'now wait_for downgraded';
+insert into t values (3);
+set debug_sync= 'now signal goforit';
+xa end 'xid';
+xa commit 'xid' one phase;
+connection con1;
+connection default;
+drop table t;
+set debug_sync= reset;
+#
+# MDEV-29069 ER_KEY_NOT_FOUND upon online autoinc addition and
+# concurrent DELETE
+#
+set @old_dbug=@@debug_dbug;
+set debug_dbug="+d,rpl_report_chosen_key";
+#
+# Add clumsy DEFAULT
+#
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t add b int default(RAND() * 20), add key(b),
+algorithm=copy, lock=none;
+connection con2;
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+connection default;
+Warnings:
+Note	1105	Key chosen: -1
+Note	1105	Key chosen: -1
+select a from t;
+a
+11
+30
+# CURRENT_TIMESTAMP
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t add b timestamp default CURRENT_TIMESTAMP, add key(b),
+algorithm=copy, lock=none;
+connection con2;
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+connection default;
+Warnings:
+Note	1105	Key chosen: -1
+Note	1105	Key chosen: -1
+select a from t;
+a
+11
+30
+# CURRENT_TIMESTAMP, mixed key
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t add b timestamp default CURRENT_TIMESTAMP, add key(a, b),
+algorithm=copy, lock=none;
+connection con2;
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+connection default;
+Warnings:
+Note	1105	Key chosen: 0
+Note	1105	Key chosen: 0
+select a from t;
+a
+11
+30
+# Mixed primary key
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t add b int default (a+1), add primary key(b, a),
+algorithm=copy, lock=none;
+connection con2;
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+connection default;
+Warnings:
+Note	1105	Key chosen: 0
+Note	1105	Key chosen: 0
+select a from t;
+a
+11
+30
+#
+# Normal row, could be used as a key
+#
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t add b int as (a * 10) unique, algorithm=copy, lock=none;
+connection con2;
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+connection default;
+Warnings:
+Note	1105	Key chosen: 0
+Note	1105	Key chosen: 0
+#
+# Add key for old row
+#
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t add unique(a), algorithm=copy, lock=none;
+connection con2;
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+connection default;
+Warnings:
+Note	1105	Key chosen: 0
+Note	1105	Key chosen: 0
+#
+# Useful UNIQUE, though a virtual column on another extra field
+#
+create or replace table t (a int primary key, b int default (a+1));
+insert into t values (10, 10),(20, 20),(30, 30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t drop primary key, add c int default(a),
+add d int as (c) stored unique, algorithm=copy, lock=none;
+connection con2;
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 2 where a = 10;
+set debug_sync= 'now signal goforit';
+connection default;
+Warnings:
+Note	1105	Key chosen: 0
+Note	1105	Key chosen: 0
+select * from t;
+a	b	c	d
+12	10	12	12
+30	30	30	30
+#
+# Now this index is not usable (missing DEFAULT on field c)
+#
+create or replace table t (a int primary key, b int);
+insert into t values (10, 10),(20, 20),(30, 30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+alter table t drop primary key, add c real default(rand(a)),
+add d real as (c) stored unique, algorithm=copy, lock=none;
+connection con2;
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 2 where a = 10;
+set debug_sync= 'now signal goforit';
+connection default;
+Warnings:
+Note	1105	Key chosen: -1
+Note	1105	Key chosen: -1
+select a, b from t;
+a	b
+12	10
+30	30
+drop table t;
+set debug_sync= reset;
+set debug_dbug= @old_dbug;
+connection default;
+#
+# MDEV-30902 Server crash in LEX::first_lists_tables_same
+#
+create table t1 engine=myisam select 1 as x ;
+create procedure p() alter table t1 engine=heap;
+set debug_sync= 'alter_table_copy_end signal ended wait_for end';
+call p;
+connection con1;
+set debug_sync= 'now wait_for ended';
+insert into t1 values (2);
+set debug_sync= 'now signal end';
+connection default;
+call p;
+drop table t1;
+drop procedure p;
+set debug_sync=reset;
+#
+# MDEV-30902 Server crash in LEX::first_lists_tables_same
+#
+create table t (id int, s date, e date, period for p(s,e),
+unique(id, p without overlaps)) engine=aria;
+insert into t values (1,'1971-01-01','1971-01-02');
+set debug_sync= 'alter_table_online_before_lock signal lock wait_for goon';
+alter table t force;
+connection con1;
+set debug_sync= 'now wait_for lock';
+delete from t;
+set debug_sync= 'now signal goon';
+connection default;
+drop table t;
+#
+# MDEV-30924 Server crashes in MYSQL_LOG::is_open upon ALTER vs FUNCTION
+#
+create table t (a int);
+insert into t values (1),(2);
+create function f () returns int
+begin
+update t set a = 10;
+return 0;
+end $
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goon';
+alter table t force, algorithm=copy;
+connection con1;
+set debug_sync= 'now wait_for downgraded';
+select f();
+f()
+0
+set debug_sync= 'now signal goon';
+connection default;
+drop table t;
+drop function f;
+# Partitioning
+create or replace table t1 (a serial);
+insert t1 values (5), (6), (7);
+set debug_sync= 'alter_table_online_downgraded SIGNAL downgraded wait_for goon';
+alter table t1 ENGINE=InnoDB, ALGORITHM=COPY, LOCK=NONE
+PARTITION BY HASH(a) PARTITIONS 5;
+connection con1;
+set debug_sync= 'now WAIT_FOR downgraded';
+insert into t1 values (123), (456), (789);
+update t1 set a= a+100;
+set debug_sync= 'now SIGNAL goon';
+connection default;
+select * from t1;
+a
+105
+106
+556
+107
+223
+889
+drop table t1;
+#
+# MDEV-31033 ER_KEY_NOT_FOUND upon online COPY ALTER on a partitioned
+# table
+create table t (a int) partition by hash(a) partitions 2;
+insert into t values (1),(3);
+set debug_sync= 'alter_table_online_downgraded SIGNAL downgraded wait_for goon';
+alter table t force, algorithm=copy, lock=none;
+connection con1;
+set debug_sync= 'now WAIT_FOR downgraded';
+update t set a = a + 1;
+insert t values (1),(2);
+delete from t where a = 4 limit 1;
+set debug_sync= 'now SIGNAL goon';
+connection default;
+select * from t;
+a
+2
+2
+1
+drop table t;
+#
+# MDEV-31043 ER_KEY_NOT_FOUND upon concurrent ALTER and transaction
+#
+create table t (a int, b int) engine=myisam;
+insert into t values (1,10),(2,20);
+set debug_sync= 'alter_table_online_before_lock signal burnit wait_for goforit';
+alter table t add c int, algorithm=copy, lock=none;
+connection con1;
+set debug_sync= 'now wait_for burnit';
+update t set b = 100;
+start transaction;
+update t set b = 200;
+connection con2;
+delete from t order by a limit 1;
+delete from t order by a limit 1;
+select * from t;
+a	b
+connection con1;
+commit;
+set debug_sync= 'now signal goforit';
+connection default;
+select * from t;
+a	b	c
+drop table t;
+create table t (a int, b int) engine=aria;
+insert into t values (1,10),(2,20);
+set debug_sync= 'alter_table_online_before_lock signal burnit wait_for goforit';
+alter table t add c int, algorithm=copy, lock=none;
+connection con1;
+set debug_sync= 'now wait_for burnit';
+update t set b = 100;
+start transaction;
+update t set b = 200;
+connection con2;
+delete from t order by a limit 1;
+delete from t order by a limit 1;
+select * from t;
+a	b
+connection con1;
+commit;
+set debug_sync= 'now signal goforit';
+connection default;
+select * from t;
+a	b	c
+drop table t;
+#
+# MDEV-30949 Direct leak in binlog_online_alter_end_trans
+#
+create table t (f longblob default null) engine=myisam;
+insert into t values (null);
+set debug_sync= "alter_table_copy_end signal copy wait_for goon";
+set debug_sync= "alter_table_online_before_lock signal lock wait_for end";
+alter table t force, algorithm=copy;
+connection con1;
+set debug_sync= "now wait_for copy";
+insert into t select repeat('a',130000);
+set debug_sync= "now signal goon wait_for lock";
+insert into t select repeat('a',130000);
+set debug_sync= "now signal end";
+connection default;
+drop table t;
+#
+# Test that correct fields are marked as explicit:
+# Drop a, reorder b, add new column with default.
+#
+create table t (a int primary key, b int) engine=innodb;
+insert into t values (1, 1), (2, 2), (3, 3);
+set debug_sync= "alter_table_copy_end signal copy wait_for goon";
+alter table t drop primary key, drop a,
+change b c bigint,
+add x longblob default 123456;
+connection con1;
+set debug_sync= "now wait_for copy";
+update t set b = 5 where a = 1;
+update t set b = NULL where a = 1;
+select * from t;
+a	b
+1	NULL
+2	2
+3	3
+update t set a = 100 where a = 1;
+update t set b = -10 where a = 100;
+select * from t;
+a	b
+2	2
+3	3
+100	-10
+set debug_sync= "now signal goon";
+connection default;
+select * from t;
+c	x
+-10	123456
+2	123456
+3	123456
+drop table t;
+# Test that all the fields are unpacked.
+create table t (a int, b int) engine=innodb;
+insert into t values (NULL, 123), (NULL, 456);
+set debug_sync= "alter_table_copy_end signal copy wait_for goon";
+alter table t drop a, add primary key(b), algorithm=copy;
+connection con1;
+set debug_sync= "now wait_for copy";
+update t set b = b + 100;
+set debug_sync= "now signal goon";
+connection default;
+select * from t;
+b
+223
+556
+drop table t;
+set debug_sync= reset;
+NOT FOUND /Slave SQL/ in mysqld.1.err
+#
+# MDEV-31646 Online alter applies binlog cache limit to cache writes
+#
+create table t (pk int primary key, a varchar(100)) engine=MyISAM;
+insert into t select seq, repeat('x', 100) from seq_1_to_500;
+set @cache.size= @@max_binlog_cache_size;
+set global max_binlog_cache_size= 4096;
+set debug_sync= 'now wait_for do_updates';
+connection con1;
+set debug_sync= 'alter_table_online_progress signal do_updates wait_for go';
+alter table t add b int, algorithm=copy, lock=none;
+connection default;
+update t set a = repeat('y', 100);
+show warnings;
+Level	Code	Message
+set debug_sync= 'now signal go';
+connection con1;
+show warnings;
+Level	Code	Message
+connection default;
+drop table t;
+set debug_sync= reset;
+set global max_binlog_cache_size= @cache.size;
+# Now make sure that smaller limits will be processed fine
+set @old_dbug=@@debug_dbug;
+create table t (pk int primary key, a text) engine=MyISAM;
+insert into t select seq, repeat('x', 1000) from seq_1_to_50;
+connection con1;
+set debug_sync= 'alter_table_online_progress signal do_updates wait_for go';
+alter table t add b int, algorithm=copy, lock=none;
+connection default;
+set debug_sync= 'now wait_for do_updates';
+set debug_dbug="+d,online_alter_small_cache";
+update t set a = repeat('y', 1000);
+ERROR HY000: Multi-row statements required more than 'max_binlog_stmt_cache_size' bytes of storage.
+show warnings;
+Level	Code	Message
+Error	1705	Multi-row statements required more than 'max_binlog_stmt_cache_size' bytes of storage.
+Error	1534	Writing one row to the row-based binary log failed
+Warning	1196	Some non-transactional changed tables couldn't be rolled back
+set debug_sync= 'now signal go';
+connection con1;
+show warnings;
+Level	Code	Message
+connection default;
+drop table t;
+set debug_sync= reset;
+set debug_dbug= @old_dbug;
+create table t (id int, a int, b text, primary key (id));
+insert into t values (1,10,''),(2,20,'');
+set debug_sync= 'alter_table_online_progress signal do_update wait_for go';
+alter table t force, algorithm=copy, lock=none;
+connection con1;
+set @old_binlog_row_image= @@binlog_row_image;
+set binlog_row_image= noblob;
+set debug_sync= 'now wait_for do_update';
+update t set a = 1;
+delete from t where id = 1;
+set debug_sync= 'now signal go';
+set binlog_row_image= @old_binlog_row_image;
+connection default;
+select * from t;
+id	a	b
+2	1	
+drop table t;
+set debug_sync= reset;
+#
+# MDEV-31812 Add switch to old_mode to disable non-locking ALTER
+#
+set @old_old_mode= @@old_mode;
+create or replace table t1 (a int primary key, b int);
+set debug_sync= 'alter_table_copy_end SIGNAL copy_end WAIT_FOR proceed';
+alter table t1 drop primary key, add primary key(b), algorithm= copy;
+connection con2;
+set debug_sync= 'now WAIT_FOR copy_end';
+select if(max_stage = 4, "online", "old") as mode
+from information_schema.processlist where id = @con;
+mode
+online
+set debug_sync= 'now SIGNAL proceed';
+connection default;
+set old_mode= LOCK_ALTER_TABLE_COPY;
+set debug_sync= 'alter_table_copy_end SIGNAL copy_end WAIT_FOR proceed';
+alter table t1 drop primary key, add primary key(b), algorithm= copy;
+connection con2;
+set debug_sync= 'now WAIT_FOR copy_end';
+select if(max_stage = 4, "online", "old") as mode
+from information_schema.processlist where id = @con;
+mode
+old
+set debug_sync= 'now SIGNAL proceed';
+connection default;
+set debug_sync= 'alter_table_copy_end SIGNAL copy_end WAIT_FOR proceed';
+alter table t1 drop primary key, add primary key(b),
+algorithm= copy, lock=none;
+connection con2;
+set debug_sync= 'now WAIT_FOR copy_end';
+select if(max_stage = 4, "online", "old") as mode
+from information_schema.processlist where id = @con;
+mode
+online
+set debug_sync= 'now SIGNAL proceed';
+connection default;
+set old_mode= @old_old_mode;
+drop table t1;
+set debug_sync= reset;
+disconnect con1;
+disconnect con2;
+#
+# End of 11.2 tests
+#
diff --git a/mysql-test/main/alter_table_online_debug.test b/mysql-test/main/alter_table_online_debug.test
new file mode 100644
index 00000000000..e054c0218f5
--- /dev/null
+++ b/mysql-test/main/alter_table_online_debug.test
@@ -0,0 +1,1703 @@
+--source include/have_debug_sync.inc
+--source include/not_embedded.inc
+--source include/binlog_combinations.inc
+--source include/have_innodb.inc
+--source include/have_sequence.inc
+--source include/have_partition.inc
+set default_storage_engine= innodb;
+
+--connect (con2, localhost, root,,)
+--connection default
+
+--echo #
+--echo # Test insert
+--echo #
+
+--echo # Insert and add column
+create or replace table t1 (a int);
+insert t1 values (5);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+
+--connection con2
+--reap
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--enable_info
+--reap
+--disable_info
+select * from t1;
+
+--echo # Insert, error
+create or replace table t1 (a int);
+insert t1 values (5), (5);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 nowait add unique (a), algorithm= copy, lock= none;
+
+--connection con2
+--reap
+start transaction;
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--error ER_DUP_ENTRY
+--reap
+--connection con2
+commit;
+--connection default
+--disable_view_protocol
+select variable_value into @otd from information_schema.session_status where variable_name='Opened_table_definitions';
+select * from t1;
+select variable_value-@otd from information_schema.session_status where variable_name='Opened_table_definitions';
+--enable_view_protocol
+
+--echo # long transaction and add column
+create or replace table t1 (a int);
+insert t1 values (5);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+--send
+alter table t1 nowait add b int NULL, algorithm= copy, lock= none;
+
+--connection con2
+--reap
+start transaction;
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--error ER_LOCK_WAIT_TIMEOUT
+--reap
+select * from t1;
+--connection con2
+rollback;
+--connection default
+
+--echo # Insert and add NOT NULL column without default value
+create or replace table t1 (a int);
+insert t1 values (5);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add b int NOT NULL, algorithm= copy, lock= none;
+
+--connection con2
+--reap
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+select * from t1;
+
+--echo # Insert and add a column with a default value
+create or replace table t1 (a int);
+insert t1 values (5);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add b int NOT NULL default (222), algorithm= copy, lock= none;
+
+--connection con2
+--reap
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+select * from t1;
+
+--echo #
+--echo # Test update
+--echo #
+
+--echo # Update and add a column
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add c int default(1),
+            algorithm= copy, lock= none;
+
+--connection con2
+--reap
+update t1 set b= 55 where a = 1;
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--enable_info
+--reap
+--disable_info
+select * from t1;
+
+--echo # Update and add a column in the middle
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add c int default(1) after a,
+            algorithm= copy, lock= none;
+
+--connection con2
+--reap
+update t1 set b= 55 where a = 1;
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+select * from t1;
+
+--echo #
+--echo # Test primary key change
+--echo #
+
+--echo # Drop key, add key
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 drop primary key, add primary key(b),
+            algorithm= copy, lock= none;
+
+--connection con2
+--reap
+update t1 set b= 55 where a = 1;
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+select * from t1;
+
+--echo # Drop key, add key. Two updates
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 11);
+insert t1 values (2, 22);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 drop primary key, add primary key(b),
+            algorithm= copy, lock= none;
+
+--connection con2
+--reap
+update t1 set b= 33 where a = 1;
+update t1 set b= 44 where a = 2;
+
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+select * from t1;
+
+--echo #
+--echo # Various tests, see below
+--echo #
+
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 11);
+insert t1 values (2, 22);
+insert t1 values (3, 33);
+insert t1 values (4, 44);
+insert t1 values (5, 55);
+insert t1 values (6, 66);
+insert t1 values (7, 77);
+insert t1 values (8, 88);
+insert t1 values (9, 99);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 drop primary key, add primary key(b),
+            algorithm= copy, lock= none;
+
+--connection con2
+--reap
+
+--echo # Two updates
+update t1 set b= 1001 where a = 1;
+update t1 set b= 2002 where a = 2;
+
+--echo # Two updates in transaction
+set autocommit = 0;
+start transaction;
+update t1 set b= 3003 where a = 3;
+update t1 set b= 4004 where a = 4;
+commit;
+set autocommit = 1;
+
+--echo # Second update is rolled back
+update t1 set b= 5005 where a = 5;
+
+set autocommit = 0;
+start transaction;
+update t1 set b= 6006 where a = 6;
+rollback;
+set autocommit = 1;
+
+--echo # Second execution in transaction fails
+
+set autocommit = 0;
+start transaction;
+update t1 set b= 7007 where a = 7;
+--error ER_DUP_ENTRY
+update t1 set a= 8, b= 8008 where a = 8 or a = 9 order by a;
+commit;
+set autocommit = 1;
+
+select * from t1;
+
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+--sorted_result
+select * from t1;
+
+--echo #
+--echo # MYISAM. Only Inserts can be tested.
+--echo # (everything else is a table lock disallowing concurrent reads)
+--echo #
+
+create or replace table t1 (a int) engine=myisam;
+insert t1 values (5);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+
+--connection con2
+--reap
+insert into t1 values (123), (456), (789);
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+select * from t1;
+
+--echo # MYISAM + error
+
+create or replace table t1 (a int primary key) engine=myisam;
+insert t1 values (5);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+
+--connection con2
+--reap
+--error ER_DUP_ENTRY
+insert into t1 values (1),(2),(3),(4),(5),(6);
+select * from t1;
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+select * from t1;
+
+--echo # Aria + error
+
+set @@binlog_format=row; # otherwise aria upgrades the lock to TL_READ_NO_INSERT
+create or replace table t1 (a int primary key) engine=aria;
+insert t1 values (5);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+
+--connection con2
+--reap
+--error ER_DUP_ENTRY
+insert into t1 values (1),(2),(3),(4),(5),(6);
+select * from t1;
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+select * from t1;
+set @@binlog_format=default;
+
+--echo # Test incompatible changes
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 drop primary key, add primary key(b),
+            algorithm= copy, lock= none;
+
+--connection con2
+--reap
+update t1 set b= 44 where a = 1;
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--error ER_DUP_ENTRY
+--reap
+select * from t1;
+
+--echo # Test log read after EXCLUSIVE lock
+--echo # Transaction is started before ALTER, and UPDATE is made.
+--echo # Then more UPDATEs.
+
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 11);
+insert t1 values (2, 22);
+insert t1 values (3, 33);
+insert t1 values (4, 44);
+insert t1 values (5, 55);
+
+set debug_sync= 'alter_table_online_before_lock SIGNAL locking WAIT_FOR end';
+set debug_sync= 'alter_table_online_downgraded SIGNAL downgraded';
+
+--send
+alter table t1 drop primary key, add primary key(b),
+            algorithm= copy, lock= none;
+
+--connection con2
+begin;
+
+set debug_sync= 'now WAIT_FOR downgraded';
+update t1 set b= 111 where a = 1;
+
+set debug_sync= 'now WAIT_FOR locking';
+set debug_sync= 'now SIGNAL end';
+
+update t1 set b= 222 where a = 2;
+update t1 set b= 333 where a = 3;
+update t1 set b= 444 where a = 4;
+
+commit;
+
+update t1 set b= 555 where a = 5;
+
+--connection default
+--reap
+select * from t1;
+
+--echo #
+--echo # Test progress report.
+--echo #
+
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 11);
+insert t1 values (2, 22);
+insert t1 values (3, 33);
+insert t1 values (4, 44);
+
+set debug_sync= 'alter_table_online_before_lock SIGNAL locking WAIT_FOR end';
+set debug_sync= 'alter_table_online_downgraded SIGNAL downgraded'
+                                             ' WAIT_FOR start_replication';
+set debug_sync= 'alter_table_online_progress SIGNAL applied WAIT_FOR proceed'
+                                           ' EXECUTE 9';
+--let $con= `select connection_id()`
+
+--send
+alter table t1 drop primary key, add primary key(b),
+            algorithm= copy, lock= none;
+
+--connection con2
+
+set debug_sync= 'now WAIT_FOR downgraded';
+
+update t1 set b= 111 where a = 1;
+insert t1 values (5, 55);
+update t1 set b= 555 where a = 5;
+insert t1 values (6, 66);
+update t1 set b= 666 where a = 6;
+set debug_sync= 'now SIGNAL start_replication';
+
+--disable_query_log
+eval set @con= $con;
+--enable_query_log
+
+--echo # First signal is for log description event.
+set debug_sync= 'now WAIT_FOR applied';
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed WAIT_FOR locking';
+
+begin;
+
+update t1 set b= 222 where a = 2;
+update t1 set b= 333 where a = 3;
+update t1 set b= 444 where a = 4;
+
+commit;
+set debug_sync= 'now SIGNAL end WAIT_FOR applied';
+
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed WAIT_FOR applied';
+
+select stage, progress from INFORMATION_SCHEMA.PROCESSLIST where id = @con;
+set debug_sync= 'now SIGNAL proceed';
+
+--connection default
+--reap
+select * from t1;
+
+--echo #
+--echo # Test system versioning
+--echo #
+create or replace table t1 (a int primary key, b int);
+insert t1 values (1, 22);
+insert t1 values (3, 44);
+
+--connection con2
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+set timestamp = 1;
+
+--send
+alter table t1 add system versioning,
+            algorithm= copy, lock= none;
+
+--connection con2
+--reap
+set timestamp = 2;
+update t1 set b= 55 where a = 1;
+set timestamp = 3;
+insert into t1 values (6, 77);
+
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+show create table t1;
+select *, UNIX_TIMESTAMP(row_start), UNIX_TIMESTAMP(row_end) from t1 for system_time all;
+
+## at the moment DROP SYSTEM VERSIONING cannot be done online
+## because it not only alters the structure, but also deletes history rows
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t1 drop system versioning, algorithm= copy, lock= none;
+#--connection con2
+#--send
+#set debug_sync= 'now WAIT_FOR ended';
+#
+#--connection default
+#set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+#
+#--send
+#alter table t1 drop system versioning,
+#            algorithm= copy, lock= none;
+#
+#--connection con2
+#--reap
+#update t1 set b= 88 where a = 1;
+#
+#set debug_sync= 'now SIGNAL end';
+#
+#--connection default
+#--reap
+#show create table t1;
+#select *, UNIX_TIMESTAMP(row_start), UNIX_TIMESTAMP(row_end) from t1 for system_time all;
+#
+#--connection con2
+#--send
+#set debug_sync= 'now WAIT_FOR ended';
+#
+#--connection default
+#set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+#
+#--send
+#alter table t1 drop system versioning,
+#            algorithm= copy, lock= none;
+#
+#--connection con2
+#--reap
+#insert into t1 values (8, 99);
+#
+#set debug_sync= 'now SIGNAL end';
+#
+#--connection default
+#--reap
+#show create table t1;
+#select * from t1;
+
+--echo #
+--echo # Test ROLLBACK TO SAVEPOINT
+--echo #
+
+create or replace table t1 (a int);
+insert t1 values (1), (2);
+
+create or replace table t2 (a int);
+insert t2 values (1), (2);
+
+--connection con2
+begin;
+update t2 set a= 222 where a = 2;
+savepoint savie;
+update t2 set a= 111 where a = 1;
+
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+
+--connection con2
+--reap
+update t1 set a= 123 where a = 1;
+
+savepoint whoopsie;
+
+rollback to savepoint savie;
+commit;
+
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+
+select * from t1;
+select * from t2;
+
+
+create or replace table t1 (a int);
+insert t1 values (1), (2);
+
+create or replace table t2 (a int);
+insert t2 values (1), (2);
+
+create or replace table t3 (a int) engine=myisam;
+insert t3 values (1);
+
+--connection con2
+begin;
+update t2 set a= 222 where a = 2;
+savepoint savie;
+update t2 set a= 111 where a = 1;
+
+--send
+set debug_sync= 'now WAIT_FOR ended';
+
+--connection default
+set debug_sync= 'alter_table_copy_end SIGNAL ended WAIT_FOR end';
+
+--send
+alter table t1 add b int NULL, algorithm= copy, lock= none;
+
+--connection con2
+--reap
+update t1 set a= 222 where a = 2;
+--disable_view_protocol
+savepoint whoopsie;
+update t1 set a= 123 where a = 1;
+insert t3 values (2);
+
+select * from t1;
+rollback to savepoint whoopsie;
+select * from t1;
+select * from t3;
+commit;
+--enable_view_protocol
+
+set debug_sync= 'now SIGNAL end';
+
+--connection default
+--reap
+
+select * from t1;
+select * from t2;
+select * from t3;
+
+--echo # Cleanup
+set debug_sync= 'reset';
+drop table t1;
+drop table t2;
+drop table t3;
+
+#
+# Lossy alter, Update_row_log_event cannot find 'abcde2' in the new table
+#
+create table t1 (a char(6), b int) engine=innodb;
+insert t1 values ('abcde1',1),('abcde2',2);
+--send set debug_sync= 'now wait_for downgraded'
+--connection con2
+set sql_mode='';
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 modify a char(4), algorithm=copy, lock=none
+--connection default
+--reap
+update t1 set b=b+10 where a='abcde2';
+select * from t1;
+set debug_sync= 'now signal goforit';
+--connection con2
+--reap
+set sql_mode=default;
+--connection default
+show create table t1;
+select * from t1;
+drop table t1;
+set debug_sync= 'reset';
+
+--echo #
+--echo # MDEV-28930 ALTER TABLE Deadlocks with parallel TL_WRITE
+--echo #
+create table t1(a int) engine=myisam select 1;
+
+set debug_sync='alter_table_online_before_lock SIGNAL ready WAIT_FOR go_for_locking';
+--send
+alter table t1 force;
+
+--connection con2
+set debug_sync='now WAIT_FOR ready';
+set debug_sync='thr_multi_lock_before_thr_lock SIGNAL go_for_locking';
+update t1 set a=2;
+
+--connection default
+--reap
+
+set debug_sync='alter_table_online_before_lock SIGNAL ready WAIT_FOR go_for_locking';
+--send
+alter table mysql.global_priv force;
+
+--connection con2
+set debug_sync='now WAIT_FOR ready';
+set debug_sync='thr_multi_lock_before_thr_lock SIGNAL go_for_locking';
+create user user1@localhost;
+
+--connection default
+--reap
+
+set debug_sync=reset;
+drop user user1@localhost;
+drop table t1;
+
+--echo #
+--echo # MDEV-28959 Online alter ignores strict table mode
+--echo #
+create table t1 (a int);
+insert into t1 values (1),(2),(3);
+--send set debug_sync= 'now wait_for downgraded'
+
+--connection con2
+set sql_mode='STRICT_TRANS_TABLES,STRICT_ALL_TABLES';
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 modify a int not null, algorithm=copy, lock=none
+
+--connection default
+--reap
+insert into t1 values (null),(null);
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--error WARN_DATA_TRUNCATED
+--reap
+show create table t1;
+select * from t1;
+set sql_mode=default;
+--connection default
+drop table t1;
+set debug_sync= reset;
+
+--echo #
+--echo # MDEV-28967 Assertion `marked_for_write_or_computed()' failed in Field_new_decimal::store_value / online_alter_read_from_binlog`
+--echo #
+create  table t1 (a decimal(8,2), b varchar(8));
+insert into t1 (b) values ('x');
+
+--send set debug_sync= 'now wait_for downgraded'
+
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 force, algorithm=copy, lock=none
+
+--connection default
+--reap
+insert t1 (b) values ('k');
+insert t1 (b) values ('m');
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--reap
+--connection default
+drop table t1;
+set debug_sync= reset;
+
+--echo #
+--echo # MDEV-29021 ALTER TABLE fails when a stored virtual column is dropped and added
+--echo #
+create table t1 (a char(9), b char(9) as (a) stored);
+insert into t1(a) values ('foobar');
+
+--send set debug_sync= 'now wait_for downgraded'
+
+--connection con2
+set sql_mode='';
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 drop b, add b char(3) as (a) stored, algorithm=copy, lock=none
+
+--connection default
+--reap
+update t1 set a = 'foobarqux';
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--reap
+set sql_mode=default;
+--connection default
+drop table t1;
+set debug_sync= reset;
+
+--echo # (duplicate) MDEV-29007 Assertion `marked_for_write_or_computed()'
+--echo # failed upon online ADD COLUMN .. FIRST
+create table t (a int);
+insert into t values (1),(2);
+--send
+set debug_sync= 'now wait_for downgraded';
+
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t add c int first, algorithm=copy, lock=none;
+
+--connection default
+--reap
+insert into t values (3);
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--reap
+--connection default
+drop table t;
+set debug_sync= reset;
+
+--echo # UNIQUE blob duplicates are not ignored.
+
+create table t1 (b blob);
+insert into t1 values ('foo'),('bar');
+--send
+set debug_sync= 'now wait_for downgraded';
+
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t1 add unique(b), algorithm=copy, lock=none;
+
+--connection default
+--reap
+insert into t1 values ('qux'),('foo');
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--error ER_DUP_ENTRY
+--reap
+select * from t1;
+show create table t1;
+
+# Cleanup
+--connection default
+drop table t1;
+set debug_sync= reset;
+
+--echo #
+--echo # DELETE with added virtual column
+--echo #
+CREATE TABLE t1 (a CHAR(3), b CHAR(3) AS (a));
+INSERT INTO t1 (a) VALUES ('foo'),('bar');
+--send
+set debug_sync= 'now wait_for downgraded';
+
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+ALTER TABLE t1 ADD c INT, ALGORITHM=COPY, LOCK=NONE;
+
+--connection default
+--reap
+DELETE FROM t1;
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--reap
+select * from t1;
+
+--connection default
+DROP TABLE t1;
+set debug_sync= reset;
+
+--echo #
+--echo # Do not ignore sql_mode when replicating
+--echo #
+create table t1 (a int);
+insert into t1 values (1);
+
+--send set debug_sync= 'now wait_for downgraded'
+
+--connection con2
+set sql_mode='STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO';
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 add b int as (1/a) stored, algorithm=copy, lock=none
+
+--connection default
+--reap
+update t1 set a= 0 where a=1;
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--error ER_DIVISION_BY_ZERO
+--reap
+
+set sql_mode= default;
+--connection default
+drop table t1;
+set debug_sync= reset;
+
+--echo #
+--echo # MDEV-29067 Online alter ignores check constraint violation
+--echo #
+
+--echo ## CHECK, INSERT
+create table t1 (a int);
+insert t1 values (1),(2);
+--send set debug_sync= 'now wait_for downgraded'
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 add check (a<10), algorithm=copy, lock=none
+--connection default
+--reap
+insert t1 values (11),(12);
+set debug_sync= 'now signal goforit';
+--connection con2
+--error ER_CONSTRAINT_FAILED
+--reap
+--connection default
+show create table t1;
+select * from t1;
+drop table t1;
+
+--echo ## DEFAULT, INSERT
+create table t1 (a int);
+insert t1 values (1),(2);
+--send set debug_sync= 'now wait_for downgraded'
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 add b int default(a+10), algorithm=copy, lock=none
+--connection default
+--reap
+insert t1 values (11),(12);
+set debug_sync= 'now signal goforit';
+--connection con2
+--reap
+--connection default
+show create table t1;
+select * from t1;
+drop table t1;
+set debug_sync= 'reset';
+
+--echo ## CHECK, UPDATE
+create table t1 (a int) engine=innodb;
+insert t1 values (1),(2),(3),(4);
+--send set debug_sync= 'now wait_for downgraded'
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 add check (a<10), algorithm=copy, lock=none
+--connection default
+--reap
+update t1 set a=a+10 where a > 2;
+set debug_sync= 'now signal goforit';
+--connection con2
+--error ER_CONSTRAINT_FAILED
+--reap
+--connection default
+show create table t1;
+select * from t1;
+drop table t1;
+
+--echo ## DEFAULT, UPDATE
+create table t1 (a int) engine=innodb;
+insert t1 values (1),(2),(3),(4);
+--send set debug_sync= 'now wait_for downgraded'
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 add b int default(a), algorithm=copy, lock=none
+--connection default
+--reap
+update t1 set a=a+10 where a > 2;
+insert t1 values(5);
+update t1 set a=a+10 where a = 5;
+
+set debug_sync= 'now signal goforit';
+--connection con2
+--reap
+--connection default
+show create table t1;
+select * from t1;
+drop table t1;
+set debug_sync= 'reset';
+
+--echo ## VCOL + CHECK
+create table t1 (a int) engine=innodb;
+insert t1 values (1),(2),(3),(4);
+--send set debug_sync= 'now wait_for downgraded'
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send alter table t1 add b int as (a), add check(b=a), algorithm=copy, lock=none
+--connection default
+--reap
+update t1 set a=a+10 where a > 2;
+insert t1 values(5);
+update t1 set a=a+10 where a = 5;
+
+set debug_sync= 'now signal goforit';
+--connection con2
+--reap
+--connection default
+show create table t1;
+select * from t1;
+drop table t1;
+set debug_sync= 'reset';
+
+--echo #
+--echo # MDEV-29013 ER_KEY_NOT_FOUND/lock timeout upon online alter
+--echo # with long unique indexes
+--echo #
+create table t1 (b text not null, unique(b));
+insert into t1 values ('foo'),('bar');
+--send
+set debug_sync= 'now wait_for downgraded';
+
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t1 add c int, algorithm=copy, lock=none;
+
+--connection default
+--reap
+delete from t1;
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--reap
+
+--connection default
+drop table t1;
+set debug_sync= reset;
+
+--echo ###
+
+create table t1 (a text, unique(a)) engine=innodb;
+create table t2 (b text, unique(b)) engine=innodb;
+insert into t2 values (null),(null);
+--send
+set debug_sync= 'now wait_for downgraded';
+
+--connection con2
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t2 add column c int, algorithm=copy, lock=none;
+
+--connection default
+--reap
+delete from t2;
+set debug_sync= 'now signal goforit';
+
+--connection con2
+--reap
+
+--connection default
+alter table t2 force;
+alter table t1 force;
+
+drop table t1, t2;
+set debug_sync= reset;
+
+--echo #
+--echo # MDEV-29038 XA assertions failing in binlog_rollback and binlog_commit
+--echo #
+create table t (a int) engine=innodb;
+insert into t values (1);
+xa begin 'xid';
+--send
+set debug_sync= 'now wait_for downgraded';
+
+--connect (con1,localhost,root,,test)
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t force, algorithm=copy, lock=none;
+
+--connection default
+--reap
+insert into t values (2);
+set debug_sync= 'now signal goforit';
+xa end 'xid';
+xa rollback 'xid';
+
+xa begin 'xid';
+--connection con1
+--reap
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t force, algorithm=copy, lock=none;
+
+--connection default
+set debug_sync= 'now wait_for downgraded';
+insert into t values (3);
+set debug_sync= 'now signal goforit';
+xa end 'xid';
+xa commit 'xid' one phase;
+
+# Cleanup
+--connection con1
+--reap
+--connection default
+drop table t;
+set debug_sync= reset;
+
+--echo #
+--echo # MDEV-29069 ER_KEY_NOT_FOUND upon online autoinc addition and
+--echo # concurrent DELETE
+--echo #
+set @old_dbug=@@debug_dbug;
+set debug_dbug="+d,rpl_report_chosen_key";
+
+# UB
+#
+# create table t (a int);
+# insert into t values (10),(20),(30);
+#
+# set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+# --send
+# alter table t add pk int auto_increment primary key, algorithm=copy, lock=none;
+# --connection con2
+# set debug_sync= 'now wait_for downgraded';
+# delete from t where a = 20;
+# update t set a = a + 1 where a = 10;
+# set debug_sync= 'now signal goforit';
+#
+# --connection default
+# --reap
+# select * from t;
+
+--echo #
+--echo # Add clumsy DEFAULT
+--echo #
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t add b int default(RAND() * 20), add key(b),
+              algorithm=copy, lock=none;
+--connection con2
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+--connection default
+--reap
+select a from t;
+
+--echo # CURRENT_TIMESTAMP
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t add b timestamp default CURRENT_TIMESTAMP, add key(b),
+              algorithm=copy, lock=none;
+--connection con2
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+--connection default
+--reap
+select a from t;
+
+--echo # CURRENT_TIMESTAMP, mixed key
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t add b timestamp default CURRENT_TIMESTAMP, add key(a, b),
+              algorithm=copy, lock=none;
+--connection con2
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+
+--connection default
+--reap
+select a from t;
+
+--echo # Mixed primary key
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t add b int default (a+1), add primary key(b, a),
+              algorithm=copy, lock=none;
+--connection con2
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+
+--connection default
+--reap
+select a from t;
+
+--echo #
+--echo # Normal row, could be used as a key
+--echo #
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t add b int as (a * 10) unique, algorithm=copy, lock=none;
+--connection con2
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+
+--connection default
+--reap
+
+--echo #
+--echo # Add key for old row
+--echo #
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t add unique(a), algorithm=copy, lock=none;
+--connection con2
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+set debug_sync= 'now signal goforit';
+
+--connection default
+--reap
+
+
+--echo #
+--echo # Useful UNIQUE, though a virtual column on another extra field
+--echo #
+create or replace table t (a int primary key, b int default (a+1));
+insert into t values (10, 10),(20, 20),(30, 30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t drop primary key, add c int default(a),
+              add d int as (c) stored unique, algorithm=copy, lock=none;
+--connection con2
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 2 where a = 10;
+set debug_sync= 'now signal goforit';
+--connection default
+--reap
+select * from t;
+
+--echo #
+--echo # Now this index is not usable (missing DEFAULT on field c)
+--echo #
+create or replace table t (a int primary key, b int);
+insert into t values (10, 10),(20, 20),(30, 30);
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goforit';
+--send
+alter table t drop primary key, add c real default(rand(a)),
+              add d real as (c) stored unique, algorithm=copy, lock=none;
+--connection con2
+set debug_sync= 'now wait_for downgraded';
+delete from t where a = 20;
+update t set a = a + 2 where a = 10;
+set debug_sync= 'now signal goforit';
+--connection default
+--reap
+select a, b from t;
+
+# Cleanup
+drop table t;
+set debug_sync= reset;
+set debug_dbug= @old_dbug;
+--connection default
+
+--echo #
+--echo # MDEV-30902 Server crash in LEX::first_lists_tables_same
+--echo #
+create table t1 engine=myisam select 1 as x ;
+create procedure p() alter table t1 engine=heap;
+
+set debug_sync= 'alter_table_copy_end signal ended wait_for end';
+send call p;
+
+--connection con1
+set debug_sync= 'now wait_for ended';
+insert into t1 values (2);
+set debug_sync= 'now signal end';
+
+--connection default
+--reap
+call p;
+
+drop table t1;
+drop procedure p;
+set debug_sync=reset;
+
+--echo #
+--echo # MDEV-30902 Server crash in LEX::first_lists_tables_same
+--echo #
+create table t (id int, s date, e date, period for p(s,e),
+                unique(id, p without overlaps)) engine=aria;
+insert into t values (1,'1971-01-01','1971-01-02');
+
+set debug_sync= 'alter_table_online_before_lock signal lock wait_for goon';
+send alter table t force;
+
+--connection con1
+set debug_sync= 'now wait_for lock';
+delete from t;
+set debug_sync= 'now signal goon';
+
+--connection default
+--reap
+drop table t;
+
+--echo #
+--echo # MDEV-30924 Server crashes in MYSQL_LOG::is_open upon ALTER vs FUNCTION
+--echo #
+create table t (a int);
+insert into t values (1),(2);
+
+--delimiter $
+create function f () returns int
+begin
+  update t set a = 10;
+  return 0;
+end $
+--delimiter ;
+
+set debug_sync= 'alter_table_online_downgraded signal downgraded wait_for goon';
+send alter table t force, algorithm=copy;
+
+--connection con1
+set debug_sync= 'now wait_for downgraded';
+select f();
+set debug_sync= 'now signal goon';
+
+--connection default
+--reap
+drop table t;
+drop function f;
+
+--echo # Partitioning
+create or replace table t1 (a serial);
+insert t1 values (5), (6), (7);
+
+set debug_sync= 'alter_table_online_downgraded SIGNAL downgraded wait_for goon';
+send alter table t1 ENGINE=InnoDB, ALGORITHM=COPY, LOCK=NONE
+                    PARTITION BY HASH(a) PARTITIONS 5;
+
+--connection con1
+set debug_sync= 'now WAIT_FOR downgraded';
+insert into t1 values (123), (456), (789);
+update t1 set a= a+100;
+set debug_sync= 'now SIGNAL goon';
+
+--connection default
+--reap
+select * from t1;
+
+drop table t1;
+
+--echo #
+--echo # MDEV-31033 ER_KEY_NOT_FOUND upon online COPY ALTER on a partitioned
+--echo # table
+create table t (a int) partition by hash(a) partitions 2;
+insert into t values (1),(3);
+set debug_sync= 'alter_table_online_downgraded SIGNAL downgraded wait_for goon';
+send alter table t force, algorithm=copy, lock=none;
+
+--connection con1
+set debug_sync= 'now WAIT_FOR downgraded';
+update t set a = a + 1;
+insert t values (1),(2);
+delete from t where a = 4 limit 1;
+set debug_sync= 'now SIGNAL goon';
+
+--connection default
+--reap
+
+select * from t;
+
+drop table t;
+
+--echo #
+--echo # MDEV-31043 ER_KEY_NOT_FOUND upon concurrent ALTER and transaction
+--echo #
+let $i=2;
+let local_engine=myisam;
+while ($i) {
+eval create table t (a int, b int) engine=$local_engine;
+insert into t values (1,10),(2,20);
+set debug_sync= 'alter_table_online_before_lock signal burnit wait_for goforit';
+--send
+alter table t add c int, algorithm=copy, lock=none;
+
+--connection con1
+
+set debug_sync= 'now wait_for burnit';
+update t set b = 100;
+start transaction;
+update t set b = 200;
+
+--connection con2
+delete from t order by a limit 1;
+delete from t order by a limit 1;
+select * from t;
+--connection con1
+commit;
+set debug_sync= 'now signal goforit';
+
+--connection default
+--reap
+select * from t;
+drop table t;
+dec $i;
+let local_engine=aria;
+}
+
+--echo #
+--echo # MDEV-30949 Direct leak in binlog_online_alter_end_trans
+--echo #
+create table t (f longblob default null) engine=myisam;
+insert into t values (null);
+
+set debug_sync= "alter_table_copy_end signal copy wait_for goon";
+set debug_sync= "alter_table_online_before_lock signal lock wait_for end";
+
+send alter table t force, algorithm=copy;
+
+--connection con1
+set debug_sync= "now wait_for copy";
+insert into t select repeat('a',130000);
+set debug_sync= "now signal goon wait_for lock";
+insert into t select repeat('a',130000);
+set debug_sync= "now signal end";
+
+
+--connection default
+--reap
+drop table t;
+
+--echo #
+--echo # Test that correct fields are marked as explicit:
+--echo # Drop a, reorder b, add new column with default.
+--echo #
+create table t (a int primary key, b int) engine=innodb;
+insert into t values (1, 1), (2, 2), (3, 3);
+
+set debug_sync= "alter_table_copy_end signal copy wait_for goon";
+send alter table t drop primary key, drop a,
+                   change b c bigint,
+                   add x longblob default 123456;
+
+--connection con1
+set debug_sync= "now wait_for copy";
+
+update t set b = 5 where a = 1;
+update t set b = NULL where a = 1;
+select * from t;
+update t set a = 100 where a = 1;
+update t set b = -10 where a = 100;
+select * from t;
+
+set debug_sync= "now signal goon";
+
+--connection default
+--reap
+select * from t;
+drop table t;
+
+--echo # Test that all the fields are unpacked.
+create table t (a int, b int) engine=innodb;
+insert into t values (NULL, 123), (NULL, 456);
+
+set debug_sync= "alter_table_copy_end signal copy wait_for goon";
+send alter table t drop a, add primary key(b), algorithm=copy;
+--connection con1
+set debug_sync= "now wait_for copy";
+update t set b = b + 100;
+
+set debug_sync= "now signal goon";
+
+--connection default
+--reap
+select * from t;
+
+drop table t;
+
+
+set debug_sync= reset;
+let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
+let SEARCH_PATTERN= Slave SQL;
+--source include/search_pattern_in_file.inc
+
+
+--echo #
+--echo # MDEV-31646 Online alter applies binlog cache limit to cache writes
+--echo #
+create table t (pk int primary key, a varchar(100)) engine=MyISAM;
+insert into t select seq, repeat('x', 100) from seq_1_to_500;
+
+set @cache.size= @@max_binlog_cache_size;
+set global max_binlog_cache_size= 4096;
+
+send set debug_sync= 'now wait_for do_updates';
+
+--connection con1
+set debug_sync= 'alter_table_online_progress signal do_updates wait_for go';
+send alter table t add b int, algorithm=copy, lock=none;
+
+--connection default
+--reap
+update t set a = repeat('y', 100);
+show warnings;
+
+set debug_sync= 'now signal go';
+
+--connection con1
+--reap
+show warnings;
+
+--connection default
+drop table t;
+set debug_sync= reset;
+set global max_binlog_cache_size= @cache.size;
+
+--echo # Now make sure that smaller limits will be processed fine
+
+set @old_dbug=@@debug_dbug;
+create table t (pk int primary key, a text) engine=MyISAM;
+insert into t select seq, repeat('x', 1000) from seq_1_to_50;
+
+--connection con1
+set debug_sync= 'alter_table_online_progress signal do_updates wait_for go';
+--send
+alter table t add b int, algorithm=copy, lock=none;
+
+--connection default
+set debug_sync= 'now wait_for do_updates';
+set debug_dbug="+d,online_alter_small_cache";
+--error ER_STMT_CACHE_FULL
+update t set a = repeat('y', 1000);
+show warnings;
+
+set debug_sync= 'now signal go';
+
+--connection con1
+--reap
+show warnings;
+
+--connection default
+drop table t;
+set debug_sync= reset;
+set debug_dbug= @old_dbug;
+
+
+
+
+create table t (id int, a int, b text, primary key (id));
+insert into t values (1,10,''),(2,20,'');
+
+set debug_sync= 'alter_table_online_progress signal do_update wait_for go';
+send alter table t force, algorithm=copy, lock=none;
+
+--connection con1
+set @old_binlog_row_image= @@binlog_row_image;
+set binlog_row_image= noblob;
+set debug_sync= 'now wait_for do_update';
+
+update t set a = 1;
+delete from t where id = 1;
+
+set debug_sync= 'now signal go';
+set binlog_row_image= @old_binlog_row_image;
+
+--connection default
+--reap
+select * from t;
+
+drop table t;
+set debug_sync= reset;
+
+--echo #
+--echo # MDEV-31812 Add switch to old_mode to disable non-locking ALTER
+--echo #
+set @old_old_mode= @@old_mode;
+create or replace table t1 (a int primary key, b int);
+set debug_sync= 'alter_table_copy_end SIGNAL copy_end WAIT_FOR proceed';
+send alter table t1 drop primary key, add primary key(b), algorithm= copy;
+
+--connection con2
+set debug_sync= 'now WAIT_FOR copy_end';
+select if(max_stage = 4, "online", "old") as mode
+        from information_schema.processlist where id = @con;
+set debug_sync= 'now SIGNAL proceed';
+
+--connection default
+--reap
+
+set old_mode= LOCK_ALTER_TABLE_COPY;
+set debug_sync= 'alter_table_copy_end SIGNAL copy_end WAIT_FOR proceed';
+send alter table t1 drop primary key, add primary key(b), algorithm= copy;
+--connection con2
+set debug_sync= 'now WAIT_FOR copy_end';
+select if(max_stage = 4, "online", "old") as mode
+        from information_schema.processlist where id = @con;
+set debug_sync= 'now SIGNAL proceed';
+
+--connection default
+--reap
+
+set debug_sync= 'alter_table_copy_end SIGNAL copy_end WAIT_FOR proceed';
+send alter table t1 drop primary key, add primary key(b),
+        algorithm= copy, lock=none;
+--connection con2
+set debug_sync= 'now WAIT_FOR copy_end';
+
+select if(max_stage = 4, "online", "old") as mode
+        from information_schema.processlist where id = @con;
+set debug_sync= 'now SIGNAL proceed';
+
+--connection default
+--reap
+set old_mode= @old_old_mode;
+drop table t1;
+set debug_sync= reset;
+
+
+--disconnect con1
+--disconnect con2
+--echo #
+--echo # End of 11.2 tests
+--echo #
diff --git a/mysql-test/main/backup_lock.result b/mysql-test/main/backup_lock.result
index 16806148c67..e6c45f6337c 100644
--- a/mysql-test/main/backup_lock.result
+++ b/mysql-test/main/backup_lock.result
@@ -41,7 +41,7 @@ connection default;
 start transaction;
 insert into t1 values (1);
 connection con1;
-alter table t1 add column (j int), algorithm copy;
+alter table t1 add column (j int), algorithm copy, lock shared;
 connection con2;
 backup stage flush;
 SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info
diff --git a/mysql-test/main/backup_lock.test b/mysql-test/main/backup_lock.test
index 5453e5b0e7d..9de45256d34 100644
--- a/mysql-test/main/backup_lock.test
+++ b/mysql-test/main/backup_lock.test
@@ -51,7 +51,7 @@ insert into t1 values (1);
 
 connection con1;
 # Waits on MDL
---send alter table t1 add column (j int), algorithm copy
+--send alter table t1 add column (j int), algorithm copy, lock shared
 
 connection con2;
 let $wait_condition=
diff --git a/mysql-test/main/delayed.result b/mysql-test/main/delayed.result
index 4b482c8bb66..d5397e73a22 100644
--- a/mysql-test/main/delayed.result
+++ b/mysql-test/main/delayed.result
@@ -384,7 +384,7 @@ SELECT * FROM t1 WHERE a=0;
 a
 connection con1;
 # Sending:
-ALTER TABLE t1 MODIFY a INT UNSIGNED;;
+ALTER TABLE t1 MODIFY a INT UNSIGNED, LOCK=SHARED;;
 connection default;
 # Wait until ALTER TABLE is blocked on table 't1'.
 INSERT DELAYED INTO t1 VALUES (3);
diff --git a/mysql-test/main/delayed.test b/mysql-test/main/delayed.test
index a99af07232c..1de23aac127 100644
--- a/mysql-test/main/delayed.test
+++ b/mysql-test/main/delayed.test
@@ -458,7 +458,7 @@ SELECT * FROM t1 WHERE a=0;
 
 connection con1;
 --echo # Sending:
---send ALTER TABLE t1 MODIFY a INT UNSIGNED;
+--send ALTER TABLE t1 MODIFY a INT UNSIGNED, LOCK=SHARED;
 
 connection default;
 --echo # Wait until ALTER TABLE is blocked on table 't1'.
diff --git a/mysql-test/main/lock_multi_bug38499.result b/mysql-test/main/lock_multi_bug38499.result
index 521ea010816..de552ca0872 100644
--- a/mysql-test/main/lock_multi_bug38499.result
+++ b/mysql-test/main/lock_multi_bug38499.result
@@ -17,7 +17,7 @@ INSERT INTO t2 VALUES (1, 1), (2, 2), (3, 3), (4, 4);
 connection default;
 # 1.2.2. PS mode
 connection default;
-ALTER TABLE t1 ADD COLUMN a INT;
+ALTER TABLE t1 ADD COLUMN a INT, LOCK=SHARED;
 # 2. test UNIONs
 # 2.1. test altering of columns that multiupdate doesn't use
 # 2.1.1. normal mode
diff --git a/mysql-test/main/lock_multi_bug38499.test b/mysql-test/main/lock_multi_bug38499.test
index c489712e5d8..9122b453eb2 100644
--- a/mysql-test/main/lock_multi_bug38499.test
+++ b/mysql-test/main/lock_multi_bug38499.test
@@ -36,8 +36,8 @@ while ($i) {
   send UPDATE t1, (SELECT 1 FROM t2 t1i) d SET a = 0 WHERE 1=0;
 
 --connection locker
-  ALTER TABLE t1 ADD COLUMN (c INT);
-  ALTER TABLE t1 DROP COLUMN c;
+  ALTER TABLE t1 ADD COLUMN (c INT), LOCK=SHARED;
+  ALTER TABLE t1 DROP COLUMN c, LOCK=SHARED;
 
 --connection writer
 --reap
@@ -56,8 +56,8 @@ while ($i) {
 --send EXECUTE stmt
 
 --connection locker
-  ALTER TABLE t1 ADD COLUMN (c INT);
-  ALTER TABLE t1 DROP COLUMN c;
+  ALTER TABLE t1 ADD COLUMN (c INT), LOCK=SHARED;
+  ALTER TABLE t1 DROP COLUMN c, LOCK=SHARED;
 
 --connection writer
 --reap
@@ -76,7 +76,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_DUP_FIELDNAME
-  ALTER TABLE t1 ADD COLUMN a int(11) unsigned default NULL;
+  ALTER TABLE t1 ADD COLUMN a int(11) unsigned default NULL, LOCK=SHARED;
   UPDATE t1 SET a=b;
 
 --connection writer
@@ -84,7 +84,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_CANT_DROP_FIELD_OR_KEY
-  ALTER TABLE t1 DROP COLUMN a;
+  ALTER TABLE t1 DROP COLUMN a, LOCK=SHARED;
 
 --connection writer
 --error 0,ER_BAD_FIELD_ERROR # unknown column error
@@ -101,7 +101,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_DUP_FIELDNAME
-  ALTER TABLE t1 ADD COLUMN a INT;
+  ALTER TABLE t1 ADD COLUMN a INT, LOCK=SHARED;
   UPDATE t1 SET a=b;
 
 --connection writer
@@ -110,7 +110,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_CANT_DROP_FIELD_OR_KEY
-  ALTER TABLE t1 DROP COLUMN a;
+  ALTER TABLE t1 DROP COLUMN a, LOCK=SHARED;
 
 --connection writer
 --error 0,ER_BAD_FIELD_ERROR # Unknown column 'a' in 'field list'
@@ -118,7 +118,7 @@ while ($i) {
 }
 --enable_query_log
 --connection default
-ALTER TABLE t1 ADD COLUMN a INT;
+ALTER TABLE t1 ADD COLUMN a INT, LOCK=SHARED;
 
 --echo # 2. test UNIONs
 --echo # 2.1. test altering of columns that multiupdate doesn't use
@@ -133,8 +133,8 @@ while ($i) {
   send UPDATE t1, ((SELECT 1 FROM t1 t1i) UNION (SELECT 2 FROM t1 t1ii)) e SET a = 0 WHERE 1=0;
 
 --connection locker
-  ALTER TABLE t1 ADD COLUMN (c INT);
-  ALTER TABLE t1 DROP COLUMN c;
+  ALTER TABLE t1 ADD COLUMN (c INT), LOCK=SHARED;
+  ALTER TABLE t1 DROP COLUMN c, LOCK=SHARED;
 
 --connection writer
 --reap
@@ -153,8 +153,8 @@ while ($i) {
 --send EXECUTE stmt
 
 --connection locker
-  ALTER TABLE t1 ADD COLUMN (c INT);
-  ALTER TABLE t1 DROP COLUMN c;
+  ALTER TABLE t1 ADD COLUMN (c INT), LOCK=SHARED;
+  ALTER TABLE t1 DROP COLUMN c, LOCK=SHARED;
 
 --connection writer
 --reap
@@ -173,7 +173,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_DUP_FIELDNAME
-  ALTER TABLE t1 ADD COLUMN a int(11) unsigned default NULL;
+  ALTER TABLE t1 ADD COLUMN a int(11) unsigned default NULL, LOCK=SHARED;
   UPDATE t1 SET a=b;
 
 --connection writer
@@ -181,7 +181,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_CANT_DROP_FIELD_OR_KEY
-  ALTER TABLE t1 DROP COLUMN a;
+  ALTER TABLE t1 DROP COLUMN a, LOCK=SHARED;
 
 --connection writer
 --error 0,ER_BAD_FIELD_ERROR # Unknown column 'a' in 'field list'
@@ -198,7 +198,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_DUP_FIELDNAME
-  ALTER TABLE t1 ADD COLUMN a INT;
+  ALTER TABLE t1 ADD COLUMN a INT, LOCK=SHARED;
   UPDATE t1 SET a=b;
 
 --connection writer
@@ -207,7 +207,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_CANT_DROP_FIELD_OR_KEY
-  ALTER TABLE t1 DROP COLUMN a;
+  ALTER TABLE t1 DROP COLUMN a, LOCK=SHARED;
 
 --connection writer
 --error 0,ER_BAD_FIELD_ERROR # Unknown column 'a' in 'field list'
diff --git a/mysql-test/main/lock_multi_bug38691.test b/mysql-test/main/lock_multi_bug38691.test
index 9760c1a873a..62ff3a5bc3b 100644
--- a/mysql-test/main/lock_multi_bug38691.test
+++ b/mysql-test/main/lock_multi_bug38691.test
@@ -48,8 +48,8 @@ while ($i) {
          SET a = NULL WHERE t1.b <> t2.b;
 
 --connection locker
-  ALTER TABLE t2 ADD COLUMN (c INT);
-  ALTER TABLE t2 DROP COLUMN c;
+  ALTER TABLE t2 ADD COLUMN (c INT), LOCK=SHARED;
+  ALTER TABLE t2 DROP COLUMN c, LOCK=SHARED;
 
 --connection writer
 --reap
@@ -69,8 +69,8 @@ while ($i) {
 --send EXECUTE stmt
 
 --connection locker
-  ALTER TABLE t2 ADD COLUMN (c INT);
-  ALTER TABLE t2 DROP COLUMN c;
+  ALTER TABLE t2 ADD COLUMN (c INT), LOCK=SHARED;
+  ALTER TABLE t2 DROP COLUMN c, LOCK=SHARED;
 
 --connection writer
 --reap
@@ -91,7 +91,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_DUP_FIELDNAME
-  ALTER TABLE t2 ADD COLUMN a int(11) unsigned default NULL;
+  ALTER TABLE t2 ADD COLUMN a int(11) unsigned default NULL, LOCK=SHARED;
   UPDATE t2 SET a=b;
 
 --connection writer
@@ -99,7 +99,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_CANT_DROP_FIELD_OR_KEY
-  ALTER TABLE t2 DROP COLUMN a;
+  ALTER TABLE t2 DROP COLUMN a, LOCK=SHARED;
 
 --connection writer
 --error 0,ER_BAD_FIELD_ERROR
@@ -116,7 +116,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_DUP_FIELDNAME
-  ALTER TABLE t2 ADD COLUMN a int(11) unsigned default NULL;
+  ALTER TABLE t2 ADD COLUMN a int(11) unsigned default NULL, LOCK=SHARED;
   UPDATE t2 SET a=b;
 
 --connection writer
@@ -125,7 +125,7 @@ while ($i) {
 
 --connection locker
 --error 0,ER_CANT_DROP_FIELD_OR_KEY
-  ALTER TABLE t2 DROP COLUMN a;
+  ALTER TABLE t2 DROP COLUMN a, LOCK=SHARED;
 
 --connection writer
 --error 0,ER_BAD_FIELD_ERROR
diff --git a/mysql-test/main/mdl_sync.result b/mysql-test/main/mdl_sync.result
index 0ffe2f745a0..939e6a01ed2 100644
--- a/mysql-test/main/mdl_sync.result
+++ b/mysql-test/main/mdl_sync.result
@@ -954,7 +954,7 @@ insert into t1 values (1);
 connection default;
 # Add pending SNW lock.
 # Sending:
-alter table t1 add primary key (c1);;
+alter table t1 add primary key (c1), lock=shared;;
 # 
 connection mdl_con1;
 # Check that ALTER TABLE is waiting with pending SNW lock.
@@ -1964,7 +1964,7 @@ create table t1 (i int);
 # Ensure that ALTER waits once it has acquired SNW lock.
 set debug_sync='alter_table_copy_after_lock_upgrade SIGNAL parked1 WAIT_FOR go1';
 # Sending:
-alter table t1 add column j int;
+alter table t1 add column j int, lock=shared;
 #
 connection deadlock_con1;
 # Wait till ALTER acquires SNW lock and stops.
@@ -2330,7 +2330,7 @@ c1	c2	c3
 #
 connection con46273;
 set debug_sync='alter_table_copy_after_lock_upgrade SIGNAL alter_table_locked WAIT_FOR alter_go';
-alter table t1 add column e int, rename to t2;;
+alter table t1 add column e int, rename to t2, lock=shared;;
 #
 connection default;
 set debug_sync='now WAIT_FOR alter_table_locked';
@@ -2586,7 +2586,7 @@ connection default;
 # table lock and get blocked on sync point.
 set debug_sync= 'alter_table_copy_after_lock_upgrade SIGNAL parked WAIT_FOR go';
 # Sending:
-alter table t1 add column j int;
+alter table t1 add column j int, lock=shared;
 connection con1;
 # Wait until ALTER TABLE gets blocked on a sync point.
 set debug_sync= 'now WAIT_FOR parked';
diff --git a/mysql-test/main/mdl_sync.test b/mysql-test/main/mdl_sync.test
index 3df19aca806..ca91528882e 100644
--- a/mysql-test/main/mdl_sync.test
+++ b/mysql-test/main/mdl_sync.test
@@ -1196,14 +1196,14 @@ insert into t1 values (1);
 connection default;
 --echo # Add pending SNW lock.
 --echo # Sending:
---send alter table t1 add primary key (c1);
+--send alter table t1 add primary key (c1), lock=shared;
 --echo # 
 connection mdl_con1;
 --echo # Check that ALTER TABLE is waiting with pending SNW lock.
 let $wait_condition=
 select count(*) = 1 from information_schema.processlist
 where state = "Waiting for table metadata lock" and
-      info = "alter table t1 add primary key (c1)";
+      info = "alter table t1 add primary key (c1), lock=shared";
 --source include/wait_condition.inc
 --echo # Check that S, SH and SR locks are compatible with pending SNW
 handler t1 open t;
@@ -2431,7 +2431,7 @@ create table t1 (i int);
 --echo # Ensure that ALTER waits once it has acquired SNW lock.
 set debug_sync='alter_table_copy_after_lock_upgrade SIGNAL parked1 WAIT_FOR go1';
 --echo # Sending:
---send alter table t1 add column j int
+--send alter table t1 add column j int, lock=shared
 
 --echo #
 connection deadlock_con1;
@@ -2986,7 +2986,7 @@ select * from t1 where c2 = 3;
 --echo #
 connection con46273;
 set debug_sync='alter_table_copy_after_lock_upgrade SIGNAL alter_table_locked WAIT_FOR alter_go';
---send alter table t1 add column e int, rename to t2;
+--send alter table t1 add column e int, rename to t2, lock=shared;
 
 --echo #
 connection default;
@@ -3333,7 +3333,7 @@ connection default;
 --echo # table lock and get blocked on sync point.
 set debug_sync= 'alter_table_copy_after_lock_upgrade SIGNAL parked WAIT_FOR go';
 --echo # Sending:
---send alter table t1 add column j int
+--send alter table t1 add column j int, lock=shared
 
 connection con1;
 --echo # Wait until ALTER TABLE gets blocked on a sync point.
diff --git a/mysql-test/main/mysql57_virtual.result b/mysql-test/main/mysql57_virtual.result
index 16dd7d55719..2b19cd795c0 100644
--- a/mysql-test/main/mysql57_virtual.result
+++ b/mysql-test/main/mysql57_virtual.result
@@ -13,8 +13,8 @@ select * from mysql57_virtual;
 a	b	c
 1	2	4
 2	3	5
-alter online table mysql57_virtual comment "I am now a MariaDB table";
-ERROR 0A000: LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED
+alter online table mysql57_virtual comment "I am now a MariaDB table", algorithm=nocopy;
+ERROR 0A000: ALGORITHM=NOCOPY is not supported for this operation. Try ALGORITHM=COPY
 alter table mysql57_virtual comment "I am now a MariaDB table";
 SHOW CREATE TABLE mysql57_virtual;
 Table	Create Table
diff --git a/mysql-test/main/mysql57_virtual.test b/mysql-test/main/mysql57_virtual.test
index 3ebdd894b79..987a8f1a3cf 100644
--- a/mysql-test/main/mysql57_virtual.test
+++ b/mysql-test/main/mysql57_virtual.test
@@ -13,8 +13,8 @@ insert into mysql57_virtual (a) values (1),(2);
 select * from mysql57_virtual;
 
 # We can't do online changes, as the MariaDB storage is incompatible with MySQL
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-alter online table mysql57_virtual comment "I am now a MariaDB table";
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+alter online table mysql57_virtual comment "I am now a MariaDB table", algorithm=nocopy;
 
 alter table mysql57_virtual comment "I am now a MariaDB table";
 SHOW CREATE TABLE mysql57_virtual;
diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result
index 8d2f1076d68..f161a4ed37d 100644
--- a/mysql-test/main/mysqld--help.result
+++ b/mysql-test/main/mysqld--help.result
@@ -708,7 +708,8 @@ The following specify which files/extra groups are read (specified before remain
  MySQL versions. Any combination of: 
  NO_DUP_KEY_WARNINGS_WITH_IGNORE, NO_PROGRESS_INFO, 
  ZERO_DATE_TIME_CAST, UTF8_IS_UTF8MB3, 
- IGNORE_INDEX_ONLY_FOR_JOIN, COMPAT_5_1_CHECKSUM
+ IGNORE_INDEX_ONLY_FOR_JOIN, COMPAT_5_1_CHECKSUM, 
+ LOCK_ALTER_TABLE_COPY
  --old-passwords     Use old password encryption method (needed for 4.0 and
  older clients)
  --old-style-user-limits 
diff --git a/mysql-test/suite/gcol/inc/gcol_select.inc b/mysql-test/suite/gcol/inc/gcol_select.inc
index 4c030cb5646..8dc76276700 100644
--- a/mysql-test/suite/gcol/inc/gcol_select.inc
+++ b/mysql-test/suite/gcol/inc/gcol_select.inc
@@ -872,7 +872,10 @@ CREATE TABLE t1(a INT);
 INSERT INTO t1 VALUES(2147483647);
 ALTER TABLE t1 ADD COLUMN h INT AS (a) VIRTUAL;
 ALTER TABLE t1 CHANGE h i INT AS (a) VIRTUAL, ALGORITHM=COPY;
+# COPY/NONE is not supported in embedded
+--error ER_WARN_DATA_OUT_OF_RANGE,ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
 ALTER TABLE t1 ADD COLUMN b SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=NONE;
+--error ER_WARN_DATA_OUT_OF_RANGE,ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
 ALTER TABLE t1 ADD COLUMN e SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=NONE;
 ALTER TABLE t1 ADD COLUMN f SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=SHARED;
 ALTER TABLE t1 ADD COLUMN g SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=EXCLUSIVE;
diff --git a/mysql-test/suite/gcol/r/gcol_select_innodb.result b/mysql-test/suite/gcol/r/gcol_select_innodb.result
index b57aceac9e0..8423c3bcdb6 100644
--- a/mysql-test/suite/gcol/r/gcol_select_innodb.result
+++ b/mysql-test/suite/gcol/r/gcol_select_innodb.result
@@ -536,9 +536,9 @@ INSERT INTO t1 VALUES(2147483647);
 ALTER TABLE t1 ADD COLUMN h INT AS (a) VIRTUAL;
 ALTER TABLE t1 CHANGE h i INT AS (a) VIRTUAL, ALGORITHM=COPY;
 ALTER TABLE t1 ADD COLUMN b SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED
+Got one of the listed errors
 ALTER TABLE t1 ADD COLUMN e SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED
+Got one of the listed errors
 ALTER TABLE t1 ADD COLUMN f SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=SHARED;
 ERROR 22003: Out of range value for column 'f' at row 1
 ALTER TABLE t1 ADD COLUMN g SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=EXCLUSIVE;
diff --git a/mysql-test/suite/gcol/r/gcol_select_myisam.result b/mysql-test/suite/gcol/r/gcol_select_myisam.result
index 070808fec4f..df1fbd27cb0 100644
--- a/mysql-test/suite/gcol/r/gcol_select_myisam.result
+++ b/mysql-test/suite/gcol/r/gcol_select_myisam.result
@@ -1155,9 +1155,9 @@ INSERT INTO t1 VALUES(2147483647);
 ALTER TABLE t1 ADD COLUMN h INT AS (a) VIRTUAL;
 ALTER TABLE t1 CHANGE h i INT AS (a) VIRTUAL, ALGORITHM=COPY;
 ALTER TABLE t1 ADD COLUMN b SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED
+Got one of the listed errors
 ALTER TABLE t1 ADD COLUMN e SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: COPY algorithm requires a lock. Try LOCK=SHARED
+Got one of the listed errors
 ALTER TABLE t1 ADD COLUMN f SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=SHARED;
 ERROR 22003: Out of range value for column 'f' at row 1
 ALTER TABLE t1 ADD COLUMN g SMALLINT AS (a) VIRTUAL, ALGORITHM=COPY, LOCK=EXCLUSIVE;
diff --git a/mysql-test/suite/gcol/r/innodb_virtual_basic.result b/mysql-test/suite/gcol/r/innodb_virtual_basic.result
index 5b599dc3520..c5703f598dd 100644
--- a/mysql-test/suite/gcol/r/innodb_virtual_basic.result
+++ b/mysql-test/suite/gcol/r/innodb_virtual_basic.result
@@ -957,12 +957,12 @@ CREATE TABLE t1 (a INT, b INT, c INT GENERATED ALWAYS AS(a+b), h VARCHAR(10));
 INSERT INTO t1 VALUES (11, 3, DEFAULT, 'mm');
 INSERT INTO t1 VALUES (18, 1, DEFAULT, 'mm');
 INSERT INTO t1 VALUES (28, 1, DEFAULT, 'mm');
-ALTER TABLE t1 ADD INDEX idx12 (c) , FORCE, LOCK=NONE;
+ALTER TABLE t1 ADD INDEX idx12 (c), FORCE, ALGORITHM = INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: online rebuild with indexed virtual columns. Try LOCK=SHARED
 ALTER TABLE t1 ADD INDEX idx12 (c), LOCK=NONE;
-ALTER TABLE t1 DROP COLUMN h,  ADD INDEX idx (c) , FORCE, LOCK=NONE;
+ALTER TABLE t1 DROP COLUMN h,  ADD INDEX idx (c), FORCE, ALGORITHM = INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: online rebuild with indexed virtual columns. Try LOCK=SHARED
-ALTER TABLE t1 DROP COLUMN h,  ADD INDEX idx (c), LOCK=NONE;
+ALTER TABLE t1 DROP COLUMN h,  ADD INDEX idx (c), ALGORITHM = INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: online rebuild with indexed virtual columns. Try LOCK=SHARED
 DROP TABLE t1 ;
 CREATE TABLE t1 (a INT, b INT, c INT GENERATED ALWAYS AS(a+b), d INT GENERATED ALWAYS AS(a+b), h VARCHAR(10));
diff --git a/mysql-test/suite/gcol/r/innodb_virtual_debug.result b/mysql-test/suite/gcol/r/innodb_virtual_debug.result
index 3f3e3ea31d1..882fbc6e6a9 100644
--- a/mysql-test/suite/gcol/r/innodb_virtual_debug.result
+++ b/mysql-test/suite/gcol/r/innodb_virtual_debug.result
@@ -53,7 +53,7 @@ a	b	c	h
 18	1	19	mm
 28	1	29	mm
 NULL	NULL	NULL	mx
-ALTER TABLE t FORCE, LOCK=NONE;
+ALTER TABLE t FORCE, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: online rebuild with indexed virtual columns. Try LOCK=SHARED
 disconnect con1;
 DROP TABLE t;
diff --git a/mysql-test/suite/gcol/r/innodb_virtual_rebuild.result b/mysql-test/suite/gcol/r/innodb_virtual_rebuild.result
index 35f37034721..028ae02360c 100644
--- a/mysql-test/suite/gcol/r/innodb_virtual_rebuild.result
+++ b/mysql-test/suite/gcol/r/innodb_virtual_rebuild.result
@@ -6,30 +6,30 @@ CREATE TABLE t4 (i INT, v INT AS (i) VIRTUAL) ENGINE=InnoDB
 ROW_FORMAT=REDUNDANT;
 INSERT INTO t4 SET i=1;
 ALTER TABLE t4 ADD INDEX(v), LOCK=NONE;
-ALTER TABLE t4 ADD COLUMN k INT, LOCK=NONE;
+ALTER TABLE t4 ADD COLUMN k INT, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: online rebuild with indexed virtual columns. Try LOCK=SHARED
-ALTER TABLE t4 DROP k, LOCK=NONE;
+ALTER TABLE t4 DROP k, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 42000: Can't DROP COLUMN `k`; check that it exists
 ALTER TABLE t4 DROP INDEX v, LOCK=NONE;
 INSERT INTO t3 SET i=1;
 ALTER TABLE t3 ADD INDEX(v), LOCK=NONE;
-ALTER TABLE t3 ADD COLUMN k INT, LOCK=NONE;
+ALTER TABLE t3 ADD COLUMN k INT, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: online rebuild with indexed virtual columns. Try LOCK=SHARED
-ALTER TABLE t3 DROP k, LOCK=NONE;
+ALTER TABLE t3 DROP k, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 42000: Can't DROP COLUMN `k`; check that it exists
 ALTER TABLE t3 DROP INDEX v, LOCK=NONE;
 INSERT INTO t2 SET i=1;
 ALTER TABLE t2 ADD INDEX(v), LOCK=NONE;
-ALTER TABLE t2 ADD COLUMN k INT, LOCK=NONE;
+ALTER TABLE t2 ADD COLUMN k INT, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: online rebuild with indexed virtual columns. Try LOCK=SHARED
-ALTER TABLE t2 DROP k, LOCK=NONE;
+ALTER TABLE t2 DROP k, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 42000: Can't DROP COLUMN `k`; check that it exists
 ALTER TABLE t2 DROP INDEX v, LOCK=NONE;
 INSERT INTO t1 SET i=1;
 ALTER TABLE t1 ADD INDEX(v), LOCK=NONE;
-ALTER TABLE t1 ADD COLUMN k INT, LOCK=NONE;
+ALTER TABLE t1 ADD COLUMN k INT, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: online rebuild with indexed virtual columns. Try LOCK=SHARED
-ALTER TABLE t1 DROP k, LOCK=NONE;
+ALTER TABLE t1 DROP k, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 42000: Can't DROP COLUMN `k`; check that it exists
 ALTER TABLE t1 DROP INDEX v, LOCK=NONE;
 connect  ddl,localhost,root,,test;
diff --git a/mysql-test/suite/gcol/t/innodb_virtual_basic.test b/mysql-test/suite/gcol/t/innodb_virtual_basic.test
index 87251ad7d52..b3a7aa614dc 100644
--- a/mysql-test/suite/gcol/t/innodb_virtual_basic.test
+++ b/mysql-test/suite/gcol/t/innodb_virtual_basic.test
@@ -886,12 +886,12 @@ INSERT INTO t1 VALUES (18, 1, DEFAULT, 'mm');
 INSERT INTO t1 VALUES (28, 1, DEFAULT, 'mm');
 
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE t1 ADD INDEX idx12 (c) , FORCE, LOCK=NONE;
+ALTER TABLE t1 ADD INDEX idx12 (c), FORCE, ALGORITHM = INPLACE, LOCK=NONE;
 ALTER TABLE t1 ADD INDEX idx12 (c), LOCK=NONE;
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE t1 DROP COLUMN h,  ADD INDEX idx (c) , FORCE, LOCK=NONE;
+ALTER TABLE t1 DROP COLUMN h,  ADD INDEX idx (c), FORCE, ALGORITHM = INPLACE, LOCK=NONE;
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE t1 DROP COLUMN h,  ADD INDEX idx (c), LOCK=NONE;
+ALTER TABLE t1 DROP COLUMN h,  ADD INDEX idx (c), ALGORITHM = INPLACE, LOCK=NONE;
 
 DROP TABLE t1 ;
 
diff --git a/mysql-test/suite/gcol/t/innodb_virtual_debug.test b/mysql-test/suite/gcol/t/innodb_virtual_debug.test
index cd2b860400c..c1bd76342e5 100644
--- a/mysql-test/suite/gcol/t/innodb_virtual_debug.test
+++ b/mysql-test/suite/gcol/t/innodb_virtual_debug.test
@@ -54,7 +54,7 @@ SHOW CREATE TABLE t;
 SELECT * FROM t;
 
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE t FORCE, LOCK=NONE;
+ALTER TABLE t FORCE, ALGORITHM=INPLACE, LOCK=NONE;
 if (0) {# MDEV-14341 TODO: re-enable this
 SET DEBUG_SYNC = 'innodb_inplace_alter_table_enter SIGNAL start_create WAIT_FOR go_ahead';
 --send ALTER TABLE t FORCE
diff --git a/mysql-test/suite/gcol/t/innodb_virtual_rebuild.test b/mysql-test/suite/gcol/t/innodb_virtual_rebuild.test
index fe4f5e307b3..85707ca01f1 100644
--- a/mysql-test/suite/gcol/t/innodb_virtual_rebuild.test
+++ b/mysql-test/suite/gcol/t/innodb_virtual_rebuild.test
@@ -16,9 +16,9 @@ eval INSERT INTO t$n SET i=1;
 eval ALTER TABLE t$n ADD INDEX(v), LOCK=NONE;
 # MDEV-17468 FIXME: Fix this, and remove the 2 --error below.
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-eval ALTER TABLE t$n ADD COLUMN k INT, LOCK=NONE;
+eval ALTER TABLE t$n ADD COLUMN k INT, ALGORITHM=INPLACE, LOCK=NONE;
 --error ER_CANT_DROP_FIELD_OR_KEY
-eval ALTER TABLE t$n DROP k, LOCK=NONE;
+eval ALTER TABLE t$n DROP k, ALGORITHM=INPLACE, LOCK=NONE;
 eval ALTER TABLE t$n DROP INDEX v, LOCK=NONE;
 dec $n;
 }
diff --git a/mysql-test/suite/heap/heap.result b/mysql-test/suite/heap/heap.result
index bef3913dcb1..24c6aadb1bb 100644
--- a/mysql-test/suite/heap/heap.result
+++ b/mysql-test/suite/heap/heap.result
@@ -398,9 +398,10 @@ qq
 *a       *a*a       *
 *a        *a*a        *
 *a         *a*a         *
+flush tables;
 explain select * from t1 where v='a';
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	SIMPLE	t1	ref	v	v	13	const	10	Using where
+1	SIMPLE	t1	ref	v	v	13	const	9	Using where
 select v,count(*) from t1 group by v limit 10;
 v	count(*)
 a	1
diff --git a/mysql-test/suite/heap/heap.test b/mysql-test/suite/heap/heap.test
index ef950da5484..7d8b91b5984 100644
--- a/mysql-test/suite/heap/heap.test
+++ b/mysql-test/suite/heap/heap.test
@@ -277,6 +277,7 @@ explain select count(*) from t1 where v between 'a' and 'a ' and v between 'a  '
 --error ER_DUP_ENTRY
 alter table t1 add unique(v);
 select concat('*',v,'*',c,'*',t,'*') as qq from t1 where v='a' order by length(concat('*',v,'*',c,'*',t,'*'));
+flush tables;
 explain select * from t1 where v='a';
 
 # GROUP BY
diff --git a/mysql-test/suite/innodb/r/alter_algorithm,INPLACE.rdiff b/mysql-test/suite/innodb/r/alter_algorithm,INPLACE.rdiff
index 79b3d8854fa..6c0b1badcf8 100644
--- a/mysql-test/suite/innodb/r/alter_algorithm,INPLACE.rdiff
+++ b/mysql-test/suite/innodb/r/alter_algorithm,INPLACE.rdiff
@@ -1,6 +1,6 @@
 --- alter_algorithm.result
 +++ alter_algorithm.reject
-@@ -7,43 +7,43 @@
+@@ -7,40 +7,40 @@
  INSERT INTO t1(f1, f2, f3) VALUES(1, 1, 1);
  SELECT @@alter_algorithm;
  @@alter_algorithm
@@ -56,13 +56,8 @@
 +affected rows: 0
 +info: Records: 0  Duplicates: 0  Warnings: 0
  ALTER TABLE t1 FORCE, ALGORITHM=DEFAULT;
--affected rows: 1
--info: Records: 1  Duplicates: 0  Warnings: 0
-+affected rows: 0
-+info: Records: 0  Duplicates: 0  Warnings: 0
- DROP TABLE t1;
  affected rows: 0
- CREATE TABLE t1(f1 INT PRIMARY KEY, f2 INT NOT NULL,
+ info: Records: 0  Duplicates: 0  Warnings: 0
 @@ -56,22 +56,22 @@
  FOREIGN KEY fidx(f1) REFERENCES t1(f1))ENGINE=INNODB;
  INSERT INTO t1(f1, f2, f4, f5) VALUES(1, 2, 3, 4);
diff --git a/mysql-test/suite/innodb/r/alter_algorithm,INSTANT.rdiff b/mysql-test/suite/innodb/r/alter_algorithm,INSTANT.rdiff
index fbcb5ca8704..6a09338dea2 100644
--- a/mysql-test/suite/innodb/r/alter_algorithm,INSTANT.rdiff
+++ b/mysql-test/suite/innodb/r/alter_algorithm,INSTANT.rdiff
@@ -1,6 +1,6 @@
 --- alter_algorithm.result
 +++ alter_algorithm.reject
-@@ -7,43 +7,35 @@
+@@ -7,40 +7,32 @@
  INSERT INTO t1(f1, f2, f3) VALUES(1, 1, 1);
  SELECT @@alter_algorithm;
  @@alter_algorithm
@@ -48,13 +48,8 @@
 -info: Records: 1  Duplicates: 0  Warnings: 0
 +Got one of the listed errors
  ALTER TABLE t1 FORCE, ALGORITHM=DEFAULT;
--affected rows: 1
--info: Records: 1  Duplicates: 0  Warnings: 0
-+affected rows: 0
-+info: Records: 0  Duplicates: 0  Warnings: 0
- DROP TABLE t1;
  affected rows: 0
- CREATE TABLE t1(f1 INT PRIMARY KEY, f2 INT NOT NULL,
+ info: Records: 0  Duplicates: 0  Warnings: 0
 @@ -56,22 +48,17 @@
  FOREIGN KEY fidx(f1) REFERENCES t1(f1))ENGINE=INNODB;
  INSERT INTO t1(f1, f2, f4, f5) VALUES(1, 2, 3, 4);
diff --git a/mysql-test/suite/innodb/r/alter_algorithm,NOCOPY.rdiff b/mysql-test/suite/innodb/r/alter_algorithm,NOCOPY.rdiff
index 44e9f63a5f4..317958638f1 100644
--- a/mysql-test/suite/innodb/r/alter_algorithm,NOCOPY.rdiff
+++ b/mysql-test/suite/innodb/r/alter_algorithm,NOCOPY.rdiff
@@ -1,6 +1,6 @@
 --- alter_algorithm.result
 +++ alter_algorithm.reject
-@@ -7,43 +7,35 @@
+@@ -7,40 +7,32 @@
  INSERT INTO t1(f1, f2, f3) VALUES(1, 1, 1);
  SELECT @@alter_algorithm;
  @@alter_algorithm
@@ -48,13 +48,8 @@
 -info: Records: 1  Duplicates: 0  Warnings: 0
 +Got one of the listed errors
  ALTER TABLE t1 FORCE, ALGORITHM=DEFAULT;
--affected rows: 1
--info: Records: 1  Duplicates: 0  Warnings: 0
-+affected rows: 0
-+info: Records: 0  Duplicates: 0  Warnings: 0
- DROP TABLE t1;
  affected rows: 0
- CREATE TABLE t1(f1 INT PRIMARY KEY, f2 INT NOT NULL,
+ info: Records: 0  Duplicates: 0  Warnings: 0
 @@ -56,22 +48,22 @@
  FOREIGN KEY fidx(f1) REFERENCES t1(f1))ENGINE=INNODB;
  INSERT INTO t1(f1, f2, f4, f5) VALUES(1, 2, 3, 4);
diff --git a/mysql-test/suite/innodb/r/alter_algorithm.result b/mysql-test/suite/innodb/r/alter_algorithm.result
index 717d31de6a0..8d85aeb1921 100644
--- a/mysql-test/suite/innodb/r/alter_algorithm.result
+++ b/mysql-test/suite/innodb/r/alter_algorithm.result
@@ -42,8 +42,8 @@ ALTER TABLE t1 ENGINE=INNODB;
 affected rows: 1
 info: Records: 1  Duplicates: 0  Warnings: 0
 ALTER TABLE t1 FORCE, ALGORITHM=DEFAULT;
-affected rows: 1
-info: Records: 1  Duplicates: 0  Warnings: 0
+affected rows: 0
+info: Records: 0  Duplicates: 0  Warnings: 0
 DROP TABLE t1;
 affected rows: 0
 CREATE TABLE t1(f1 INT PRIMARY KEY, f2 INT NOT NULL,
diff --git a/mysql-test/suite/innodb/r/autoinc_persist.result b/mysql-test/suite/innodb/r/autoinc_persist.result
index 9e5cd4ad23e..f3e466a8563 100644
--- a/mysql-test/suite/innodb/r/autoinc_persist.result
+++ b/mysql-test/suite/innodb/r/autoinc_persist.result
@@ -626,7 +626,7 @@ CREATE TABLE mdev6076a (b INT) ENGINE=InnoDB;
 INSERT INTO mdev6076a VALUES(2),(1);
 CREATE TABLE mdev6076b (b INT) ENGINE=InnoDB;
 INSERT INTO mdev6076b VALUES(2),(1);
-ALTER TABLE mdev6076a ADD COLUMN a SERIAL FIRST, LOCK=NONE;
+ALTER TABLE mdev6076a ADD COLUMN a SERIAL FIRST, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: Adding an auto-increment column requires a lock. Try LOCK=SHARED
 ALTER TABLE mdev6076a ADD COLUMN a SERIAL FIRST, ALGORITHM=INPLACE;
 ALTER TABLE mdev6076b ADD COLUMN a SERIAL FIRST, AUTO_INCREMENT=100,
diff --git a/mysql-test/suite/innodb/r/innodb-alter-autoinc.result b/mysql-test/suite/innodb/r/innodb-alter-autoinc.result
index 3186b00aa38..4aea9d14aa5 100644
--- a/mysql-test/suite/innodb/r/innodb-alter-autoinc.result
+++ b/mysql-test/suite/innodb/r/innodb-alter-autoinc.result
@@ -5,7 +5,7 @@ SET @@sql_mode = 'STRICT_TRANS_TABLES';
 ALTER TABLE t1 ADD PRIMARY KEY(a);
 SET @@sql_mode = @old_sql_mode;
 ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY,
-LOCK=NONE;
+ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: Adding an auto-increment column requires a lock. Try LOCK=SHARED
 ALTER TABLE t1 ADD id INT AUTO_INCREMENT;
 ERROR 42000: Incorrect table definition; there can be only one auto column and it must be defined as a key
@@ -50,7 +50,7 @@ t1	CREATE TABLE `t1` (
   KEY `id` (`id`,`a`)
 ) ENGINE=InnoDB AUTO_INCREMENT=75 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
 ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY,
-DROP COLUMN id, AUTO_INCREMENT = 42, LOCK=NONE;
+DROP COLUMN id, AUTO_INCREMENT = 42, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: Adding an auto-increment column requires a lock. Try LOCK=SHARED
 ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY,
 DROP COLUMN id, AUTO_INCREMENT = 42, ALGORITHM=INPLACE;
diff --git a/mysql-test/suite/innodb/r/innodb-alter-timestamp.result b/mysql-test/suite/innodb/r/innodb-alter-timestamp.result
index 1a68c54095a..fc26244df74 100644
--- a/mysql-test/suite/innodb/r/innodb-alter-timestamp.result
+++ b/mysql-test/suite/innodb/r/innodb-alter-timestamp.result
@@ -86,8 +86,8 @@ ALTER TABLE t1 ADD COLUMN d2 TIMESTAMP DEFAULT '2017-05-08 16:23:45',
 LOCK=NONE;
 affected rows: 0
 info: Records: 0  Duplicates: 0  Warnings: 0
-ALTER TABLE t1 ADD COLUMN d3 TIMESTAMP DEFAULT d1, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported for this operation. Try LOCK=SHARED
+ALTER TABLE t1 ADD COLUMN d3 TIMESTAMP DEFAULT d1, ALGORITHM=INPLACE, LOCK=NONE;
+ERROR 0A000: ALGORITHM=INPLACE is not supported for this operation. Try ALGORITHM=COPY
 ALTER TABLE t1 ADD COLUMN d3 TIMESTAMP DEFAULT d1, ALGORITHM=INPLACE;
 ERROR 0A000: ALGORITHM=INPLACE is not supported for this operation. Try ALGORITHM=COPY
 ALTER TABLE t1 ADD COLUMN d3 TIMESTAMP DEFAULT d1;
diff --git a/mysql-test/suite/innodb/r/innodb-alter.result b/mysql-test/suite/innodb/r/innodb-alter.result
index 16c33ee02a2..949bb9c8f24 100644
--- a/mysql-test/suite/innodb/r/innodb-alter.result
+++ b/mysql-test/suite/innodb/r/innodb-alter.result
@@ -438,28 +438,28 @@ tt	CREATE TABLE `tt` (
   PRIMARY KEY (`pk`),
   FULLTEXT KEY `ct` (`ct`)
 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
-ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
-ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
+ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL FIRST, ALGORITHM=INSTANT;
+ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=INPLACE
+ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL, ALGORITHM=INSTANT;
+ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=INPLACE
 CREATE TABLE tu (
 pk INT PRIMARY KEY, FTS_DOC_ID BIGINT UNSIGNED NOT NULL, t TEXT,
 FULLTEXT INDEX(t)
 ) ENGINE=InnoDB;
-ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
-ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
+ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL FIRST, ALGORITHM=INSTANT;
+ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=INPLACE
+ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL, ALGORITHM=INSTANT;
+ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=INPLACE
 DROP TABLE tu;
 CREATE TABLE tv (
 pk INT PRIMARY KEY, FTS_DOC_ID BIGINT UNSIGNED NOT NULL, t TEXT,
 UNIQUE INDEX FTS_DOC_ID_INDEX(FTS_DOC_ID),
 FULLTEXT INDEX(t)
 ) ENGINE=InnoDB;
-ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
-ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
+ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL FIRST, ALGORITHM=INSTANT;
+ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=INPLACE
+ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL, ALGORITHM=INSTANT;
+ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=INPLACE
 DROP TABLE tv;
 ALTER TABLE t1o CHANGE c1 dB_row_Id INT, ALGORITHM=COPY;
 ERROR 42000: Incorrect column name 'dB_row_Id'
diff --git a/mysql-test/suite/innodb/r/innodb-index.result b/mysql-test/suite/innodb/r/innodb-index.result
index 1ebed058aaf..8346b61dfed 100644
--- a/mysql-test/suite/innodb/r/innodb-index.result
+++ b/mysql-test/suite/innodb/r/innodb-index.result
@@ -1598,8 +1598,8 @@ create table t1(f1 int not null, f2 int not null,
 primary key (f1), unique key(f1, f2))engine=innodb;
 insert into t1 values(1,3), (2,2);
 alter table t1 drop primary key, lock=none;
-alter table t1 drop index f1, lock=none;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Dropping a primary key is not allowed without also adding a new primary key. Try LOCK=SHARED
+alter table t1 drop index f1, algorithm=inplace, lock=none;
+ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Dropping a primary key is not allowed without also adding a new primary key. Try ALGORITHM=COPY
 drop table t1;
 #
 #  BUG#21612714 ALTER TABLE SORTING SKIPPED WHEN CHANGE PK AND DROP
diff --git a/mysql-test/suite/innodb/r/innodb-online-alter-gis.result b/mysql-test/suite/innodb/r/innodb-online-alter-gis.result
index 6a57be49ac5..59859303d57 100644
--- a/mysql-test/suite/innodb/r/innodb-online-alter-gis.result
+++ b/mysql-test/suite/innodb/r/innodb-online-alter-gis.result
@@ -23,7 +23,7 @@ t1	CREATE TABLE `t1` (
   SPATIAL KEY `c` (`b`),
   KEY `d` (`d`)
 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
-ALTER ONLINE TABLE t1 ADD PRIMARY KEY(a),DROP INDEX d;
+ALTER ONLINE TABLE t1 ADD PRIMARY KEY(a),DROP INDEX d, ALGORITHM=INPLACE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: Do not support online operation on table with GIS index. Try LOCK=SHARED
 show warnings;
 Level	Code	Message
diff --git a/mysql-test/suite/innodb/r/innodb-wl5980-alter.result b/mysql-test/suite/innodb/r/innodb-wl5980-alter.result
index 84f3d7811c2..5f7291183dc 100644
--- a/mysql-test/suite/innodb/r/innodb-wl5980-alter.result
+++ b/mysql-test/suite/innodb/r/innodb-wl5980-alter.result
@@ -1383,8 +1383,8 @@ t1c.ibd
 t1p.ibd
 tt.ibd
 ALTER TABLE t1o CHANGE FTS_DOC_ID foo_id BIGINT UNSIGNED NOT NULL,
-LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Cannot drop or rename FTS_DOC_ID. Try LOCK=SHARED
+ALGORITHM=INPLACE, LOCK=NONE;
+ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Cannot drop or rename FTS_DOC_ID. Try ALGORITHM=COPY
 SELECT sc.pos FROM information_schema.innodb_sys_columns sc
 INNER JOIN information_schema.innodb_sys_tables st
 ON sc.TABLE_ID=st.TABLE_ID
diff --git a/mysql-test/suite/innodb/t/autoinc_persist.test b/mysql-test/suite/innodb/t/autoinc_persist.test
index ac933cc7b13..29e8c7640f3 100644
--- a/mysql-test/suite/innodb/t/autoinc_persist.test
+++ b/mysql-test/suite/innodb/t/autoinc_persist.test
@@ -393,7 +393,7 @@ INSERT INTO mdev6076a VALUES(2),(1);
 CREATE TABLE mdev6076b (b INT) ENGINE=InnoDB;
 INSERT INTO mdev6076b VALUES(2),(1);
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE mdev6076a ADD COLUMN a SERIAL FIRST, LOCK=NONE;
+ALTER TABLE mdev6076a ADD COLUMN a SERIAL FIRST, ALGORITHM=INPLACE, LOCK=NONE;
 ALTER TABLE mdev6076a ADD COLUMN a SERIAL FIRST, ALGORITHM=INPLACE;
 ALTER TABLE mdev6076b ADD COLUMN a SERIAL FIRST, AUTO_INCREMENT=100,
 ALGORITHM=INPLACE;
diff --git a/mysql-test/suite/innodb/t/innodb-alter-autoinc.test b/mysql-test/suite/innodb/t/innodb-alter-autoinc.test
index e01c44aa26e..49b26691f08 100644
--- a/mysql-test/suite/innodb/t/innodb-alter-autoinc.test
+++ b/mysql-test/suite/innodb/t/innodb-alter-autoinc.test
@@ -11,7 +11,7 @@ SET @@sql_mode = @old_sql_mode;
 # We cannot assign AUTO_INCREMENT values during online index creation.
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
 ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY,
-	LOCK=NONE;
+	ALGORITHM=INPLACE, LOCK=NONE;
 
 --error ER_WRONG_AUTO_KEY
 ALTER TABLE t1 ADD id INT AUTO_INCREMENT;
@@ -42,7 +42,7 @@ SHOW CREATE TABLE t1;
 # We cannot assign AUTO_INCREMENT values during online index creation.
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
 ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY,
-	DROP COLUMN id, AUTO_INCREMENT = 42, LOCK=NONE;
+	DROP COLUMN id, AUTO_INCREMENT = 42, ALGORITHM=INPLACE, LOCK=NONE;
 
 ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY,
 	DROP COLUMN id, AUTO_INCREMENT = 42, ALGORITHM=INPLACE;
diff --git a/mysql-test/suite/innodb/t/innodb-alter-timestamp.test b/mysql-test/suite/innodb/t/innodb-alter-timestamp.test
index 95df8d6b649..eeeeaa55410 100644
--- a/mysql-test/suite/innodb/t/innodb-alter-timestamp.test
+++ b/mysql-test/suite/innodb/t/innodb-alter-timestamp.test
@@ -67,7 +67,7 @@ SELECT u1, COUNT(DISTINCT d1) FROM t1 GROUP BY u1;
 ALTER TABLE t1 ADD COLUMN d2 TIMESTAMP DEFAULT '2017-05-08 16:23:45',
 LOCK=NONE;
 --error ER_ALTER_OPERATION_NOT_SUPPORTED
-ALTER TABLE t1 ADD COLUMN d3 TIMESTAMP DEFAULT d1, LOCK=NONE;
+ALTER TABLE t1 ADD COLUMN d3 TIMESTAMP DEFAULT d1, ALGORITHM=INPLACE, LOCK=NONE;
 --error ER_ALTER_OPERATION_NOT_SUPPORTED
 ALTER TABLE t1 ADD COLUMN d3 TIMESTAMP DEFAULT d1, ALGORITHM=INPLACE;
 ALTER TABLE t1 ADD COLUMN d3 TIMESTAMP DEFAULT d1;
diff --git a/mysql-test/suite/innodb/t/innodb-alter.test b/mysql-test/suite/innodb/t/innodb-alter.test
index 78550763a9f..c6a76b66b3e 100644
--- a/mysql-test/suite/innodb/t/innodb-alter.test
+++ b/mysql-test/suite/innodb/t/innodb-alter.test
@@ -195,20 +195,20 @@ ALGORITHM=INPLACE, LOCK=SHARED;
 
 SHOW CREATE TABLE tt;
 # Non-instant ADD COLUMN would require the table to be rebuilt.
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL FIRST, ALGORITHM=INSTANT;
 # This is still non-instant ADD COLUMN, because FTS_DOC_ID is hidden.
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL, ALGORITHM=INSTANT;
 
 CREATE TABLE tu (
   pk INT PRIMARY KEY, FTS_DOC_ID BIGINT UNSIGNED NOT NULL, t TEXT,
   FULLTEXT INDEX(t)
 ) ENGINE=InnoDB;
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE;
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL FIRST, ALGORITHM=INSTANT;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL, ALGORITHM=INSTANT;
 DROP TABLE tu;
 
 CREATE TABLE tv (
@@ -216,10 +216,10 @@ CREATE TABLE tv (
   UNIQUE INDEX FTS_DOC_ID_INDEX(FTS_DOC_ID),
   FULLTEXT INDEX(t)
 ) ENGINE=InnoDB;
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE;
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL FIRST, ALGORITHM=INSTANT;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED
+ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL, ALGORITHM=INSTANT;
 DROP TABLE tv;
 
 # DB_ROW_ID, DB_TRX_ID, DB_ROLL_PTR are reserved InnoDB system column names.
diff --git a/mysql-test/suite/innodb/t/innodb-index.test b/mysql-test/suite/innodb/t/innodb-index.test
index c80e3e6c066..b434ef46627 100644
--- a/mysql-test/suite/innodb/t/innodb-index.test
+++ b/mysql-test/suite/innodb/t/innodb-index.test
@@ -921,7 +921,7 @@ create table t1(f1 int not null, f2 int not null,
 insert into t1 values(1,3), (2,2);
 alter table t1 drop primary key, lock=none;
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-alter table t1 drop index f1, lock=none;
+alter table t1 drop index f1, algorithm=inplace, lock=none;
 drop table t1;
 
 --echo #
diff --git a/mysql-test/suite/innodb/t/innodb-online-alter-gis.test b/mysql-test/suite/innodb/t/innodb-online-alter-gis.test
index df0ab2e8184..26311793992 100644
--- a/mysql-test/suite/innodb/t/innodb-online-alter-gis.test
+++ b/mysql-test/suite/innodb/t/innodb-online-alter-gis.test
@@ -11,8 +11,8 @@ show errors;
 drop table t1;
 create table t1(a int not null, b geometry not null, d int,spatial key c(b), key d(d)) engine=innodb;
 show create table t1;
---error 1846
-ALTER ONLINE TABLE t1 ADD PRIMARY KEY(a),DROP INDEX d;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+ALTER ONLINE TABLE t1 ADD PRIMARY KEY(a),DROP INDEX d, ALGORITHM=INPLACE;
 show warnings;
 show errors;
 ALTER ONLINE TABLE t1 ADD PRIMARY KEY(a),DROP INDEX d, LOCK=SHARED;
diff --git a/mysql-test/suite/innodb/t/innodb-wl5980-alter.test b/mysql-test/suite/innodb/t/innodb-wl5980-alter.test
index 6627b77ed9c..ec0000579e6 100644
--- a/mysql-test/suite/innodb/t/innodb-wl5980-alter.test
+++ b/mysql-test/suite/innodb/t/innodb-wl5980-alter.test
@@ -609,7 +609,7 @@ ALGORITHM=INPLACE;
 # This would create a hidden FTS_DOC_ID column, which cannot be done online.
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
 ALTER TABLE t1o CHANGE FTS_DOC_ID foo_id BIGINT UNSIGNED NOT NULL,
-LOCK=NONE;
+ALGORITHM=INPLACE, LOCK=NONE;
 
 # This should not show duplicates.
 SELECT sc.pos FROM information_schema.innodb_sys_columns sc
diff --git a/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result b/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result
index d831477c2af..18be4423398 100644
--- a/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result
+++ b/mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result
@@ -75,10 +75,10 @@ INSERT INTO fts_test (title,body) VALUES
 ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
 ('MySQL vs. YourSQL','In the following database comparison ...'),
 ('MySQL Security','When configured properly, MySQL ...');
-CREATE FULLTEXT INDEX idx on fts_test (title, body) LOCK=NONE;
+ALTER TABLE fts_test ADD FULLTEXT `idx` (title, body), ALGORITHM=NOCOPY, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
 ALTER TABLE fts_test ADD FULLTEXT `idx` (title, body), ALGORITHM=NOCOPY;
-ALTER TABLE fts_test ROW_FORMAT=REDUNDANT, LOCK=NONE;
+ALTER TABLE fts_test ROW_FORMAT=REDUNDANT, ALGORITHM=INPLACE, LOCK=NONE;
 ERROR 0A000: LOCK=NONE is not supported. Reason: Fulltext index creation requires a lock. Try LOCK=SHARED
 ALTER TABLE fts_test ROW_FORMAT=REDUNDANT;
 SELECT * FROM fts_test WHERE MATCH (title, body)
diff --git a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test
index 31f10399bc2..ae85b726801 100644
--- a/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test
+++ b/mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test
@@ -111,11 +111,11 @@ INSERT INTO fts_test (title,body) VALUES
 # We could support online fulltext index creation when a FTS_DOC_ID
 # column already exists. This has not been implemented yet.
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-CREATE FULLTEXT INDEX idx on fts_test (title, body) LOCK=NONE;
+ALTER TABLE fts_test ADD FULLTEXT `idx` (title, body), ALGORITHM=NOCOPY, LOCK=NONE;
 ALTER TABLE fts_test ADD FULLTEXT `idx` (title, body), ALGORITHM=NOCOPY;
 
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE fts_test ROW_FORMAT=REDUNDANT, LOCK=NONE;
+ALTER TABLE fts_test ROW_FORMAT=REDUNDANT, ALGORITHM=INPLACE, LOCK=NONE;
 ALTER TABLE fts_test ROW_FORMAT=REDUNDANT;
 
 SELECT * FROM fts_test WHERE MATCH (title, body)
diff --git a/mysql-test/suite/innodb_gis/r/alter_spatial_index.result b/mysql-test/suite/innodb_gis/r/alter_spatial_index.result
index 5365e7d0f8c..100a5e94836 100644
--- a/mysql-test/suite/innodb_gis/r/alter_spatial_index.result
+++ b/mysql-test/suite/innodb_gis/r/alter_spatial_index.result
@@ -759,17 +759,6 @@ ERROR 22003: Cannot get geometry object from data you send to the GEOMETRY field
 alter table t1 add b geometry not null default st_geomfromtext('POINT(0 0)'),
 add spatial index(b), algorithm=copy;
 DROP table t1;
-#
-# BUG#20111575 ALTER TABLE...ADD SPATIAL INDEX...LOCK NONE IS REFUSED
-# WITHOUT STATING A REASON
-#
-CREATE TABLE t1(p point NOT NULL) ENGINE=innodb;
-ALTER TABLE t1 ADD SPATIAL INDEX(p), LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Do not support online operation on table with GIS index. Try LOCK=SHARED
-ALTER TABLE t1 ADD SPATIAL INDEX(p);
-ALTER TABLE t1 FORCE, LOCK=NONE;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Do not support online operation on table with GIS index. Try LOCK=SHARED
-DROP TABLE t1;
 create table t1 (p point not null default if(unix_timestamp()>10,POINT(1,1),LineString(Point(0,0),Point(1,1)))) ENGINE=innodb;
 set timestamp=10;
 insert into t1 values(default);
diff --git a/mysql-test/suite/innodb_gis/t/alter_spatial_index.test b/mysql-test/suite/innodb_gis/t/alter_spatial_index.test
index 6f30b38b6d4..50364b97a8a 100644
--- a/mysql-test/suite/innodb_gis/t/alter_spatial_index.test
+++ b/mysql-test/suite/innodb_gis/t/alter_spatial_index.test
@@ -747,18 +747,6 @@ alter table t1 add b geometry not null default st_geomfromtext('POINT(0 0)'),
 add spatial index(b), algorithm=copy;
 DROP table t1;
 
---echo #
---echo # BUG#20111575 ALTER TABLE...ADD SPATIAL INDEX...LOCK NONE IS REFUSED
---echo # WITHOUT STATING A REASON
---echo #
-CREATE TABLE t1(p point NOT NULL) ENGINE=innodb;
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE t1 ADD SPATIAL INDEX(p), LOCK=NONE;
-ALTER TABLE t1 ADD SPATIAL INDEX(p);
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
-ALTER TABLE t1 FORCE, LOCK=NONE;
-DROP TABLE t1;
-
 create table t1 (p point not null default if(unix_timestamp()>10,POINT(1,1),LineString(Point(0,0),Point(1,1)))) ENGINE=innodb;
 set timestamp=10;
 --error ER_TRUNCATED_WRONG_VALUE_FOR_FIELD
diff --git a/mysql-test/suite/perfschema/r/alter_table_progress.result b/mysql-test/suite/perfschema/r/alter_table_progress.result
index 31cc60927f6..d97377d3721 100644
--- a/mysql-test/suite/perfschema/r/alter_table_progress.result
+++ b/mysql-test/suite/perfschema/r/alter_table_progress.result
@@ -75,15 +75,15 @@ stage/sql/table lock	NULL	NULL
 stage/sql/After create	NULL	NULL
 stage/sql/copy to tmp table	5	5
 stage/sql/Enabling keys	NULL	NULL
-stage/sql/Rename result table	NULL	NULL
 stage/sql/Unlocking tables	NULL	NULL
+stage/sql/Enabling keys	NULL	NULL
+stage/sql/Apply log event	NULL	NULL
+stage/sql/After apply log event	NULL	NULL
 stage/sql/Rename result table	NULL	NULL
 stage/sql/End of update loop	NULL	NULL
 stage/sql/Query end	NULL	NULL
 stage/sql/Commit	NULL	NULL
 stage/sql/closing tables	NULL	NULL
-stage/sql/Unlocking tables	NULL	NULL
-stage/sql/closing tables	NULL	NULL
 stage/sql/Commit implicit	NULL	NULL
 stage/sql/Starting cleanup	NULL	NULL
 stage/sql/Freeing items	NULL	NULL
diff --git a/mysql-test/suite/rpl/include/rpl_extra_col_slave.test b/mysql-test/suite/rpl/include/rpl_extra_col_slave.test
index 680d5724bea..028c833a6e5 100644
--- a/mysql-test/suite/rpl/include/rpl_extra_col_slave.test
+++ b/mysql-test/suite/rpl/include/rpl_extra_col_slave.test
@@ -397,8 +397,7 @@ sync_slave_with_master;
   STOP SLAVE;
   --source include/reset_slave.inc
   eval CREATE TABLE t9 (a INT KEY, b BLOB, c CHAR(5),
-                        d TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 
-                          ON UPDATE CURRENT_TIMESTAMP,
+                        d TIMESTAMP NOT NULL DEFAULT 0,
                         e INT NOT NULL,
                         f text not null,
                         g text,
diff --git a/mysql-test/suite/rpl/include/rpl_row_blob.test b/mysql-test/suite/rpl/include/rpl_row_blob.test
index 5cd7b6b4b29..408a23519e6 100644
--- a/mysql-test/suite/rpl/include/rpl_row_blob.test
+++ b/mysql-test/suite/rpl/include/rpl_row_blob.test
@@ -173,3 +173,21 @@ DROP TABLE IF EXISTS test.t1;
 DROP TABLE IF EXISTS test.t2;
 # ensure cleanup on slave as well:
 --sync_slave_with_master
+
+--echo #
+--echo # MDEV-30985 Replica stops with error on ALTER ONLINE with Geometry Types
+--echo #
+--connection master
+CREATE TABLE t(txt TEXT DEFAULT '111111111', blb LONGBLOB AS (txt));
+INSERT INTO t () VALUES (),(),();
+DELETE FROM t;
+
+DROP TABLE t;
+--sync_slave_with_master
+--connection master
+CREATE TABLE t(txt TEXT DEFAULT '111111111', blb LONGBLOB AS (txt) STORED);
+INSERT INTO t () VALUES (),(),();
+DELETE FROM t;
+
+DROP TABLE t;
+--sync_slave_with_master
diff --git a/mysql-test/suite/rpl/r/rpl_alter_extra_persistent.result b/mysql-test/suite/rpl/r/rpl_alter_extra_persistent.result
index 96df87d8ad4..b1b728304f5 100644
--- a/mysql-test/suite/rpl/r/rpl_alter_extra_persistent.result
+++ b/mysql-test/suite/rpl/r/rpl_alter_extra_persistent.result
@@ -32,8 +32,24 @@ a	z1	z2
 4	5	6
 5	6	7
 6	7	8
-#UPDATE query
+alter table t1 add column z3 int default(a+2);
 connection master;
+insert into t1 values(7);
+insert into t1 values(8);
+connection slave;
+select * from t1 order by a;
+a	z1	z2	z3
+1	2	3	3
+2	3	4	4
+3	4	5	5
+4	5	6	6
+5	6	7	7
+6	7	8	8
+7	8	9	9
+8	9	10	10
+connection master;
+delete from t1 where a > 6;
+#UPDATE query
 update t1 set a = a+10;
 select * from t1 order by a;
 a
@@ -45,13 +61,13 @@ a
 16
 connection slave;
 select * from t1 order by a;
-a	z1	z2
-11	12	13
-12	13	14
-13	14	15
-14	15	16
-15	16	17
-16	17	18
+a	z1	z2	z3
+11	12	13	3
+12	13	14	4
+13	14	15	5
+14	15	16	6
+15	16	17	7
+16	17	18	8
 connection master;
 update t1 set a = a-10;
 select * from t1 order by a;
@@ -64,13 +80,13 @@ a
 6
 connection slave;
 select * from t1 order by a;
-a	z1	z2
-1	2	3
-2	3	4
-3	4	5
-4	5	6
-5	6	7
-6	7	8
+a	z1	z2	z3
+1	2	3	3
+2	3	4	4
+3	4	5	5
+4	5	6	6
+5	6	7	7
+6	7	8	8
 #DELETE quert
 connection master;
 delete from t1 where a > 2 and a < 4;
@@ -83,12 +99,12 @@ a
 6
 connection slave;
 select * from t1 order by a;
-a	z1	z2
-1	2	3
-2	3	4
-4	5	6
-5	6	7
-6	7	8
+a	z1	z2	z3
+1	2	3	3
+2	3	4	4
+4	5	6	6
+5	6	7	7
+6	7	8	8
 #REPLACE query
 connection master;
 replace into t1 values(1);
@@ -96,13 +112,13 @@ replace into t1 values(3);
 replace into t1 values(1);
 connection slave;
 select * from t1 order by a;
-a	z1	z2
-1	2	3
-2	3	4
-3	4	5
-4	5	6
-5	6	7
-6	7	8
+a	z1	z2	z3
+1	2	3	3
+2	3	4	4
+3	4	5	5
+4	5	6	6
+5	6	7	7
+6	7	8	8
 #SELECT query
 connection master;
 select * from t1 where a > 2 and a < 4;
@@ -110,8 +126,8 @@ a
 3
 connection slave;
 select * from t1 where a > 2 and a < 4;
-a	z1	z2
-3	4	5
+a	z1	z2	z3
+3	4	5	5
 #UPDATE with SELECT query
 connection master;
 update t1 set a = a + 10  where a > 2 and a < 4;
@@ -125,13 +141,13 @@ a
 13
 connection slave;
 select * from t1 order by a;
-a	z1	z2
-1	2	3
-2	3	4
-4	5	6
-5	6	7
-6	7	8
-13	14	15
+a	z1	z2	z3
+1	2	3	3
+2	3	4	4
+4	5	6	6
+5	6	7	7
+6	7	8	8
+13	14	15	5
 connection master;
 update t1 set a = a - 10  where a = 13;
 select * from t1 order by a;
@@ -144,13 +160,13 @@ a
 6
 connection slave;
 select * from t1 order by a;
-a	z1	z2
-1	2	3
-2	3	4
-3	4	5
-4	5	6
-5	6	7
-6	7	8
+a	z1	z2	z3
+1	2	3	3
+2	3	4	4
+3	4	5	5
+4	5	6	6
+5	6	7	7
+6	7	8	8
 #Break Unique Constraint
 alter table t1 add column z4 int as (a % 6) persistent unique;
 connection master;
@@ -168,27 +184,27 @@ a
 connection slave;
 include/wait_for_slave_sql_error.inc [errno=1062]
 select * from t1 order by a;
-a	z1	z2	z4
-1	2	3	1
-2	3	4	2
-3	4	5	3
-4	5	6	4
-5	6	7	5
-6	7	8	0
+a	z1	z2	z3	z4
+1	2	3	3	1
+2	3	4	4	2
+3	4	5	5	3
+4	5	6	6	4
+5	6	7	7	5
+6	7	8	8	0
 alter table t1 drop column z4;
 start slave;
 include/wait_for_slave_sql_to_start.inc
 connection master;
 connection slave;
 select * from t1 order by a;
-a	z1	z2
-1	2	3
-2	3	4
-3	4	5
-4	5	6
-5	6	7
-6	7	8
-7	8	9
+a	z1	z2	z3
+1	2	3	3
+2	3	4	4
+3	4	5	5
+4	5	6	6
+5	6	7	7
+6	7	8	8
+7	8	9	9
 connection master;
 select * from t1 order by a;
 a
@@ -200,4 +216,161 @@ a
 6
 7
 drop table t1;
+connection slave;
+connection master;
+set binlog_row_image=minimal;
+create table t1(a int primary key auto_increment, b int unique);
+insert into t1 values(1, 1);
+insert into t1 values(2, 2);
+insert into t1 values(3, 3);
+insert into t1 values(4, 4);
+insert into t1 values(5, 5);
+connection slave;
+alter table t1 add column d1 int default (b),
+add column z1 int as (b+1) virtual,
+add column z2 int as (b+2) persistent;
+connection master;
+insert into t1 values(6, 6);
+update t1 set a = 11 where a = 1;
+update t1 set b = 12 where b = 2;
+delete from t1 where a = 3;
+delete from t1 where b = 5;
+update t1 set b = 16 where a = 6;
+connection slave;
+select * from t1;
+a	b	d1	z1	z2
+11	1	1	2	3
+2	12	2	13	14
+4	4	4	5	6
+6	16	6	17	18
+# Cleanup
+connection master;
+drop table t1;
+connection slave;
+connection master;
+set binlog_row_image=minimal;
+#
+# MDEV-29069 ER_KEY_NOT_FOUND upon online autoinc addition and
+# concurrent DELETE
+#
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+connection slave;
+alter table t add pk int auto_increment primary key;
+connection master;
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+connection slave;
+select * from t;
+a	pk
+11	1
+30	3
+connection master;
+#
+# Add clumsy DEFAULT
+#
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+connection slave;
+alter table t add b int default(RAND() * 20), add key(b),
+algorithm=copy, lock=none;
+connection master;
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+connection slave;
+select a from t;
+a
+11
+30
+connection master;
+# CURRENT_TIMESTAMP
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+connection slave;
+alter table t add b timestamp default CURRENT_TIMESTAMP, add key(b);
+connection master;
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+connection slave;
+select a from t;
+a
+11
+30
+connection master;
+# CURRENT_TIMESTAMP, mixed key
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+connection slave;
+alter table t add b timestamp default CURRENT_TIMESTAMP, add key(a, b);
+connection master;
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+connection slave;
+select a from t;
+a
+11
+30
+connection master;
+# Mixed primary key
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+connection slave;
+alter table t add b int default (1), add primary key(b, a);
+connection master;
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+connection slave;
+select a from t;
+a
+11
+30
+connection master;
+#
+# Normal row, could be used as a key
+#
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+connection slave;
+alter table t add b int as (a * 10) unique;
+connection master;
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+connection slave;
+select * from t;
+a	b
+11	110
+30	300
+connection master;
+#
+# Add key for old row
+#
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+connection slave;
+alter table t add unique(a);
+connection master;
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+connection slave;
+select * from t;
+a
+11
+30
+# Cleanup
+connection master;
+connection slave;
+connection master;
+drop table t;
+#
+# MDEV-30985 Replica stops with error on ALTER ONLINE with Geometry Types
+#
+create table t(geo geometrycollection default st_geomfromtext('point(1 1)'));
+insert into t () values (),(),();
+connection slave;
+alter table t add vcol9 point as (geo), add key(vcol9);
+connection master;
+delete from t;
+connection slave;
+connection master;
+drop table t;
 include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/r/rpl_alter_instant.result b/mysql-test/suite/rpl/r/rpl_alter_innodb.result
similarity index 100%
rename from mysql-test/suite/rpl/r/rpl_alter_instant.result
rename to mysql-test/suite/rpl/r/rpl_alter_innodb.result
diff --git a/mysql-test/suite/rpl/r/rpl_alter_online_debug.result b/mysql-test/suite/rpl/r/rpl_alter_online_debug.result
new file mode 100644
index 00000000000..d0040b39f5c
--- /dev/null
+++ b/mysql-test/suite/rpl/r/rpl_alter_online_debug.result
@@ -0,0 +1,84 @@
+include/master-slave.inc
+[connection master]
+connection master;
+call mtr.add_suppression("Unsafe statement written to the binary log using statement format");
+connection slave;
+call mtr.add_suppression("Unsafe statement written to the binary log using statement format");
+# MDEV-31804 Assertion `thd->m_transaction_psi == __null' fails upon
+# replicating online ALTER
+connection master;
+create table t (a char(8)) engine=myisam;
+insert into t values ('foo'),('bar');
+set debug_sync= 'alter_table_online_progress signal go_dml wait_for go_alter';
+set @old_slave_exec_mode= @@global.slave_exec_mode;
+set @@global.slave_exec_mode= idempotent;
+alter table t force;
+connection master1;
+set debug_sync= 'now wait_for go_dml';
+insert into t (a) values ('qux');
+set debug_sync= 'now signal go_alter';
+connection master;
+connection slave;
+connection master;
+drop table t;
+set global slave_exec_mode= @old_slave_exec_mode;
+set debug_sync= reset;
+#
+# End of 11.2 tests (Single-phase alter)
+#
+connection slave;
+include/stop_slave.inc
+set global slave_parallel_threads=3;
+set global slave_parallel_mode= optimistic;
+set global binlog_row_image=MINIMAL;
+connection master;
+#
+# MDEV-31755 Replica's DML event deadlocks wit online alter table
+#
+# Three threads for SA,U,CA
+create table t (id int, a int, primary key (id)) engine=innodb;
+insert into t values (1,10), (2,20);
+set @@session.binlog_alter_two_phase=1;
+set debug_sync= 'alter_table_online_downgraded signal ready wait_for go';
+alter table t add c text default('qqq') after id, algorithm=copy, lock=none;
+connection master1;
+set debug_sync= 'now wait_for ready';
+update t set a = 1;
+set debug_sync= 'now signal go';
+connection master;
+include/save_master_gtid.inc
+connection slave;
+include/start_slave.inc
+include/sync_with_master_gtid.inc
+select * from t;
+id	c	a
+1	qqq	1
+2	qqq	1
+connection master;
+drop table t;
+# MDEV-31838 Assertion fails on replica upon parallel
+# replication with two-phase alter and MINIMAL row image
+create table t (id int, a int, primary key(id)) engine=aria;
+insert into t values (1,0);
+set @@session.binlog_alter_two_phase=1;
+set debug_sync= 'alter_table_online_progress signal go_dml wait_for go_alter';
+alter table t force, algorithm=copy, lock=none;
+connection master1;
+set binlog_row_image=MINIMAL;
+set debug_sync= 'now wait_for go_dml';
+update ignore t set a = 1;
+set debug_sync= 'now signal go_alter';
+connection master;
+drop table t;
+connection slave;
+connection master;
+#
+# End of 11.2 tests (Two-phase alter)
+#
+connection slave;
+include/stop_slave.inc
+set global binlog_row_image=FULL;
+set global slave_parallel_threads=0;
+set global slave_parallel_mode=   optimistic;
+include/start_slave.inc
+include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/r/rpl_extra_col_slave_innodb.result b/mysql-test/suite/rpl/r/rpl_extra_col_slave_innodb.result
index ea784e7fc21..5a9eb5ee198 100644
--- a/mysql-test/suite/rpl/r/rpl_extra_col_slave_innodb.result
+++ b/mysql-test/suite/rpl/r/rpl_extra_col_slave_innodb.result
@@ -260,8 +260,7 @@ connection slave;
 STOP SLAVE;
 include/reset_slave.inc
 CREATE TABLE t9 (a INT KEY, b BLOB, c CHAR(5),
-d TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 
-ON UPDATE CURRENT_TIMESTAMP,
+d TIMESTAMP NOT NULL DEFAULT 0,
 e INT NOT NULL,
 f text not null,
 g text,
diff --git a/mysql-test/suite/rpl/r/rpl_extra_col_slave_myisam.result b/mysql-test/suite/rpl/r/rpl_extra_col_slave_myisam.result
index 8ad7e98798a..d0fb5a305f8 100644
--- a/mysql-test/suite/rpl/r/rpl_extra_col_slave_myisam.result
+++ b/mysql-test/suite/rpl/r/rpl_extra_col_slave_myisam.result
@@ -260,8 +260,7 @@ connection slave;
 STOP SLAVE;
 include/reset_slave.inc
 CREATE TABLE t9 (a INT KEY, b BLOB, c CHAR(5),
-d TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 
-ON UPDATE CURRENT_TIMESTAMP,
+d TIMESTAMP NOT NULL DEFAULT 0,
 e INT NOT NULL,
 f text not null,
 g text,
diff --git a/mysql-test/suite/rpl/r/rpl_geometry.result b/mysql-test/suite/rpl/r/rpl_geometry.result
index 01dad7b00a0..b2c9503f26c 100644
--- a/mysql-test/suite/rpl/r/rpl_geometry.result
+++ b/mysql-test/suite/rpl/r/rpl_geometry.result
@@ -14,4 +14,14 @@ insert into t2(c) values (null);
 connection slave;
 connection master;
 drop table t1, t2;
+#
+# MDEV-30985 Replica stops with error on ALTER ONLINE with Geometry Types
+#
+create table t(geo geometrycollection default st_geomfromtext('point(1 1)'),
+vc point as (geo));
+insert into t () values (),(),();
+delete from t;
+connection slave;
+connection master;
+drop table t;
 include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/r/rpl_row_blob_innodb.result b/mysql-test/suite/rpl/r/rpl_row_blob_innodb.result
index 084089078cc..9959854a07c 100644
--- a/mysql-test/suite/rpl/r/rpl_row_blob_innodb.result
+++ b/mysql-test/suite/rpl/r/rpl_row_blob_innodb.result
@@ -160,4 +160,19 @@ connection master;
 DROP TABLE IF EXISTS test.t1;
 DROP TABLE IF EXISTS test.t2;
 connection slave;
+#
+# MDEV-30985 Replica stops with error on ALTER ONLINE with Geometry Types
+#
+connection master;
+CREATE TABLE t(txt TEXT DEFAULT '111111111', blb LONGBLOB AS (txt));
+INSERT INTO t () VALUES (),(),();
+DELETE FROM t;
+DROP TABLE t;
+connection slave;
+connection master;
+CREATE TABLE t(txt TEXT DEFAULT '111111111', blb LONGBLOB AS (txt) STORED);
+INSERT INTO t () VALUES (),(),();
+DELETE FROM t;
+DROP TABLE t;
+connection slave;
 include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/r/rpl_row_blob_myisam.result b/mysql-test/suite/rpl/r/rpl_row_blob_myisam.result
index 084089078cc..9959854a07c 100644
--- a/mysql-test/suite/rpl/r/rpl_row_blob_myisam.result
+++ b/mysql-test/suite/rpl/r/rpl_row_blob_myisam.result
@@ -160,4 +160,19 @@ connection master;
 DROP TABLE IF EXISTS test.t1;
 DROP TABLE IF EXISTS test.t2;
 connection slave;
+#
+# MDEV-30985 Replica stops with error on ALTER ONLINE with Geometry Types
+#
+connection master;
+CREATE TABLE t(txt TEXT DEFAULT '111111111', blb LONGBLOB AS (txt));
+INSERT INTO t () VALUES (),(),();
+DELETE FROM t;
+DROP TABLE t;
+connection slave;
+connection master;
+CREATE TABLE t(txt TEXT DEFAULT '111111111', blb LONGBLOB AS (txt) STORED);
+INSERT INTO t () VALUES (),(),();
+DELETE FROM t;
+DROP TABLE t;
+connection slave;
 include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/t/rpl_alter_extra_persistent.test b/mysql-test/suite/rpl/t/rpl_alter_extra_persistent.test
index 4e604787c70..e61d05029f1 100644
--- a/mysql-test/suite/rpl/t/rpl_alter_extra_persistent.test
+++ b/mysql-test/suite/rpl/t/rpl_alter_extra_persistent.test
@@ -18,13 +18,21 @@ select * from t1 order by a;
 insert into t1 values(5);
 insert into t1 values(6);
 
+--sync_slave_with_master
+select * from t1 order by a;
+alter table t1 add column z3 int default(a+2);
+--connection master
+insert into t1 values(7);
+insert into t1 values(8);
+
 --sync_slave_with_master
 select * from t1 order by a;
 
+--connection master
+delete from t1 where a > 6;
 
 --echo #UPDATE query
 
---connection master
 update t1 set a = a+10;
 select * from t1 order by a;
 
@@ -95,12 +103,177 @@ start slave;
 
 --source include/wait_for_slave_sql_to_start.inc
 
---connection master 
+--connection master
 --sync_slave_with_master
 select * from t1 order by a;
 
 --connection master
 select * from t1 order by a;
 drop table t1;
+--sync_slave_with_master
+--connection master
+
+set binlog_row_image=minimal;
+
+create table t1(a int primary key auto_increment, b int unique);
+insert into t1 values(1, 1);
+insert into t1 values(2, 2);
+insert into t1 values(3, 3);
+insert into t1 values(4, 4);
+insert into t1 values(5, 5);
+
+--sync_slave_with_master
+alter table t1 add column d1 int default (b),
+               add column z1 int as (b+1) virtual,
+               add column z2 int as (b+2) persistent;
+--connection master
+
+insert into t1 values(6, 6);
+update t1 set a = 11 where a = 1;
+update t1 set b = 12 where b = 2;
+
+delete from t1 where a = 3;
+delete from t1 where b = 5;
+
+update t1 set b = 16 where a = 6;
+
+--sync_slave_with_master
+select * from t1;
+
+--echo # Cleanup
+--connection master
+drop table t1;
+--sync_slave_with_master
+--connection master
+
+
+set binlog_row_image=minimal;
+
+--echo #
+--echo # MDEV-29069 ER_KEY_NOT_FOUND upon online autoinc addition and
+--echo # concurrent DELETE
+--echo #
+
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+--sync_slave_with_master
+alter table t add pk int auto_increment primary key;
+
+--connection master
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+--sync_slave_with_master
+select * from t;
+--connection master
+
+--echo #
+--echo # Add clumsy DEFAULT
+--echo #
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+--sync_slave_with_master
+alter table t add b int default(RAND() * 20), add key(b),
+              algorithm=copy, lock=none;
+--connection master
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+--sync_slave_with_master
+select a from t;
+--connection master
+
+--echo # CURRENT_TIMESTAMP
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+--sync_slave_with_master
+alter table t add b timestamp default CURRENT_TIMESTAMP, add key(b);
+--connection master
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+--sync_slave_with_master
+select a from t;
+--connection master
+
+--echo # CURRENT_TIMESTAMP, mixed key
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+--sync_slave_with_master
+alter table t add b timestamp default CURRENT_TIMESTAMP, add key(a, b);
+--connection master
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+
+--sync_slave_with_master
+select a from t;
+--connection master
+
+--echo # Mixed primary key
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+--sync_slave_with_master
+alter table t add b int default (1), add primary key(b, a);
+--connection master
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+
+--sync_slave_with_master
+select a from t;
+--connection master
+
+--echo #
+--echo # Normal row, could be used as a key
+--echo #
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+--sync_slave_with_master
+alter table t add b int as (a * 10) unique;
+--connection master
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+
+--sync_slave_with_master
+select * from t;
+--connection master
+
+--echo #
+--echo # Add key for old row
+--echo #
+create or replace table t (a int);
+insert into t values (10),(20),(30);
+
+--sync_slave_with_master
+alter table t add unique(a);
+--connection master
+delete from t where a = 20;
+update t set a = a + 1 where a = 10;
+
+--sync_slave_with_master
+select * from t;
+
+--echo # Cleanup
+--connection master
+--sync_slave_with_master
+--connection master
+drop table t;
+
+--echo #
+--echo # MDEV-30985 Replica stops with error on ALTER ONLINE with Geometry Types
+--echo #
+create table t(geo geometrycollection default st_geomfromtext('point(1 1)'));
+insert into t () values (),(),();
+--sync_slave_with_master
+alter table t add vcol9 point as (geo), add key(vcol9);
+--connection master
+
+delete from t;
+--sync_slave_with_master
+--connection master
+
+drop table t;
 
 --source include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/t/rpl_alter_instant.test b/mysql-test/suite/rpl/t/rpl_alter_innodb.test
similarity index 100%
rename from mysql-test/suite/rpl/t/rpl_alter_instant.test
rename to mysql-test/suite/rpl/t/rpl_alter_innodb.test
diff --git a/mysql-test/suite/rpl/t/rpl_alter_online_debug.test b/mysql-test/suite/rpl/t/rpl_alter_online_debug.test
new file mode 100644
index 00000000000..b257ce29ef3
--- /dev/null
+++ b/mysql-test/suite/rpl/t/rpl_alter_online_debug.test
@@ -0,0 +1,128 @@
+source include/have_debug_sync.inc;
+source include/have_innodb.inc;
+source include/master-slave.inc;
+
+--connection master
+call mtr.add_suppression("Unsafe statement written to the binary log using statement format");
+
+--connection slave
+call mtr.add_suppression("Unsafe statement written to the binary log using statement format");
+
+#
+# Single-phase alter
+#
+
+--echo # MDEV-31804 Assertion `thd->m_transaction_psi == __null' fails upon
+--echo # replicating online ALTER
+--connection master
+create table t (a char(8)) engine=myisam;
+insert into t values ('foo'),('bar');
+
+set debug_sync= 'alter_table_online_progress signal go_dml wait_for go_alter';
+set @old_slave_exec_mode= @@global.slave_exec_mode;
+set @@global.slave_exec_mode= idempotent;
+send alter table t force;
+
+--connection master1
+set debug_sync= 'now wait_for go_dml';
+insert into t (a) values ('qux');
+set debug_sync= 'now signal go_alter';
+
+--connection master
+--reap
+--sync_slave_with_master
+
+# Cleanup
+--connection master
+drop table t;
+set global slave_exec_mode= @old_slave_exec_mode;
+set debug_sync= reset;
+
+
+--echo #
+--echo # End of 11.2 tests (Single-phase alter)
+--echo #
+
+
+#
+# Two-phase alter
+#
+--connection slave
+source include/stop_slave.inc;
+--let $slave_parallel_threads=`select @@global.slave_parallel_threads`
+--let $slave_parallel_mode=   `select @@global.slave_parallel_mode`
+--let $binlog_row_image= `select @@global.binlog_row_image`
+set global slave_parallel_threads=3;
+set global slave_parallel_mode= optimistic;
+set global binlog_row_image=MINIMAL;
+--connection master
+
+
+--echo #
+--echo # MDEV-31755 Replica's DML event deadlocks wit online alter table
+--echo #
+--echo # Three threads for SA,U,CA
+
+create table t (id int, a int, primary key (id)) engine=innodb;
+insert into t values (1,10), (2,20);
+
+set @@session.binlog_alter_two_phase=1;
+set debug_sync= 'alter_table_online_downgraded signal ready wait_for go';
+send alter table t add c text default('qqq') after id, algorithm=copy, lock=none;
+
+--connection master1
+set debug_sync= 'now wait_for ready';
+
+update t set a = 1;
+
+set debug_sync= 'now signal go';
+
+--connection master
+--reap
+--source include/save_master_gtid.inc
+
+--connection slave
+source include/start_slave.inc;
+--source include/sync_with_master_gtid.inc
+select * from t;
+
+# Cleanup
+--connection master
+drop table t;
+
+--echo # MDEV-31838 Assertion fails on replica upon parallel
+--echo # replication with two-phase alter and MINIMAL row image
+create table t (id int, a int, primary key(id)) engine=aria;
+insert into t values (1,0);
+
+set @@session.binlog_alter_two_phase=1;
+set debug_sync= 'alter_table_online_progress signal go_dml wait_for go_alter';
+send alter table t force, algorithm=copy, lock=none;
+
+--connection master1
+set binlog_row_image=MINIMAL;
+set debug_sync= 'now wait_for go_dml';
+--disable_warnings
+update ignore t set a = 1;
+--enable_warnings
+set debug_sync= 'now signal go_alter';
+
+# Cleanup
+--connection master
+--reap
+drop table t;
+--sync_slave_with_master
+--connection master
+
+--echo #
+--echo # End of 11.2 tests (Two-phase alter)
+--echo #
+
+--connection slave
+source include/stop_slave.inc;
+--eval set global binlog_row_image=$binlog_row_image
+--eval set global slave_parallel_threads=$slave_parallel_threads
+--eval set global slave_parallel_mode=   $slave_parallel_mode
+source include/start_slave.inc;
+
+--source include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/t/rpl_geometry.test b/mysql-test/suite/rpl/t/rpl_geometry.test
index 415732a0228..664979ebe0e 100644
--- a/mysql-test/suite/rpl/t/rpl_geometry.test
+++ b/mysql-test/suite/rpl/t/rpl_geometry.test
@@ -23,4 +23,17 @@ sync_slave_with_master;
 
 connection master;
 drop table t1, t2;
+
+--echo #
+--echo # MDEV-30985 Replica stops with error on ALTER ONLINE with Geometry Types
+--echo #
+create table t(geo geometrycollection default st_geomfromtext('point(1 1)'),
+	       vc point as (geo));
+insert into t () values (),(),();
+delete from t;
+
+sync_slave_with_master;
+connection master;
+drop table t;
+
 --source include/rpl_end.inc
diff --git a/mysql-test/suite/s3/alter.result b/mysql-test/suite/s3/alter.result
index 1a931b71e01..da8accca66c 100644
--- a/mysql-test/suite/s3/alter.result
+++ b/mysql-test/suite/s3/alter.result
@@ -130,3 +130,7 @@ count(*)	sum(a)	sum(b)
 select count(*), sum(a), sum(b) from t1;
 ERROR 42S02: Table 'database.t1' doesn't exist
 drop table t2;
+# MDEV-31781 ALTER TABLE ENGINE=s3 fails
+create table t (a int) engine=Aria;
+alter table t engine=S3, algorithm=copy, lock=none;
+ERROR 0A000: LOCK=NONE is not supported. Reason: S3. Try LOCK=SHARED
diff --git a/mysql-test/suite/s3/alter.test b/mysql-test/suite/s3/alter.test
index 7882d14e7b4..495e6dc208b 100644
--- a/mysql-test/suite/s3/alter.test
+++ b/mysql-test/suite/s3/alter.test
@@ -93,6 +93,12 @@ select count(*), sum(a), sum(b) from t2;
 select count(*), sum(a), sum(b) from t1;
 drop table t2;
 
+
+--echo # MDEV-31781 ALTER TABLE ENGINE=s3 fails
+create table t (a int) engine=Aria;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+alter table t engine=S3, algorithm=copy, lock=none;
+
 #
 # clean up
 #
diff --git a/mysql-test/suite/sys_vars/r/old_mode_basic.result b/mysql-test/suite/sys_vars/r/old_mode_basic.result
index 252316dc1cb..776d45a1fe3 100644
--- a/mysql-test/suite/sys_vars/r/old_mode_basic.result
+++ b/mysql-test/suite/sys_vars/r/old_mode_basic.result
@@ -114,8 +114,8 @@ SET @@global.old_mode = 4;
 SELECT @@global.old_mode;
 @@global.old_mode
 ZERO_DATE_TIME_CAST
-SET @@global.old_mode = 64;
-ERROR 42000: Variable 'old_mode' can't be set to the value of '64'
+SET @@global.old_mode = 128;
+ERROR 42000: Variable 'old_mode' can't be set to the value of '128'
 SELECT @@global.old_mode;
 @@global.old_mode
 ZERO_DATE_TIME_CAST
diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result
index fa3bf6b4354..986161e3d66 100644
--- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result
+++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result
@@ -2289,7 +2289,7 @@ VARIABLE_COMMENT	Used to emulate old behavior from earlier MariaDB or MySQL vers
 NUMERIC_MIN_VALUE	NULL
 NUMERIC_MAX_VALUE	NULL
 NUMERIC_BLOCK_SIZE	NULL
-ENUM_VALUE_LIST	NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM
+ENUM_VALUE_LIST	NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM,LOCK_ALTER_TABLE_COPY
 READ_ONLY	NO
 COMMAND_LINE_ARGUMENT	REQUIRED
 VARIABLE_NAME	OLD_PASSWORDS
diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result
index 09b0cf41d8f..9d26e3c6771 100644
--- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result
+++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result
@@ -2489,7 +2489,7 @@ VARIABLE_COMMENT	Used to emulate old behavior from earlier MariaDB or MySQL vers
 NUMERIC_MIN_VALUE	NULL
 NUMERIC_MAX_VALUE	NULL
 NUMERIC_BLOCK_SIZE	NULL
-ENUM_VALUE_LIST	NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM
+ENUM_VALUE_LIST	NO_DUP_KEY_WARNINGS_WITH_IGNORE,NO_PROGRESS_INFO,ZERO_DATE_TIME_CAST,UTF8_IS_UTF8MB3,IGNORE_INDEX_ONLY_FOR_JOIN,COMPAT_5_1_CHECKSUM,LOCK_ALTER_TABLE_COPY
 READ_ONLY	NO
 COMMAND_LINE_ARGUMENT	REQUIRED
 VARIABLE_NAME	OLD_PASSWORDS
diff --git a/mysql-test/suite/sys_vars/t/old_mode_basic.test b/mysql-test/suite/sys_vars/t/old_mode_basic.test
index 631d638767f..cb18796729e 100644
--- a/mysql-test/suite/sys_vars/t/old_mode_basic.test
+++ b/mysql-test/suite/sys_vars/t/old_mode_basic.test
@@ -172,7 +172,7 @@ SET @@global.old_mode = 4;
 SELECT @@global.old_mode;
 
 --Error ER_WRONG_VALUE_FOR_VAR
-SET @@global.old_mode = 64;
+SET @@global.old_mode = 128;
 SELECT @@global.old_mode;
 
 # use of decimal values
diff --git a/mysql-test/suite/versioning/r/online.result b/mysql-test/suite/versioning/r/online.result
index b247ce49e18..b31957021f4 100644
--- a/mysql-test/suite/versioning/r/online.result
+++ b/mysql-test/suite/versioning/r/online.result
@@ -1,13 +1,13 @@
 set system_versioning_alter_history=keep;
 create or replace table t (a int);
 alter table t add system versioning, lock=none;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Not implemented for system-versioned timestamp tables. Try LOCK=SHARED
+alter table t drop system versioning;
 alter table t add system versioning, algorithm=inplace;
 ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Not implemented for system-versioned timestamp tables. Try ALGORITHM=COPY
 alter table t add system versioning, lock=shared;
 alter table t add column b int, change column a a int without system versioning, lock=none;
 alter table t drop system versioning, lock=none;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Not implemented for system-versioned operations. Try LOCK=SHARED
+ERROR 0A000: LOCK=NONE is not supported. Reason: DROP SYSTEM VERSIONING. Try LOCK=SHARED
 alter table t drop system versioning, algorithm=inplace;
 create or replace table t (
 a int, b int,
@@ -127,7 +127,7 @@ alter table t drop column b, algorithm=instant;
 alter table t add index idx(a), lock=none;
 alter table t drop column s, drop column e;
 alter table t drop system versioning, lock=none;
-ERROR 0A000: LOCK=NONE is not supported. Reason: Not implemented for system-versioned operations. Try LOCK=SHARED
+ERROR 0A000: LOCK=NONE is not supported. Reason: DROP SYSTEM VERSIONING. Try LOCK=SHARED
 #
 # MDEV-17697 Broken versioning info after instant drop column
 #
diff --git a/mysql-test/suite/versioning/t/online.test b/mysql-test/suite/versioning/t/online.test
index 5932c34687a..469b0c6408d 100644
--- a/mysql-test/suite/versioning/t/online.test
+++ b/mysql-test/suite/versioning/t/online.test
@@ -5,8 +5,8 @@
 set system_versioning_alter_history=keep;
 
 create or replace table t (a int);
---error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
 alter table t add system versioning, lock=none;
+alter table t drop system versioning;
 --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
 alter table t add system versioning, algorithm=inplace;
 alter table t add system versioning, lock=shared;
diff --git a/mysys/my_bitmap.c b/mysys/my_bitmap.c
index 9893c7e4a58..229e512dc1e 100644
--- a/mysys/my_bitmap.c
+++ b/mysys/my_bitmap.c
@@ -576,14 +576,17 @@ uint bitmap_bits_set(const MY_BITMAP *map)
 void bitmap_copy(MY_BITMAP *map, const MY_BITMAP *map2)
 {
   my_bitmap_map *to= map->bitmap, *from= map2->bitmap, *end;
+  uint len= no_words_in_map(map), len2 = no_words_in_map(map2);
 
   DBUG_ASSERT(map->bitmap);
   DBUG_ASSERT(map2->bitmap);
-  DBUG_ASSERT(map->n_bits == map2->n_bits);
-  end= map->last_word_ptr;
 
-  while (to <= end)
+  end= to + MY_MIN(len, len2 - 1);
+  while (to < end)
     *to++ = *from++;
+
+  if (len2 <= len)
+    *to= (*from & ~map2->last_word_mask) | (*to & map2->last_word_mask);
 }
 
 
diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c
index c1ec062309b..729a18d4e13 100644
--- a/mysys/thr_lock.c
+++ b/mysys/thr_lock.c
@@ -1386,52 +1386,6 @@ my_bool thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread_id)
 }
 
 
-/*
-  Downgrade a WRITE_* to a lower WRITE level
-  SYNOPSIS
-    thr_downgrade_write_lock()
-    in_data                   Lock data of thread downgrading its lock
-    new_lock_type             New write lock type
-  RETURN VALUE
-    NONE
-  DESCRIPTION
-    This can be used to downgrade a lock already owned. When the downgrade
-    occurs also other waiters, both readers and writers can be allowed to
-    start.
-    The previous lock is often TL_WRITE_ONLY but can also be
-    TL_WRITE. The normal downgrade variants are:
-    TL_WRITE_ONLY => TL_WRITE after a short exclusive lock while holding a
-    write table lock
-    TL_WRITE_ONLY => TL_WRITE_ALLOW_WRITE After a short exclusive lock after
-    already earlier having dongraded lock to TL_WRITE_ALLOW_WRITE
-    The implementation is conservative and rather don't start rather than
-    go on unknown paths to start, the common cases are handled.
-
-    NOTE:
-    In its current implementation it is only allowed to downgrade from
-    TL_WRITE_ONLY. In this case there are no waiters. Thus no wake up
-    logic is required.
-*/
-
-void thr_downgrade_write_lock(THR_LOCK_DATA *in_data,
-                              enum thr_lock_type new_lock_type)
-{
-  THR_LOCK *lock=in_data->lock;
-#ifdef DBUG_ASSERT_EXISTS
-  enum thr_lock_type old_lock_type= in_data->type;
-#endif
-  DBUG_ENTER("thr_downgrade_write_only_lock");
-
-  mysql_mutex_lock(&lock->mutex);
-  DBUG_ASSERT(old_lock_type == TL_WRITE_ONLY);
-  DBUG_ASSERT(old_lock_type > new_lock_type);
-  in_data->type= new_lock_type;
-  check_locks(lock,"after downgrading lock", old_lock_type, 0);
-
-  mysql_mutex_unlock(&lock->mutex);
-  DBUG_VOID_RETURN;
-}
-
 /* Upgrade a WRITE_DELAY lock to a WRITE_LOCK */
 
 my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data,
diff --git a/sql/field.cc b/sql/field.cc
index 8b30c6a71ed..5acebecd5c4 100644
--- a/sql/field.cc
+++ b/sql/field.cc
@@ -10508,7 +10508,6 @@ void Column_definition::create_length_to_internal_length_newdecimal()
 
 bool check_expression(Virtual_column_info *vcol, const LEX_CSTRING *name,
                       enum_vcol_info_type type, Alter_info *alter_info)
-
 {
   bool ret;
   Item::vcol_func_processor_result res;
diff --git a/sql/field.h b/sql/field.h
index 94f1d977cdd..477dc0fe282 100644
--- a/sql/field.h
+++ b/sql/field.h
@@ -1192,7 +1192,7 @@ class Field: public Value_source
   {
     bitmap_set_bit(&table->has_value_set, field_index);
   }
-  bool has_explicit_value()
+  bool has_explicit_value() const
   {
     return bitmap_is_set(&table->has_value_set, field_index);
   }
diff --git a/sql/ha_sequence.cc b/sql/ha_sequence.cc
index bab0614706d..1ba57875d52 100644
--- a/sql/ha_sequence.cc
+++ b/sql/ha_sequence.cc
@@ -287,8 +287,7 @@ int ha_sequence::write_row(const uchar *buf)
       sequence->copy(&tmp_seq);
     rows_changed++;
     /* We have to do the logging while we hold the sequence mutex */
-    if (row_logging)
-      error= binlog_log_row(table, 0, buf, log_func);
+    error= binlog_log_row(0, buf, log_func);
   }
 
   /* Row is already logged, don't log it again in ha_write_row() */
diff --git a/sql/handler.cc b/sql/handler.cc
index bb3b0976164..1c8d56cafd1 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -7243,25 +7243,62 @@ bool handler::check_table_binlog_row_based_internal()
                     mysql_bin_log.is_open()));
 }
 
-
-int handler::binlog_log_row(TABLE *table,
-                            const uchar *before_record,
-                            const uchar *after_record,
-                            Log_func *log_func)
+static int binlog_log_row_to_binlog(TABLE* table,
+                                    const uchar *before_record,
+                                    const uchar *after_record,
+                                    Log_func *log_func,
+                                    bool has_trans)
 {
-  bool error;
-  THD *thd= table->in_use;
-  DBUG_ENTER("binlog_log_row");
+  bool error= 0;
+  THD *const thd= table->in_use;
+
+  DBUG_ENTER("binlog_log_row_to_binlog");
 
   if (!thd->binlog_table_maps &&
       thd->binlog_write_table_maps())
     DBUG_RETURN(HA_ERR_RBR_LOGGING_FAILED);
 
-  error= (*log_func)(thd, table, row_logging_has_trans,
-                     before_record, after_record);
+  DBUG_ASSERT(thd->is_current_stmt_binlog_format_row());
+  DBUG_ASSERT((WSREP_NNULL(thd) && wsrep_emulate_bin_log)
+              || mysql_bin_log.is_open());
+
+  auto *cache_mngr= thd->binlog_setup_trx_data();
+  if (cache_mngr == NULL)
+    DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
+  /* Ensure that all events in a GTID group are in the same cache */
+  if (thd->variables.option_bits & OPTION_GTID_BEGIN)
+    has_trans= 1;
+
+  auto *cache= binlog_get_cache_data(cache_mngr,
+                                     use_trans_cache(thd, has_trans));
+
+    error= (*log_func)(thd, table, mysql_bin_log.as_event_log(), cache,
+                       has_trans, thd->variables.binlog_row_image,
+                       before_record, after_record);
   DBUG_RETURN(error ? HA_ERR_RBR_LOGGING_FAILED : 0);
 }
 
+int handler::binlog_log_row(const uchar *before_record,
+                            const uchar *after_record,
+                            Log_func *log_func)
+{
+  DBUG_ENTER("handler::binlog_log_row");
+
+  int error = 0;
+  if (row_logging)
+    error= binlog_log_row_to_binlog(table, before_record, after_record,
+                                    log_func, row_logging_has_trans);
+
+#ifdef HAVE_REPLICATION
+  if (unlikely(!error && table->s->online_alter_binlog && is_root_handler()))
+    error= binlog_log_row_online_alter(table, before_record, after_record,
+                                       log_func);
+#endif // HAVE_REPLICATION
+
+  DBUG_RETURN(error);
+}
+
 
 int handler::ha_external_lock(THD *thd, int lock_type)
 {
@@ -7779,18 +7816,7 @@ int handler::ha_write_row(const uchar *buf)
   if ((error= ha_check_overlaps(NULL, buf)))
     DBUG_RETURN(error);
 
-  /*
-    NOTE: this != table->file is true in 3 cases:
-
-    1. under copy_partitions() (REORGANIZE PARTITION): that does not
-       require long unique check as it does not introduce new rows or new index.
-    2. under partition's ha_write_row() (INSERT): check_duplicate_long_entries()
-       was already done by ha_partition::ha_write_row(), no need to check it
-       again for each single partition.
-    3. under ha_mroonga::wrapper_write_row()
-  */
-
-  if (table->s->long_unique_table && this == table->file)
+  if (table->s->long_unique_table && is_root_handler())
   {
     DBUG_ASSERT(inited == NONE || lookup_handler != this);
     if ((error= check_duplicate_long_entries(buf)))
@@ -7808,11 +7834,9 @@ int handler::ha_write_row(const uchar *buf)
   if (likely(!error))
   {
     rows_changed++;
-    if (row_logging)
-    {
-      Log_func *log_func= Write_rows_log_event::binlog_row_logging_function;
-      error= binlog_log_row(table, 0, buf, log_func);
-    }
+    Log_func *log_func= Write_rows_log_event::binlog_row_logging_function;
+    error= binlog_log_row(0, buf, log_func);
+
 #ifdef WITH_WSREP
     if (WSREP_NNULL(ha_thd()) && table_share->tmp_table == NO_TMP_TABLE &&
         ht->flags & HTON_WSREP_REPLICATION &&
@@ -7843,14 +7867,7 @@ int handler::ha_update_row(const uchar *old_data, const uchar *new_data)
   uint saved_status= table->status;
   error= ha_check_overlaps(old_data, new_data);
 
-  /*
-    NOTE: this != table->file is true under partition's ha_update_row():
-    check_duplicate_long_entries_update() was already done by
-    ha_partition::ha_update_row(), no need to check it again for each single
-    partition. Same applies to ha_mroonga wrapper.
-  */
-
-  if (!error && table->s->long_unique_table && this == table->file)
+  if (!error && table->s->long_unique_table && is_root_handler())
     error= check_duplicate_long_entries_update(new_data);
   table->status= saved_status;
 
@@ -7868,11 +7885,9 @@ int handler::ha_update_row(const uchar *old_data, const uchar *new_data)
   if (likely(!error))
   {
     rows_changed++;
-    if (row_logging)
-    {
-      Log_func *log_func= Update_rows_log_event::binlog_row_logging_function;
-      error= binlog_log_row(table, old_data, new_data, log_func);
-    }
+    Log_func *log_func= Update_rows_log_event::binlog_row_logging_function;
+    error= binlog_log_row(old_data, new_data, log_func);
+
 #ifdef WITH_WSREP
     THD *thd= ha_thd();
     if (WSREP_NNULL(thd))
@@ -7946,11 +7961,9 @@ int handler::ha_delete_row(const uchar *buf)
   if (likely(!error))
   {
     rows_changed++;
-    if (row_logging)
-    {
-      Log_func *log_func= Delete_rows_log_event::binlog_row_logging_function;
-      error= binlog_log_row(table, buf, 0, log_func);
-    }
+    Log_func *log_func= Delete_rows_log_event::binlog_row_logging_function;
+    error= binlog_log_row(buf, 0, log_func);
+
 #ifdef WITH_WSREP
     THD *thd= ha_thd();
     if (WSREP_NNULL(thd))
diff --git a/sql/handler.h b/sql/handler.h
index 7625fc9c35e..91349802c65 100644
--- a/sql/handler.h
+++ b/sql/handler.h
@@ -371,7 +371,10 @@ enum chf_create_flags {
 /* Implements SELECT ... FOR UPDATE SKIP LOCKED */
 #define HA_CAN_SKIP_LOCKED  (1ULL << 61)
 
-#define HA_LAST_TABLE_FLAG HA_CAN_SKIP_LOCKED
+/* This engine is not compatible with Online ALTER TABLE */
+#define HA_NO_ONLINE_ALTER  (1ULL << 62)
+
+#define HA_LAST_TABLE_FLAG HA_NO_ONLINE_ALTER
 
 
 /* bits in index_flags(index_number) for what you can do with index */
@@ -654,7 +657,13 @@ given at all. */
 #define HA_CREATE_PRINT_ALL_OPTIONS       (1UL << 26)
 
 typedef ulonglong alter_table_operations;
-typedef bool Log_func(THD*, TABLE*, bool, const uchar*, const uchar*);
+
+class Event_log;
+class Cache_flip_event_log;
+class binlog_cache_data;
+class online_alter_cache_data;
+typedef bool Log_func(THD*, TABLE*, Event_log *, binlog_cache_data *, bool,
+                      ulong, const uchar*, const uchar*);
 
 /*
   These flags are set by the parser and describes the type of
@@ -3551,6 +3560,11 @@ class handler :public Sql_alloc
     if (org_keyread != MAX_KEY)
       ha_start_keyread(org_keyread);
   }
+
+protected:
+  bool is_root_handler() const;
+
+public:
   int check_collation_compatibility();
   int check_long_hash_compatibility() const;
   int ha_check_for_upgrade(HA_CHECK_OPT *check_opt);
@@ -4420,7 +4434,7 @@ class handler :public Sql_alloc
     @return The handler error code or zero for success.
   */
   virtual int
-  get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list)
+  get_foreign_key_list(const THD *thd, List<FOREIGN_KEY_INFO> *f_key_list)
   { return 0; }
   /**
     Get the list of foreign keys referencing this table.
@@ -4434,7 +4448,7 @@ class handler :public Sql_alloc
     @return The handler error code or zero for success.
   */
   virtual int
-  get_parent_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list)
+  get_parent_foreign_key_list(const THD *thd, List<FOREIGN_KEY_INFO> *f_key_list)
   { return 0; }
   virtual uint referenced_by_foreign_key() { return 0;}
   virtual void init_table_handle_for_HANDLER()
@@ -4506,8 +4520,7 @@ class handler :public Sql_alloc
     than lock_count() claimed. This can happen when the MERGE children
     are not attached when this is called from another thread.
   */
-  virtual THR_LOCK_DATA **store_lock(THD *thd,
-				     THR_LOCK_DATA **to,
+  virtual THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to,
 				     enum thr_lock_type lock_type)=0;
 
   /** Type of table for caching query */
@@ -5049,8 +5062,7 @@ class handler :public Sql_alloc
   bool check_table_binlog_row_based();
   bool prepare_for_row_logging();
   int prepare_for_insert(bool do_create);
-  int binlog_log_row(TABLE *table,
-                     const uchar *before_record,
+  int binlog_log_row(const uchar *before_record,
                      const uchar *after_record,
                      Log_func *log_func);
 
@@ -5623,11 +5635,6 @@ inline const LEX_CSTRING *table_case_name(HA_CREATE_INFO *info, const LEX_CSTRIN
   return ((lower_case_table_names == 2 && info->alias.str) ? &info->alias : name);
 }
 
-typedef bool Log_func(THD*, TABLE*, bool, const uchar*, const uchar*);
-int binlog_log_row(TABLE* table,
-                   const uchar *before_record,
-                   const uchar *after_record,
-                   Log_func *log_func);
 
 /**
   @def MYSQL_TABLE_IO_WAIT
diff --git a/sql/item.cc b/sql/item.cc
index a07b5532f83..e7815941a84 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -1560,7 +1560,7 @@ bool Item_field::check_vcol_func_processor(void *arg)
 {
   context= 0;
   vcol_func_processor_result *res= (vcol_func_processor_result *) arg;
-  if (res && res->alter_info)
+  if (res->alter_info)
   {
     for (Key &k: res->alter_info->key_list)
     {
diff --git a/sql/lock.cc b/sql/lock.cc
index da13fff35c8..08514daddd8 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -816,9 +816,8 @@ MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags)
     enum thr_lock_type lock_type;
     THR_LOCK_DATA **locks_start;
 
-    if (!((likely(!table->s->tmp_table) ||
-           (table->s->tmp_table == TRANSACTIONAL_TMP_TABLE)) &&
-          (!(flags & GET_LOCK_SKIP_SEQUENCES) || table->s->sequence == 0)))
+    if ((table->s->tmp_table && table->s->tmp_table != TRANSACTIONAL_TMP_TABLE)
+       || (flags & GET_LOCK_SKIP_SEQUENCES && table->s->sequence != NULL))
       continue;
     lock_type= table->reginfo.lock_type;
     DBUG_ASSERT(lock_type != TL_WRITE_DEFAULT && lock_type != TL_READ_DEFAULT);
diff --git a/sql/log.cc b/sql/log.cc
index 3dd0944b4e9..171ebc03d85 100644
--- a/sql/log.cc
+++ b/sql/log.cc
@@ -45,6 +45,7 @@
 #include <m_ctype.h>				// For test_if_number
 
 #include <set_var.h> // for Sys_last_gtid_ptr
+#include <ilist.h>
 
 #ifdef _WIN32
 #include "message.h"
@@ -103,6 +104,8 @@ static int binlog_flush_cache(THD *thd, binlog_cache_mngr *cache_mngr,
                               Log_event *end_ev, bool all, bool using_stmt,
                               bool using_trx, bool is_ro_1pc);
 
+static int binlog_online_alter_end_trans(THD *thd, bool all, bool commit);
+
 static const LEX_CSTRING write_error_msg=
     { STRING_WITH_LEN("error writing to the binary log") };
 
@@ -275,10 +278,8 @@ void make_default_log_name(char **out, const char* log_ext, bool once)
 class binlog_cache_data
 {
 public:
-  binlog_cache_data(): m_pending(0), status(0),
-  before_stmt_pos(MY_OFF_T_UNDEF),
-  incident(FALSE),
-  saved_max_binlog_cache_size(0), ptr_binlog_cache_use(0),
+  binlog_cache_data(): before_stmt_pos(MY_OFF_T_UNDEF), m_pending(0), status(0),
+  incident(FALSE), saved_max_binlog_cache_size(0), ptr_binlog_cache_use(0),
   ptr_binlog_cache_disk_use(0)
   { }
   
@@ -410,6 +411,12 @@ class binlog_cache_data
   */
   IO_CACHE cache_log;
 
+protected:
+  /*
+    Binlog position before the start of the current statement.
+  */
+  my_off_t before_stmt_pos;
+
 private:
   /*
     Pending binrows event. This event is the event where the rows are currently
@@ -424,11 +431,6 @@ class binlog_cache_data
   */
   uint32 status;
 
-  /*
-    Binlog position before the start of the current statement.
-  */
-  my_off_t before_stmt_pos;
- 
   /*
     This indicates that some events did not get into the cache and most likely
     it is corrupted.
@@ -487,7 +489,8 @@ class binlog_cache_data
       delete pending();
       set_pending(0);
     }
-    reinit_io_cache(&cache_log, WRITE_CACHE, pos, 0, reset_cache);
+    my_bool res= reinit_io_cache(&cache_log, WRITE_CACHE, pos, 0, reset_cache);
+    DBUG_ASSERT(res == 0);
     cache_log.end_of_file= saved_max_binlog_cache_size;
   }
 
@@ -496,6 +499,20 @@ class binlog_cache_data
 };
 
 
+class online_alter_cache_data: public Sql_alloc, public ilist_node<>,
+  public binlog_cache_data
+{
+public:
+  void store_prev_position()
+  {
+    before_stmt_pos= my_b_write_tell(&cache_log);
+  }
+
+  handlerton *hton;
+  Cache_flip_event_log *sink_log;
+  SAVEPOINT *sv_list;
+};
+
 void Log_event_writer::add_status(enum_logged_status status)
 {
   if (likely(cache_data))
@@ -578,6 +595,7 @@ class binlog_cache_mngr {
   ulong binlog_id;
   /* Set if we get an error during commit that must be returned from unlog(). */
   bool delayed_error;
+
   //Will be reset when gtid is written into binlog
   uchar  gtid_flags3;
   decltype (rpl_gtid::seq_no) sa_seq_no;
@@ -1763,12 +1781,11 @@ binlog_trans_log_truncate(THD *thd, my_off_t pos)
   DBUG_ENTER("binlog_trans_log_truncate");
   DBUG_PRINT("enter", ("pos: %lu", (ulong) pos));
 
-  DBUG_ASSERT(thd_get_ha_data(thd, binlog_hton) != NULL);
+  DBUG_ASSERT(thd->binlog_get_cache_mngr() != NULL);
   /* Only true if binlog_trans_log_savepos() wasn't called before */
   DBUG_ASSERT(pos != ~(my_off_t) 0);
 
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr();
   cache_mngr->trx_cache.restore_savepoint(pos);
   DBUG_VOID_RETURN;
 }
@@ -1808,8 +1825,7 @@ int binlog_init(void *p)
 static int binlog_close_connection(handlerton *hton, THD *thd)
 {
   DBUG_ENTER("binlog_close_connection");
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr();
 #ifdef WITH_WSREP
   if (WSREP(thd) && cache_mngr && !cache_mngr->trx_cache.empty()) {
     IO_CACHE* cache= cache_mngr->get_binlog_cache_log(true);
@@ -2057,29 +2073,31 @@ static int
 binlog_truncate_trx_cache(THD *thd, binlog_cache_mngr *cache_mngr, bool all)
 {
   DBUG_ENTER("binlog_truncate_trx_cache");
+
+  if(!WSREP_EMULATE_BINLOG_NNULL(thd) && !mysql_bin_log.is_open())
+    DBUG_RETURN(0);
+
   int error=0;
-  /*
-    This function handles transactional changes and as such this flag
-    equals to true.
-  */
-  bool const is_transactional= TRUE;
 
   DBUG_PRINT("info", ("thd->options={ %s %s}, transaction: %s",
                       FLAGSTR(thd->variables.option_bits, OPTION_NOT_AUTOCOMMIT),
                       FLAGSTR(thd->variables.option_bits, OPTION_BEGIN),
                       all ? "all" : "stmt"));
 
-  thd->binlog_remove_pending_rows_event(TRUE, is_transactional);
+  auto &trx_cache= cache_mngr->trx_cache;
+  MYSQL_BIN_LOG::remove_pending_rows_event(thd, &trx_cache);
+  thd->reset_binlog_for_next_statement();
+
   /*
     If rolling back an entire transaction or a single statement not
     inside a transaction, we reset the transaction cache.
   */
   if (ending_trans(thd, all))
   {
-    if (cache_mngr->trx_cache.has_incident())
+    if (trx_cache.has_incident())
       error= mysql_bin_log.write_incident(thd);
 
-    thd->reset_binlog_for_next_statement();
+    DBUG_ASSERT(thd->binlog_table_maps == 0);
 
     cache_mngr->reset(false, true);
   }
@@ -2088,9 +2106,9 @@ binlog_truncate_trx_cache(THD *thd, binlog_cache_mngr *cache_mngr, bool all)
     transaction cache to remove the statement.
   */
   else
-    cache_mngr->trx_cache.restore_prev_position();
+    trx_cache.restore_prev_position();
 
-  DBUG_ASSERT(thd->binlog_get_pending_rows_event(is_transactional) == NULL);
+  DBUG_ASSERT(trx_cache.pending() == NULL);
   DBUG_RETURN(error);
 }
 
@@ -2249,6 +2267,62 @@ static int binlog_commit_flush_xa_prepare(THD *thd, bool all,
   return (binlog_flush_cache(thd, cache_mngr, &end_evt, all, TRUE, TRUE));
 }
 
+#ifdef HAVE_REPLICATION
+int binlog_log_row_online_alter(TABLE* table, const uchar *before_record,
+                                const uchar *after_record, Log_func *log_func)
+{
+  THD *thd= table->in_use;
+
+  if (!table->online_alter_cache)
+  {
+    table->online_alter_cache= online_alter_binlog_get_cache_data(thd, table);
+    trans_register_ha(thd, false, binlog_hton, 0);
+    if (thd->in_multi_stmt_transaction_mode())
+      trans_register_ha(thd, true, binlog_hton, 0);
+  }
+
+  // We need to log all columns for the case if alter table changes primary key
+  DBUG_ASSERT(!before_record || bitmap_is_set_all(table->read_set));
+  MY_BITMAP *old_rpl_write_set= table->rpl_write_set;
+  table->rpl_write_set= &table->s->all_set;
+
+  table->online_alter_cache->store_prev_position();
+  int error= (*log_func)(thd, table, table->s->online_alter_binlog,
+                         table->online_alter_cache,
+                         table->file->has_transactions_and_rollback(),
+                         BINLOG_ROW_IMAGE_FULL,
+                         before_record, after_record);
+
+  table->rpl_write_set= old_rpl_write_set;
+
+  if (unlikely(error))
+  {
+    table->online_alter_cache->restore_prev_position();
+    return HA_ERR_RBR_LOGGING_FAILED;
+  }
+
+  return 0;
+}
+
+static void
+binlog_online_alter_cleanup(ilist<online_alter_cache_data> &list, bool ending_trans)
+{
+  if (ending_trans)
+  {
+    auto it= list.begin();
+    while (it != list.end())
+    {
+      auto &cache= *it++;
+      cache.sink_log->release();
+      cache.reset();
+      delete &cache;
+    }
+    list.clear();
+    DBUG_ASSERT(list.empty());
+  }
+}
+#endif // HAVE_REPLICATION
+
 /**
   This function is called once after each statement.
 
@@ -2265,12 +2339,20 @@ int binlog_commit(THD *thd, bool all, bool ro_1pc)
   PSI_stage_info org_stage;
   DBUG_ENTER("binlog_commit");
 
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  IF_DBUG(bool commit_online= !thd->online_alter_cache_list.empty(),);
+
+  bool is_ending_transaction= ending_trans(thd, all);
+  error= binlog_online_alter_end_trans(thd, all, true);
+  if (error)
+    DBUG_RETURN(error);
 
+  binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr();
+  /*
+    cache_mngr can be NULL in case if binlog logging is disabled.
+  */
   if (!cache_mngr)
   {
-    DBUG_ASSERT(WSREP(thd) ||
+    DBUG_ASSERT(WSREP(thd) || commit_online ||
                 (thd->lex->sql_command != SQLCOM_XA_PREPARE &&
                 !(thd->lex->sql_command == SQLCOM_XA_COMMIT &&
                   thd->lex->xa_opt == XA_ONE_PHASE)));
@@ -2327,7 +2409,7 @@ int binlog_commit(THD *thd, bool all, bool ro_1pc)
      - We are in a transaction and a full transaction is committed.
     Otherwise, we accumulate the changes.
   */
-  if (likely(!error) && ending_trans(thd, all))
+  if (likely(!error) && is_ending_transaction)
   {
     bool is_xa_prepare= is_preparing_xa(thd);
 
@@ -2367,14 +2449,18 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
 {
   DBUG_ENTER("binlog_rollback");
 
+  bool is_ending_trans= ending_trans(thd, all);
+
+  bool rollback_online= !thd->online_alter_cache_list.empty();
+  if (rollback_online)
+    binlog_online_alter_end_trans(thd, all, 0);
   int error= 0;
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr();
 
   if (!cache_mngr)
   {
-    DBUG_ASSERT(WSREP(thd));
-    DBUG_ASSERT(thd->lex->sql_command != SQLCOM_XA_ROLLBACK);
+    DBUG_ASSERT(WSREP(thd) || rollback_online);
+    DBUG_ASSERT(thd->lex->sql_command != SQLCOM_XA_ROLLBACK || rollback_online);
 
     DBUG_RETURN(0);
   }
@@ -2410,7 +2496,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
     thd->reset_binlog_for_next_statement();
     DBUG_RETURN(error);
   }
-  if (!wsrep_emulate_bin_log && mysql_bin_log.check_write_error(thd))
+  if (!wsrep_emulate_bin_log && Event_log::check_write_error(thd))
   {
     /*
       "all == true" means that a "rollback statement" triggered the error and
@@ -2427,7 +2513,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
   }
   else if (likely(!error))
   {  
-    if (ending_trans(thd, all) && trans_cannot_safely_rollback(thd, all))
+    if (is_ending_trans && trans_cannot_safely_rollback(thd, all))
       error= binlog_rollback_flush_trx_cache(thd, all, cache_mngr);
     /*
       Truncate the cache if:
@@ -2439,7 +2525,7 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
         . the format is not MIXED or no temporary non-trans table
           was updated.
     */
-    else if (ending_trans(thd, all) ||
+    else if (is_ending_trans ||
              (!(thd->transaction->stmt.has_created_dropped_temp_table() &&
                 !thd->is_current_stmt_binlog_format_row()) &&
               (!stmt_has_updated_non_trans_table(thd) ||
@@ -2462,19 +2548,20 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all)
 
 void binlog_reset_cache(THD *thd)
 {
-  binlog_cache_mngr *const cache_mngr= opt_bin_log ? 
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton) : 0;
+  binlog_cache_mngr *const cache_mngr= opt_bin_log ?
+                                       thd->binlog_get_cache_mngr() : 0;
   DBUG_ENTER("binlog_reset_cache");
   if (cache_mngr)
   {
-    thd->binlog_remove_pending_rows_event(TRUE, TRUE);
+    MYSQL_BIN_LOG::remove_pending_rows_event(thd, &cache_mngr->trx_cache);
+    thd->reset_binlog_for_next_statement();
     cache_mngr->reset(true, true);
   }
   DBUG_VOID_RETURN;
 }
 
 
-void MYSQL_BIN_LOG::set_write_error(THD *thd, bool is_transactional)
+void Event_log::set_write_error(THD *thd, bool is_transactional)
 {
   DBUG_ENTER("MYSQL_BIN_LOG::set_write_error");
 
@@ -2514,7 +2601,7 @@ void MYSQL_BIN_LOG::set_write_error(THD *thd, bool is_transactional)
   DBUG_VOID_RETURN;
 }
 
-bool MYSQL_BIN_LOG::check_write_error(THD *thd)
+bool Event_log::check_write_error(THD *thd)
 {
   DBUG_ENTER("MYSQL_BIN_LOG::check_write_error");
 
@@ -2566,6 +2653,9 @@ static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv)
   int error= 1;
   DBUG_ENTER("binlog_savepoint_set");
 
+  if (!mysql_bin_log.is_open() && !thd->online_alter_cache_list.empty())
+    DBUG_RETURN(0);
+
   char buf[1024];
 
   String log_query(buf, sizeof(buf), &my_charset_bin);
@@ -2598,22 +2688,20 @@ static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv)
 {
   DBUG_ENTER("binlog_savepoint_rollback");
 
+  if (!mysql_bin_log.is_open() && !thd->online_alter_cache_list.empty())
+    DBUG_RETURN(0);
+
   /*
     Write ROLLBACK TO SAVEPOINT to the binlog cache if we have updated some
     non-transactional table. Otherwise, truncate the binlog cache starting
     from the SAVEPOINT command.
-  */
-#ifdef WITH_WSREP
-  /* for streaming replication, we  must replicate savepoint rollback so that 
-     slaves can maintain SR transactions
+
+    For streaming replication, we  must replicate savepoint rollback so that
+    slaves can maintain SR transactions
    */
-  if (unlikely(thd->wsrep_trx().is_streaming() ||
-               (trans_has_updated_non_trans_table(thd)) ||
-               (thd->variables.option_bits & OPTION_BINLOG_THIS_TRX)))
-#else
-  if (unlikely(trans_has_updated_non_trans_table(thd) ||
-               (thd->variables.option_bits & OPTION_BINLOG_THIS_TRX)))
-#endif /* WITH_WSREP */
+  if (IF_WSREP(thd->wsrep_trx().is_streaming(),0) ||
+               trans_has_updated_non_trans_table(thd) ||
+               (thd->variables.option_bits & OPTION_BINLOG_THIS_TRX))
   {
     char buf[1024];
     String log_query(buf, sizeof(buf), &my_charset_bin);
@@ -3072,13 +3160,14 @@ void MYSQL_LOG::close(uint exiting)
   {
     end_io_cache(&log_file);
 
-    if (log_type == LOG_BIN && mysql_file_sync(log_file.file, MYF(MY_WME)) && ! write_error)
+    if (log_type == LOG_BIN && log_file.file >= 0 &&
+        mysql_file_sync(log_file.file, MYF(MY_WME)) && ! write_error)
     {
       write_error= 1;
       sql_print_error(ER_DEFAULT(ER_ERROR_ON_WRITE), name, errno);
     }
 
-    if (!(exiting & LOG_CLOSE_DELAYED_CLOSE) &&
+    if (!(exiting & LOG_CLOSE_DELAYED_CLOSE) && log_file.file >= 0 &&
         mysql_file_close(log_file.file, MYF(MY_WME)) && ! write_error)
     {
       write_error= 1;
@@ -3575,7 +3664,7 @@ MYSQL_BIN_LOG::MYSQL_BIN_LOG(uint *sync_period)
     We don't want to initialize locks here as such initialization depends on
     safe_mutex (when using safe_mutex) which depends on MY_INIT(), which is
     called only in main(). Doing initialization here would make it happen
-    before main().
+    before main(). init_pthread_objects() can be called for that purpose.
   */
   index_file_name[0] = 0;
   bzero((char*) &index_file, sizeof(index_file));
@@ -3667,7 +3756,7 @@ void MYSQL_BIN_LOG::init(ulong max_size_arg)
 
 void MYSQL_BIN_LOG::init_pthread_objects()
 {
-  MYSQL_LOG::init_pthread_objects();
+  Event_log::init_pthread_objects();
   mysql_mutex_init(m_key_LOCK_index, &LOCK_index, MY_MUTEX_INIT_SLOW);
   mysql_mutex_setflags(&LOCK_index, MYF_NO_DEADLOCK_DETECTION);
   mysql_mutex_init(key_BINLOG_LOCK_xid_list,
@@ -3683,9 +3772,6 @@ void MYSQL_BIN_LOG::init_pthread_objects()
                   &COND_binlog_background_thread, 0);
   mysql_cond_init(key_BINLOG_COND_binlog_background_thread_end,
                   &COND_binlog_background_thread_end, 0);
-
-  mysql_mutex_init(m_key_LOCK_binlog_end_pos, &LOCK_binlog_end_pos,
-                   MY_MUTEX_INIT_SLOW);
 }
 
 
@@ -3756,6 +3842,74 @@ bool MYSQL_BIN_LOG::open_index_file(const char *index_file_name_arg,
 }
 
 
+bool Event_log::open(enum cache_type io_cache_type_arg)
+{
+  bool error= init_io_cache(&log_file, -1, LOG_BIN_IO_SIZE, io_cache_type_arg,
+                            0, 0, MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL));
+
+  log_state= LOG_OPENED;
+  inited= true;
+  if (error)
+    return error;
+
+  longlong bytes_written= write_description_event(BINLOG_CHECKSUM_ALG_OFF,
+                                                  false, true, false);
+  status_var_add(current_thd->status_var.binlog_bytes_written, bytes_written);
+  return bytes_written < 0;
+}
+
+longlong
+Event_log::write_description_event(enum_binlog_checksum_alg checksum_alg,
+                                   bool encrypt, bool dont_set_created,
+                                   bool is_relay_log)
+{
+  Format_description_log_event s(BINLOG_VERSION);
+  /*
+    don't set LOG_EVENT_BINLOG_IN_USE_F for SEQ_READ_APPEND io_cache
+    as we won't be able to reset it later
+  */
+  if (io_cache_type == WRITE_CACHE)
+    s.flags |= LOG_EVENT_BINLOG_IN_USE_F;
+  s.checksum_alg= checksum_alg;
+  if (is_relay_log)
+    s.set_relay_log_event();
+
+  crypto.scheme = 0;
+  DBUG_ASSERT(s.checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF);
+  if (!s.is_valid())
+    return -1;
+  s.dont_set_created= dont_set_created;
+  if (write_event(&s, 0, &log_file))
+    return -1;
+
+  if (encrypt)
+  {
+    uint key_version= encryption_key_get_latest_version(ENCRYPTION_KEY_SYSTEM_DATA);
+    if (key_version == ENCRYPTION_KEY_VERSION_INVALID)
+    {
+      sql_print_error("Failed to enable encryption of binary logs");
+      return -1;
+    }
+
+    if (key_version != ENCRYPTION_KEY_NOT_ENCRYPTED)
+    {
+      if (my_random_bytes(crypto.nonce, sizeof(crypto.nonce)))
+        return -1;
+
+      Start_encryption_log_event sele(1, key_version, crypto.nonce);
+      sele.checksum_alg= s.checksum_alg;
+      if (write_event(&sele, 0, &log_file))
+        return -1;
+
+      // Start_encryption_log_event is written, enable the encryption
+      if (crypto.init(sele.crypto_scheme, key_version))
+        return -1;
+    }
+  }
+  return (longlong)s.data_written;
+}
+
+
 /**
   Open a (new) binlog file.
 
@@ -3882,17 +4036,7 @@ bool MYSQL_BIN_LOG::open(const char *log_name,
     }
 
     {
-      /*
-        In 4.x we put Start event only in the first binlog. But from 5.0 we
-        want a Start event even if this is not the very first binlog.
-      */
-      Format_description_log_event s(BINLOG_VERSION);
-      /*
-        don't set LOG_EVENT_BINLOG_IN_USE_F for SEQ_READ_APPEND io_cache
-        as we won't be able to reset it later
-      */
-      if (io_cache_type == WRITE_CACHE)
-        s.flags |= LOG_EVENT_BINLOG_IN_USE_F;
+      enum_binlog_checksum_alg alg;
 
       if (is_relay_log)
       {
@@ -3900,45 +4044,16 @@ bool MYSQL_BIN_LOG::open(const char *log_name,
           relay_log_checksum_alg=
             opt_slave_sql_verify_checksum ? (enum_binlog_checksum_alg) binlog_checksum_options
                                           : BINLOG_CHECKSUM_ALG_OFF;
-        s.checksum_alg= relay_log_checksum_alg;
-        s.set_relay_log_event();
+        alg= relay_log_checksum_alg;
       }
       else
-        s.checksum_alg= (enum_binlog_checksum_alg)binlog_checksum_options;
+        alg= (enum_binlog_checksum_alg)binlog_checksum_options;
 
-      crypto.scheme = 0;
-      DBUG_ASSERT(s.checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF);
-      if (!s.is_valid())
+      longlong written= write_description_event(alg, encrypt_binlog,
+                                                null_created_arg, is_relay_log);
+      if (written == -1)
         goto err;
-      s.dont_set_created= null_created_arg;
-      if (write_event(&s))
-        goto err;
-      bytes_written+= s.data_written;
-
-      if (encrypt_binlog)
-      {
-        uint key_version= encryption_key_get_latest_version(ENCRYPTION_KEY_SYSTEM_DATA);
-        if (key_version == ENCRYPTION_KEY_VERSION_INVALID)
-        {
-          sql_print_error("Failed to enable encryption of binary logs");
-          goto err;
-        }
-
-        if (key_version != ENCRYPTION_KEY_NOT_ENCRYPTED)
-        {
-          if (my_random_bytes(crypto.nonce, sizeof(crypto.nonce)))
-            goto err;
-
-          Start_encryption_log_event sele(1, key_version, crypto.nonce);
-          sele.checksum_alg= s.checksum_alg;
-          if (write_event(&sele))
-            goto err;
-
-          // Start_encryption_log_event is written, enable the encryption
-          if (crypto.init(sele.crypto_scheme, key_version))
-            goto err;
-        }
-      }
+      bytes_written+= written;
 
       if (!is_relay_log)
       {
@@ -4308,7 +4423,8 @@ int MYSQL_BIN_LOG::find_log_pos(LOG_INFO *linfo, const char *log_name,
                        log_name ? log_name : "NULL", full_log_name));
 
   /* As the file is flushed, we can't get an error here */
-  (void) reinit_io_cache(&index_file, READ_CACHE, (my_off_t) 0, 0, 0);
+  error= reinit_io_cache(&index_file, READ_CACHE, (my_off_t) 0, 0, 0);
+  DBUG_ASSERT(!error);
 
   for (;;)
   {
@@ -5627,7 +5743,7 @@ int MYSQL_BIN_LOG::new_file_impl()
   DBUG_RETURN(error);
 }
 
-bool MYSQL_BIN_LOG::write_event(Log_event *ev, binlog_cache_data *cache_data,
+bool Event_log::write_event(Log_event *ev, binlog_cache_data *cache_data,
                                 IO_CACHE *file)
 {
   Log_event_writer writer(file, 0, &crypto);
@@ -5791,8 +5907,7 @@ bool MYSQL_BIN_LOG::is_query_in_union(THD *thd, query_id_t query_id_param)
 bool
 trans_has_updated_trans_table(const THD* thd)
 {
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr();
 
   return (cache_mngr ? !cache_mngr->trx_cache.empty() : 0);
 }
@@ -5841,8 +5956,7 @@ bool use_trans_cache(const THD* thd, bool is_transactional)
 {
   if (is_transactional)
     return 1;
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  auto *const cache_mngr= thd->binlog_get_cache_mngr();
 
   return ((thd->is_current_stmt_binlog_format_row() ||
            thd->variables.binlog_direct_non_trans_update) ? 0 :
@@ -5913,17 +6027,11 @@ bool stmt_has_updated_non_trans_table(const THD* thd)
   binlog_hton, which has internal linkage.
 */
 
-binlog_cache_mngr *THD::binlog_setup_trx_data()
+binlog_cache_mngr *binlog_setup_cache_mngr()
 {
-  DBUG_ENTER("THD::binlog_setup_trx_data");
-  binlog_cache_mngr *cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
-
-  if (cache_mngr)
-    DBUG_RETURN(cache_mngr);                             // Already set up
-
-  cache_mngr= (binlog_cache_mngr*) my_malloc(key_memory_binlog_cache_mngr,
-                                  sizeof(binlog_cache_mngr), MYF(MY_ZEROFILL));
+  auto *cache_mngr= (binlog_cache_mngr*) my_malloc(key_memory_binlog_cache_mngr,
+                                  sizeof(binlog_cache_mngr),
+                                                   MYF(MY_ZEROFILL));
   if (!cache_mngr ||
       open_cached_file(&cache_mngr->stmt_cache.cache_log, mysql_tmpdir,
                        LOG_PREFIX, (size_t)binlog_stmt_cache_size, MYF(MY_WME)) ||
@@ -5931,17 +6039,33 @@ binlog_cache_mngr *THD::binlog_setup_trx_data()
                        LOG_PREFIX, (size_t)binlog_cache_size, MYF(MY_WME)))
   {
     my_free(cache_mngr);
-    DBUG_RETURN(0);                      // Didn't manage to set it up
+    return NULL;
   }
-  thd_set_ha_data(this, binlog_hton, cache_mngr);
 
   cache_mngr= new (cache_mngr)
-              binlog_cache_mngr(max_binlog_stmt_cache_size,
-                                max_binlog_cache_size,
-                                &binlog_stmt_cache_use,
-                                &binlog_stmt_cache_disk_use,
-                                &binlog_cache_use,
-                                &binlog_cache_disk_use);
+          binlog_cache_mngr(max_binlog_stmt_cache_size,
+                            max_binlog_cache_size,
+                            &binlog_stmt_cache_use,
+                            &binlog_stmt_cache_disk_use,
+                            &binlog_cache_use,
+                            &binlog_cache_disk_use);
+
+  return cache_mngr;
+}
+
+binlog_cache_mngr *THD::binlog_setup_trx_data()
+{
+  DBUG_ENTER("THD::binlog_setup_trx_data");
+  binlog_cache_mngr *cache_mngr=
+    (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+
+  if (!cache_mngr)
+  {
+    cache_mngr= binlog_setup_cache_mngr();
+    thd_set_ha_data(this, binlog_hton, cache_mngr);
+  }
+
+
   DBUG_RETURN(cache_mngr);
 }
 
@@ -6008,7 +6132,7 @@ void THD::set_binlog_start_alter_seq_no(uint64 s_no)
 void
 THD::binlog_start_trans_and_stmt()
 {
-  binlog_cache_mngr *cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+  binlog_cache_mngr *cache_mngr= binlog_get_cache_mngr();
   DBUG_ENTER("binlog_start_trans_and_stmt");
   DBUG_PRINT("enter", ("cache_mngr: %p  cache_mngr->trx_cache.get_prev_position(): %lu",
                        cache_mngr,
@@ -6095,8 +6219,7 @@ THD::binlog_start_trans_and_stmt()
 }
 
 void THD::binlog_set_stmt_begin() {
-  binlog_cache_mngr *cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+  binlog_cache_mngr *cache_mngr= binlog_get_cache_mngr();
 
   /*
     The call to binlog_trans_log_savepos() might create the cache_mngr
@@ -6106,7 +6229,7 @@ void THD::binlog_set_stmt_begin() {
   */
   my_off_t pos= 0;
   binlog_trans_log_savepos(this, &pos);
-  cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+  cache_mngr= binlog_get_cache_mngr();
   cache_mngr->trx_cache.set_prev_position(pos);
 }
 
@@ -6213,7 +6336,7 @@ bool THD::binlog_write_table_maps()
       }
       if (table->file->row_logging)
       {
-        if (binlog_write_table_map(table, with_annotate))
+        if (mysql_bin_log.write_table_map(this, table, with_annotate))
           DBUG_RETURN(1);
         with_annotate= 0;
       }
@@ -6246,7 +6369,7 @@ bool THD::binlog_write_table_maps()
     nonzero if an error pops up when writing the table map event.
 */
 
-bool THD::binlog_write_table_map(TABLE *table, bool with_annotate)
+bool MYSQL_BIN_LOG::write_table_map(THD *thd, TABLE *table, bool with_annotate)
 {
   int error= 1;
   bool is_transactional= table->file->row_logging_has_trans;
@@ -6259,21 +6382,20 @@ bool THD::binlog_write_table_map(TABLE *table, bool with_annotate)
   DBUG_ASSERT(table->s->table_map_id != ULONG_MAX);
 
   /* Ensure that all events in a GTID group are in the same cache */
-  if (variables.option_bits & OPTION_GTID_BEGIN)
+  if (thd->variables.option_bits & OPTION_GTID_BEGIN)
     is_transactional= 1;
 
   Table_map_log_event
-    the_event(this, table, table->s->table_map_id, is_transactional);
+    the_event(thd, table, table->s->table_map_id, is_transactional);
 
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+  binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr();
   binlog_cache_data *cache_data= (cache_mngr->
                                   get_binlog_cache_data(is_transactional));
   IO_CACHE *file= &cache_data->cache_log;
   Log_event_writer writer(file, cache_data);
 
   if (with_annotate)
-    if (binlog_write_annotated_row(&writer))
+    if (thd->binlog_write_annotated_row(&writer))
       goto write_err;
 
   DBUG_EXECUTE_IF("table_map_write_error",
@@ -6291,77 +6413,124 @@ bool THD::binlog_write_table_map(TABLE *table, bool with_annotate)
   DBUG_RETURN(0);
 
 write_err:
-  mysql_bin_log.set_write_error(this, is_transactional);
+  set_write_error(thd, is_transactional);
   /*
     For non-transactional engine or multi statement transaction with mixed
     engines, data is written to table but writing to binary log failed. In
     these scenarios rollback is not possible. Hence report an incident.
   */
-  if (mysql_bin_log.check_write_error(this) && cache_data &&
-      lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) &&
+  if (check_write_error(thd) && cache_data &&
+      thd->lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) &&
       table->current_lock == F_WRLCK)
     cache_data->set_incident();
   DBUG_RETURN(error);
 }
 
 
+#ifdef HAVE_REPLICATION
+static online_alter_cache_data *
+online_alter_binlog_setup_cache_data(MEM_ROOT *root, TABLE_SHARE *share)
+{
+  static ulong online_alter_cache_use= 0, online_alter_cache_disk_use= 0;
+
+  auto cache= new (root) online_alter_cache_data();
+  if (!cache || open_cached_file(&cache->cache_log, mysql_tmpdir,
+                         LOG_PREFIX, (size_t)binlog_cache_size, MYF(MY_WME)))
+  {
+    delete cache;
+    return NULL;
+  }
+
+  share->online_alter_binlog->acquire();
+  cache->hton= share->db_type();
+  cache->sink_log= share->online_alter_binlog;
+
+  my_off_t binlog_max_size= SIZE_T_MAX; // maximum possible cache size
+  DBUG_EXECUTE_IF("online_alter_small_cache", binlog_max_size= 4096;);
+
+  cache->set_binlog_cache_info(binlog_max_size,
+                               &online_alter_cache_use,
+                               &online_alter_cache_disk_use);
+  cache->store_prev_position();
+  return cache;
+}
+
+
+online_alter_cache_data *online_alter_binlog_get_cache_data(THD *thd, TABLE *table)
+{
+  ilist<online_alter_cache_data> &list= thd->online_alter_cache_list;
+
+  /* we assume it's very rare to have more than one online ALTER running */
+  for (auto &cache: list)
+  {
+    if (cache.sink_log == table->s->online_alter_binlog)
+      return &cache;
+  }
+
+  MEM_ROOT *root= &thd->transaction->mem_root;
+  auto *new_cache_data= online_alter_binlog_setup_cache_data(root, table->s);
+  list.push_back(*new_cache_data);
+
+  return new_cache_data;
+}
+#endif
+
+binlog_cache_mngr *THD::binlog_get_cache_mngr() const
+{
+  return (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
+}
+
+
 /**
   This function retrieves a pending row event from a cache which is
   specified through the parameter @c is_transactional. Respectively, when it
   is @c true, the pending event is returned from the transactional cache.
   Otherwise from the non-transactional cache.
 
-  @param is_transactional  @c true indicates a transactional cache,
+  @param cache_mngr        cache manager to return pending row from
+  @param use_trans_cache   @c true indicates a transactional cache,
                            otherwise @c false a non-transactional.
   @return
     The row event if any. 
 */
-Rows_log_event*
-THD::binlog_get_pending_rows_event(bool is_transactional) const
+Rows_log_event* binlog_get_pending_rows_event(binlog_cache_mngr *cache_mngr,
+                                              bool use_trans_cache)
 {
-  Rows_log_event* rows= NULL;
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(this, binlog_hton);
-
-  /*
-    This is less than ideal, but here's the story: If there is no cache_mngr,
-    prepare_pending_rows_event() has never been called (since the cache_mngr
-    is set up there). In that case, we just return NULL.
-   */
-  if (cache_mngr)
-  {
-    binlog_cache_data *cache_data=
-      cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional));
-
-    rows= cache_data->pending();
-  }
-  return (rows);
+  DBUG_ASSERT(cache_mngr);
+  return cache_mngr->get_binlog_cache_data(use_trans_cache)->pending();
 }
 
-/**
-  This function stores a pending row event into a cache which is specified
-  through the parameter @c is_transactional. Respectively, when it is @c
-  true, the pending event is stored into the transactional cache. Otherwise
-  into the non-transactional cache.
-
-  @param evt               a pointer to the row event.
-  @param is_transactional  @c true indicates a transactional cache,
-                           otherwise @c false a non-transactional.
-*/
-void
-THD::binlog_set_pending_rows_event(Rows_log_event* ev, bool is_transactional)
+binlog_cache_data* binlog_get_cache_data(binlog_cache_mngr *cache_mngr,
+                                         bool use_trans_cache)
 {
-  binlog_cache_mngr *const cache_mngr= binlog_setup_trx_data();
-
-  DBUG_ASSERT(cache_mngr);
+  return cache_mngr->get_binlog_cache_data(use_trans_cache);
+}
 
-  binlog_cache_data *cache_data=
-    cache_mngr->get_binlog_cache_data(use_trans_cache(this, is_transactional));
+int binlog_flush_pending_rows_event(THD *thd, bool stmt_end,
+                                    bool is_transactional,
+                                    Event_log *bin_log,
+                                    binlog_cache_data *cache_data)
+{
+  int error= 0;
+  auto *pending= cache_data->pending();
+  if (pending)
+  {
+    /*
+      Mark the event as the last event of a statement if the stmt_end
+      flag is set.
+    */
+    if (stmt_end)
+    {
+      pending->set_flags(Rows_log_event::STMT_END_F);
+      thd->reset_binlog_for_next_statement();
+    }
 
-  cache_data->set_pending(ev);
+    error= bin_log->flush_and_set_pending_rows_event(thd, 0, cache_data,
+                                                     is_transactional);
+  }
+  return error;
 }
 
-
 /**
   This function removes the pending rows event, discarding any outstanding
   rows. If there is no pending rows event available, this is effectively a
@@ -6372,18 +6541,10 @@ THD::binlog_set_pending_rows_event(Rows_log_event* ev, bool is_transactional)
                            otherwise @c false a non-transactional.
 */
 int
-MYSQL_BIN_LOG::remove_pending_rows_event(THD *thd, bool is_transactional)
+MYSQL_BIN_LOG::remove_pending_rows_event(THD *thd, binlog_cache_data *cache_data)
 {
   DBUG_ENTER("MYSQL_BIN_LOG::remove_pending_rows_event");
 
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
-
-  DBUG_ASSERT(cache_mngr);
-
-  binlog_cache_data *cache_data=
-    cache_mngr->get_binlog_cache_data(use_trans_cache(thd, is_transactional));
-
   if (Rows_log_event* pending= cache_data->pending())
   {
     delete pending;
@@ -6397,6 +6558,7 @@ MYSQL_BIN_LOG::remove_pending_rows_event(THD *thd, bool is_transactional)
   Moves the last bunch of rows from the pending Rows event to a cache (either
   transactional cache if is_transaction is @c true, or the non-transactional
   cache otherwise. Sets a new pending event.
+  In case of error during flushing, sets write_error=1 to itself.
 
   @param thd               a pointer to the user thread.
   @param evt               a pointer to the row event.
@@ -6404,22 +6566,14 @@ MYSQL_BIN_LOG::remove_pending_rows_event(THD *thd, bool is_transactional)
                            otherwise @c false a non-transactional.
 */
 int
-MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd,
-                                                Rows_log_event* event,
-                                                bool is_transactional)
+Event_log::flush_and_set_pending_rows_event(THD *thd, Rows_log_event* event,
+                                            binlog_cache_data *cache_data,
+                                            bool is_transactional)
 {
   DBUG_ENTER("MYSQL_BIN_LOG::flush_and_set_pending_rows_event(event)");
-  DBUG_ASSERT(WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open());
+  DBUG_ASSERT(WSREP_EMULATE_BINLOG(thd) || is_open());
   DBUG_PRINT("enter", ("event: %p", event));
 
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
-
-  DBUG_ASSERT(cache_mngr);
-
-  binlog_cache_data *cache_data=
-    cache_mngr->get_binlog_cache_data(use_trans_cache(thd, is_transactional));
-
   DBUG_PRINT("info", ("cache_mngr->pending(): %p", cache_data->pending()));
 
   if (Rows_log_event* pending= cache_data->pending())
@@ -6447,11 +6601,89 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd,
     delete pending;
   }
 
-  thd->binlog_set_pending_rows_event(event, is_transactional);
+  cache_data->set_pending(event);
 
   DBUG_RETURN(0);
 }
 
+/*
+  Member function for ensuring that there is an rows log
+  event of the apropriate type before proceeding.
+
+  POST CONDITION:
+    If a non-NULL pointer is returned, the pending event for thread 'thd' will
+    be an event created by callback hold by event_factory, and
+    will be either empty or have enough space to hold 'needed' bytes.
+    In addition, the columns bitmap will be correct for the row, meaning that
+    the pending event will be flushed if the columns in the event differ from
+    the columns suppled to the function.
+
+  RETURNS
+    If no error, a non-NULL pending event (either one which already existed or
+    the newly created one).
+    If error, NULL.
+ */
+
+Rows_log_event*
+Event_log::prepare_pending_rows_event(THD *thd, TABLE* table,
+                                      binlog_cache_data *cache_data,
+                                      uint32 serv_id, size_t needed,
+                                      bool is_transactional,
+                                      Rows_event_factory event_factory)
+{
+  DBUG_ENTER("MYSQL_BIN_LOG::prepare_pending_rows_event");
+  /* Pre-conditions */
+  DBUG_ASSERT(table->s->table_map_id != ~0UL);
+
+  /*
+    There is no good place to set up the transactional data, so we
+    have to do it here.
+  */
+  Rows_log_event* pending= cache_data->pending();
+
+  if (unlikely(pending && !pending->is_valid()))
+    DBUG_RETURN(NULL);
+
+  /*
+    Check if the current event is non-NULL and a write-rows
+    event. Also check if the table provided is mapped: if it is not,
+    then we have switched to writing to a new table.
+    If there is no pending event, we need to create one. If there is a pending
+    event, but it's not about the same table id, or not of the same type
+    (between Write, Update and Delete), or not the same affected columns, or
+    going to be too big, flush this event to disk and create a new pending
+    event.
+  */
+  if (!pending ||
+      pending->server_id != serv_id ||
+      pending->get_table_id() != table->s->table_map_id ||
+      pending->get_general_type_code() != event_factory.type_code ||
+      pending->get_data_size() + needed > opt_binlog_rows_event_max_size ||
+      pending->read_write_bitmaps_cmp(table) == FALSE)
+  {
+    /* Create a new RowsEventT... */
+    Rows_log_event* const
+            ev= event_factory.create(thd, table, table->s->table_map_id,
+                                     is_transactional);
+    if (unlikely(!ev))
+      DBUG_RETURN(NULL);
+    ev->server_id= serv_id; // I don't like this, it's too easy to forget.
+    /*
+      flush the pending event and replace it with the newly created
+      event...
+    */
+    if (unlikely(flush_and_set_pending_rows_event(thd, ev, cache_data,
+                                                  is_transactional)))
+    {
+      delete ev;
+      DBUG_RETURN(NULL);
+    }
+
+    DBUG_RETURN(ev);               /* This is the new pending event */
+  }
+  DBUG_RETURN(pending);        /* This is the current pending event */
+}
+
 
 /* Generate a new global transaction ID, and write it to the binlog */
 
@@ -7499,6 +7731,151 @@ class CacheWriter: public Log_event_writer
   bool first;
 };
 
+static int binlog_online_alter_end_trans(THD *thd, bool all, bool commit)
+{
+  DBUG_ENTER("binlog_online_alter_end_trans");
+  int error= 0;
+#ifdef HAVE_REPLICATION
+  if (thd->online_alter_cache_list.empty())
+    DBUG_RETURN(0);
+
+  bool is_ending_transaction= ending_trans(thd, all);
+
+  for (auto &cache: thd->online_alter_cache_list)
+  {
+    auto *binlog= cache.sink_log;
+    DBUG_ASSERT(binlog);
+    bool non_trans= cache.hton->flags & HTON_NO_ROLLBACK // Aria
+                    || !cache.hton->rollback;
+    bool do_commit= (commit && is_ending_transaction) || non_trans;
+
+    if (commit || non_trans)
+    {
+      // Do not set STMT_END for last event to leave table open in altering thd
+      error= binlog_flush_pending_rows_event(thd, false, true, binlog, &cache);
+    }
+
+    if (do_commit)
+    {
+      /*
+        If the cache wasn't reinited to write, then it remains empty after
+        the last write.
+      */
+      if (my_b_bytes_in_cache(&cache.cache_log) && likely(!error))
+      {
+        DBUG_ASSERT(cache.cache_log.type != READ_CACHE);
+        mysql_mutex_lock(binlog->get_log_lock());
+        error= binlog->write_cache_raw(thd, &cache.cache_log);
+        mysql_mutex_unlock(binlog->get_log_lock());
+      }
+    }
+    else if (!commit) // rollback
+    {
+      DBUG_ASSERT(!non_trans);
+      cache.restore_prev_position();
+    }
+    else
+    {
+      DBUG_ASSERT(!is_ending_transaction);
+      cache.store_prev_position();
+    }
+
+
+    if (error)
+    {
+      my_error(ER_ERROR_ON_WRITE, MYF(ME_ERROR_LOG),
+               binlog->get_name(), errno);
+      binlog_online_alter_cleanup(thd->online_alter_cache_list,
+                                  is_ending_transaction);
+      DBUG_RETURN(error);
+    }
+  }
+
+  binlog_online_alter_cleanup(thd->online_alter_cache_list,
+                              is_ending_transaction);
+
+  for (TABLE *table= thd->open_tables; table; table= table->next)
+    table->online_alter_cache= NULL;
+#endif // HAVE_REPLICATION
+  DBUG_RETURN(error);
+}
+
+SAVEPOINT** find_savepoint_in_list(THD *thd, LEX_CSTRING name,
+                                   SAVEPOINT ** const list);
+
+SAVEPOINT* savepoint_add(THD *thd, LEX_CSTRING name, SAVEPOINT **list,
+                         int (*release_old)(THD*, SAVEPOINT*));
+
+int online_alter_savepoint_set(THD *thd, LEX_CSTRING name)
+{
+
+  DBUG_ENTER("binlog_online_alter_savepoint");
+#ifdef HAVE_REPLICATION
+  if (thd->online_alter_cache_list.empty())
+    DBUG_RETURN(0);
+
+  if (savepoint_alloc_size < sizeof (SAVEPOINT) + sizeof(my_off_t))
+    savepoint_alloc_size= sizeof (SAVEPOINT) + sizeof(my_off_t);
+
+  for (auto &cache: thd->online_alter_cache_list)
+  {
+    if (cache.hton->savepoint_set == NULL)
+      continue;
+
+    SAVEPOINT *sv= savepoint_add(thd, name, &cache.sv_list, NULL);
+    if(unlikely(sv == NULL))
+      DBUG_RETURN(1);
+    my_off_t *pos= (my_off_t*)(sv+1);
+    *pos= cache.get_byte_position();
+
+    sv->prev= cache.sv_list;
+    cache.sv_list= sv;
+  }
+#endif
+  DBUG_RETURN(0);
+}
+
+int online_alter_savepoint_rollback(THD *thd, LEX_CSTRING name)
+{
+  DBUG_ENTER("online_alter_savepoint_rollback");
+#ifdef HAVE_REPLICATION
+  for (auto &cache: thd->online_alter_cache_list)
+  {
+    if (cache.hton->savepoint_set == NULL)
+      continue;
+
+    SAVEPOINT **sv= find_savepoint_in_list(thd, name, &cache.sv_list);
+    // sv is null if savepoint was set up before online table was modified
+    my_off_t pos= *sv ? *(my_off_t*)(*sv+1) : 0;
+
+    cache.restore_savepoint(pos);
+  }
+
+#endif
+  DBUG_RETURN(0);
+}
+
+int Event_log::write_cache_raw(THD *thd, IO_CACHE *cache)
+{
+  DBUG_ENTER("Event_log::write_cache_raw");
+  mysql_mutex_assert_owner(&LOCK_log);
+  if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0))
+    DBUG_RETURN(ER_ERROR_ON_WRITE);
+
+  IO_CACHE *file= get_log_file();
+  IF_DBUG(size_t total= cache->end_of_file,);
+  do
+  {
+    size_t read_len= cache->read_end - cache->read_pos;
+    int res= my_b_safe_write(file, cache->read_pos, read_len);
+    if (unlikely(res))
+      DBUG_RETURN(res);
+    IF_DBUG(total-= read_len,);
+  } while (my_b_fill(cache));
+  DBUG_ASSERT(total == 0);
+  DBUG_RETURN(0);
+}
+
 /*
   Write the contents of a cache to the binary log.
 
@@ -7516,9 +7893,9 @@ class CacheWriter: public Log_event_writer
     events prior to fill in the binlog cache.
 */
 
-int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache)
+int Event_log::write_cache(THD *thd, IO_CACHE *cache)
 {
-  DBUG_ENTER("MYSQL_BIN_LOG::write_cache");
+  DBUG_ENTER("Event_log::write_cache");
 
   mysql_mutex_assert_owner(&LOCK_log);
   if (reinit_io_cache(cache, READ_CACHE, 0, 0, 0))
@@ -7527,7 +7904,7 @@ int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache)
   size_t val;
   size_t end_log_pos_inc= 0; // each event processed adds BINLOG_CHECKSUM_LEN 2 t
   uchar header[LOG_EVENT_HEADER_LEN];
-  CacheWriter writer(thd, &log_file, binlog_checksum_options, &crypto);
+  CacheWriter writer(thd, get_log_file(), binlog_checksum_options, &crypto);
 
   if (crypto.scheme)
   {
@@ -7552,7 +7929,7 @@ int MYSQL_BIN_LOG::write_cache(THD *thd, IO_CACHE *cache)
     split.
   */
 
-  group= (size_t)my_b_tell(&log_file);
+  group= (size_t)my_b_tell(get_log_file());
   hdr_offs= carry= 0;
 
   do
@@ -11817,7 +12194,7 @@ mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file)
 {
   binlog_cache_mngr *cache_mngr;
   if (opt_bin_log &&
-      (cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton)))
+      (cache_mngr= thd->binlog_get_cache_mngr()))
   {
     *out_file= cache_mngr->last_commit_pos_file;
     *out_pos= (ulonglong)(cache_mngr->last_commit_pos_offset);
@@ -11930,7 +12307,7 @@ TC_LOG_BINLOG::set_status_variables(THD *thd)
   binlog_cache_mngr *cache_mngr;
 
   if (thd && opt_bin_log)
-    cache_mngr= (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+    cache_mngr= thd->binlog_get_cache_mngr();
   else
     cache_mngr= 0;
 
@@ -12055,8 +12432,7 @@ maria_declare_plugin_end;
 IO_CACHE *wsrep_get_cache(THD * thd, bool is_transactional)
 {
   DBUG_ASSERT(binlog_hton->slot != HA_SLOT_UNDEF);
-  binlog_cache_mngr *cache_mngr = (binlog_cache_mngr*)
-    thd_get_ha_data(thd, binlog_hton);
+  binlog_cache_mngr *cache_mngr = thd->binlog_get_cache_mngr();
   if (cache_mngr)
     return cache_mngr->get_binlog_cache_log(is_transactional);
 
@@ -12072,8 +12448,7 @@ void wsrep_thd_binlog_trx_reset(THD * thd)
   /*
     todo: fix autocommit select to not call the caller
   */
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr();
   if (cache_mngr)
   {
     cache_mngr->reset(false, true);
@@ -12091,11 +12466,11 @@ void wsrep_thd_binlog_stmt_rollback(THD * thd)
 {
   DBUG_ENTER("wsrep_thd_binlog_stmt_rollback");
   WSREP_DEBUG("wsrep_thd_binlog_stmt_rollback");
-  binlog_cache_mngr *const cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr();
   if (cache_mngr)
   {
-    thd->binlog_remove_pending_rows_event(TRUE, TRUE);
+    MYSQL_BIN_LOG::remove_pending_rows_event(thd, &cache_mngr->trx_cache);
+    thd->reset_binlog_for_next_statement();
     cache_mngr->stmt_cache.reset();
   }
   DBUG_VOID_RETURN;
@@ -12117,8 +12492,7 @@ void wsrep_register_binlog_handler(THD *thd, bool trx)
     back a statement or a transaction. However, notifications do not happen
     if the binary log is set as read/write.
   */
-  binlog_cache_mngr *cache_mngr=
-    (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
+  binlog_cache_mngr *cache_mngr= thd->binlog_get_cache_mngr();
   /* cache_mngr may be missing e.g. in mtr test ev51914.test */
   if (cache_mngr)
   {
diff --git a/sql/log.h b/sql/log.h
index c20f0fe5a57..81e5243a555 100644
--- a/sql/log.h
+++ b/sql/log.h
@@ -326,6 +326,7 @@ class MYSQL_LOG
                             bool strip_ext, char *buff);
   virtual int generate_new_name(char *new_name, const char *log_name,
                                 ulong next_log_number);
+  inline mysql_mutex_t* get_log_lock() { return &LOCK_log; }
  protected:
   /* LOCK_log is inited by init_pthread_objects() */
   mysql_mutex_t LOCK_log;
@@ -350,6 +351,174 @@ class MYSQL_LOG
                                   enum cache_type io_cache_type_arg);
 };
 
+/**
+  @struct Rows_event_factory
+
+  Holds an event type code and a callback function to create it.
+  Should be created by Rows_event_factory::get.
+*/
+struct Rows_event_factory
+{
+  int type_code;
+
+  Rows_log_event *(*create)(THD*, TABLE*, ulong, bool is_transactional);
+
+  template<class RowsEventT>
+  static Rows_event_factory get()
+  {
+    return { RowsEventT::TYPE_CODE,
+             [](THD* thd, TABLE* table, ulong flags, bool is_transactional)
+                     -> Rows_log_event*
+             {
+               return new RowsEventT(thd, table, flags, is_transactional);
+             }
+    };
+  }
+};
+
+class Event_log: public MYSQL_LOG
+{
+protected:
+  /* binlog encryption data */
+  struct Binlog_crypt_data crypto;
+
+  mysql_mutex_t LOCK_binlog_end_pos;
+
+  /** The instrumentation key to use for LOCK_binlog_end_pos. */
+  PSI_mutex_key m_key_LOCK_binlog_end_pos;
+  /** The instrumentation key to use for opening the log file. */
+  PSI_file_key m_key_file_log, m_key_file_log_cache;
+public:
+#if !defined(MYSQL_CLIENT)
+  Rows_log_event*
+  prepare_pending_rows_event(THD *thd, TABLE* table,
+                             binlog_cache_data *cache_data,
+                             uint32 serv_id, size_t needed,
+                             bool is_transactional,
+                             Rows_event_factory event_factory);
+#endif
+  int flush_and_set_pending_rows_event(THD *thd, Rows_log_event* event,
+                                       binlog_cache_data *cache_data,
+                                       bool is_transactional);
+  void set_write_error(THD *thd, bool is_transactional);
+  static bool check_write_error(THD *thd);
+  int write_cache(THD *thd, IO_CACHE *cache);
+  int write_cache_raw(THD *thd, IO_CACHE *cache);
+  char* get_name() { return name; }
+  void cleanup()
+  {
+    if (inited)
+      mysql_mutex_destroy(&LOCK_binlog_end_pos);
+
+    MYSQL_LOG::cleanup();
+  }
+  void init_pthread_objects()
+  {
+    MYSQL_LOG::init_pthread_objects();
+
+    mysql_mutex_init(m_key_LOCK_binlog_end_pos, &LOCK_binlog_end_pos,
+                     MY_MUTEX_INIT_SLOW);
+  }
+
+  bool open(enum cache_type io_cache_type_arg);
+  virtual IO_CACHE *get_log_file() { return &log_file; }
+
+  longlong write_description_event(enum_binlog_checksum_alg checksum_alg,
+                                   bool encrypt, bool dont_set_created,
+                                   bool is_relay_log);
+
+  bool write_event(Log_event *ev, binlog_cache_data *data, IO_CACHE *file);
+};
+
+/**
+  A single-reader, single-writer non-blocking layer for Event_log.
+  Provides IO_CACHE for writing and IO_CACHE for reading.
+
+  Writers use an overrided get_log_file version for their writes, while readers
+  should use flip() to initiate reading.
+  flip() swaps pointers to allow non-blocking reads.
+
+  Writers can block other writers and a reader with a mutex, but a reader only
+  swaps two pointers under a lock, so it won't block writers.
+
+  TODO should be unnecessary after MDEV-24676 is done
+ */
+class Cache_flip_event_log: public Event_log {
+  IO_CACHE alt_buf;
+  IO_CACHE *current, *alt;
+  std::atomic<uint> ref_count;
+public:
+
+  Cache_flip_event_log() : Event_log(), alt_buf{},
+                           current(&log_file), alt(&alt_buf), ref_count(1) {}
+  bool open(enum cache_type io_cache_type_arg)
+  {
+    log_file.dir= mysql_tmpdir;
+    alt_buf.dir= log_file.dir;
+    bool res= Event_log::open(io_cache_type_arg);
+    if (res)
+      return res;
+
+    name= my_strdup(key_memory_MYSQL_LOG_name, "online-alter-binlog",
+                    MYF(MY_WME));
+    if (!name)
+      return false;
+
+    res= init_io_cache(&alt_buf, -1, LOG_BIN_IO_SIZE, io_cache_type_arg, 0, 0,
+                       MYF(MY_WME | MY_NABP | MY_WAIT_IF_FULL)) != 0;
+    return res;
+  }
+
+  /**
+    Swaps current and alt_log. Can be called only from the reader thread.
+    @return a new IO_CACHE pointer to read from.
+   */
+  IO_CACHE *flip()
+  {
+    IO_CACHE *tmp= current;
+    reinit_io_cache(alt, WRITE_CACHE, 0, 0, 0);
+    mysql_mutex_lock(get_log_lock());
+    reinit_io_cache(current, READ_CACHE, 0, 0, 0);
+    current= alt;
+    mysql_mutex_unlock(get_log_lock());
+    alt= tmp;
+
+    return alt;
+  }
+
+  IO_CACHE *get_log_file() override
+  {
+    mysql_mutex_assert_owner(get_log_lock());
+    return current;
+  }
+
+  void acquire()
+  {
+    IF_DBUG(auto prev= ,)
+    ref_count.fetch_add(1);
+    DBUG_ASSERT(prev != 0);
+  }
+
+  void release()
+  {
+    auto prev= ref_count.fetch_add(-1);
+
+    if (prev == 1)
+    {
+      cleanup();
+      delete this;
+    }
+  }
+
+private:
+  void cleanup()
+  {
+    close_cached_file(&log_file);
+    close_cached_file(&alt_buf);
+    Event_log::cleanup();
+  }
+};
+
 /* Tell the io thread if we can delay the master info sync. */
 #define SEMI_SYNC_SLAVE_DELAY_SYNC 1
 /* Tell the io thread if the current event needs a ack. */
@@ -419,12 +588,13 @@ class MYSQL_QUERY_LOG: public MYSQL_LOG
 #define BINLOG_COOKIE_IS_DUMMY(c) \
   ( ((ulong)(c)>>1) == BINLOG_COOKIE_DUMMY_ID )
 
+
 class binlog_cache_mngr;
 class binlog_cache_data;
 struct rpl_gtid;
 struct wait_for_commit;
 
-class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
+class MYSQL_BIN_LOG: public TC_LOG, private Event_log
 {
   /** The instrumentation key to use for @ LOCK_index. */
   PSI_mutex_key m_key_LOCK_index;
@@ -432,14 +602,10 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
   PSI_cond_key m_key_relay_log_update;
   /** The instrumentation key to use for @ COND_bin_log_updated */
   PSI_cond_key m_key_bin_log_update;
-  /** The instrumentation key to use for opening the log file. */
-  PSI_file_key m_key_file_log, m_key_file_log_cache;
   /** The instrumentation key to use for opening the log index file. */
   PSI_file_key m_key_file_log_index, m_key_file_log_index_cache;
 
   PSI_cond_key m_key_COND_queue_busy;
-  /** The instrumentation key to use for LOCK_binlog_end_pos. */
-  PSI_mutex_key m_key_LOCK_binlog_end_pos;
 
   struct group_commit_entry
   {
@@ -492,7 +658,6 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
 
   /* LOCK_log and LOCK_index are inited by init_pthread_objects() */
   mysql_mutex_t LOCK_index;
-  mysql_mutex_t LOCK_binlog_end_pos;
   mysql_mutex_t LOCK_xid_list;
   mysql_cond_t  COND_xid_list;
   mysql_cond_t  COND_relay_log_updated, COND_bin_log_updated;
@@ -542,9 +707,6 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
   ulonglong group_commit_trigger_count, group_commit_trigger_timeout;
   ulonglong group_commit_trigger_lock_wait;
 
-  /* binlog encryption data */
-  struct Binlog_crypt_data crypto;
-
   /* pointer to the sync period variable, for binlog this will be
      sync_binlog_period, for relay log this will be
      sync_relay_log_period
@@ -565,7 +727,6 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
     new_file() is locking. new_file_without_locking() does not acquire
     LOCK_log.
   */
-  int new_file_without_locking();
   int new_file_impl();
   void do_checkpoint_request(ulong binlog_id);
   void purge();
@@ -575,6 +736,7 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
   void trx_group_commit_leader(group_commit_entry *leader);
   bool is_xidlist_idle_nolock();
 public:
+  int new_file_without_locking();
   /*
     A list of struct xid_count_per_binlog is used to keep track of how many
     XIDs are in prepared, but not committed, state in each binlog. And how
@@ -710,6 +872,11 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
   }
 #endif
 
+  Event_log *as_event_log()
+  {
+    return this;
+  }
+
   int open(const char *opt_name);
   void close();
   virtual int generate_new_name(char *new_name, const char *log_name,
@@ -723,10 +890,7 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
               Format_description_log_event *fdle, bool do_xa);
   int do_binlog_recovery(const char *opt_name, bool do_xa_recovery);
 #if !defined(MYSQL_CLIENT)
-
-  int flush_and_set_pending_rows_event(THD *thd, Rows_log_event* event,
-                                       bool is_transactional);
-  int remove_pending_rows_event(THD *thd, bool is_transactional);
+  static int remove_pending_rows_event(THD *thd, binlog_cache_data *cache_data);
 
 #endif /* !defined(MYSQL_CLIENT) */
   void reset_bytes_written()
@@ -821,15 +985,13 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
   bool write_incident_already_locked(THD *thd);
   bool write_incident(THD *thd);
   void write_binlog_checkpoint_event_already_locked(const char *name, uint len);
-  int  write_cache(THD *thd, IO_CACHE *cache);
-  void set_write_error(THD *thd, bool is_transactional);
-  bool check_write_error(THD *thd);
-
+  bool write_table_map(THD *thd, TABLE *table, bool with_annotate);
   void start_union_events(THD *thd, query_id_t query_id_param);
   void stop_union_events(THD *thd);
   bool is_query_in_union(THD *thd, query_id_t query_id_param);
 
-  bool write_event(Log_event *ev, binlog_cache_data *data, IO_CACHE *file);
+  using Event_log::write_event;
+
   bool write_event(Log_event *ev) { return write_event(ev, 0, &log_file); }
 
   bool write_event_buffer(uchar* buf,uint len);
@@ -893,8 +1055,7 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
   uint next_file_id();
   inline char* get_index_fname() { return index_file_name;}
   inline char* get_log_fname() { return log_file_name; }
-  inline char* get_name() { return name; }
-  inline mysql_mutex_t* get_log_lock() { return &LOCK_log; }
+  using MYSQL_LOG::get_log_lock;
   inline mysql_cond_t* get_bin_log_cond() { return &COND_bin_log_updated; }
   inline IO_CACHE* get_log_file() { return &log_file; }
   inline uint64 get_reset_master_count() { return reset_master_count; }
@@ -1175,6 +1336,17 @@ File open_binlog(IO_CACHE *log, const char *log_file_name,
 void make_default_log_name(char **out, const char* log_ext, bool once);
 void binlog_reset_cache(THD *thd);
 bool write_annotated_row(THD *thd);
+int binlog_flush_pending_rows_event(THD *thd, bool stmt_end,
+                                    bool is_transactional,
+                                    Event_log *bin_log,
+                                    binlog_cache_data *cache_data);
+Rows_log_event* binlog_get_pending_rows_event(binlog_cache_mngr *cache_mngr,
+                                              bool use_trans_cache);
+int binlog_log_row_online_alter(TABLE* table, const uchar *before_record,
+                                const uchar *after_record, Log_func *log_func);
+online_alter_cache_data *online_alter_binlog_get_cache_data(THD *thd, TABLE *table);
+binlog_cache_data* binlog_get_cache_data(binlog_cache_mngr *cache_mngr,
+                                         bool use_trans_cache);
 
 extern MYSQL_PLUGIN_IMPORT MYSQL_BIN_LOG mysql_bin_log;
 extern handlerton *binlog_hton;
@@ -1267,5 +1439,7 @@ int binlog_rollback_by_xid(handlerton *hton, XID *xid);
 bool write_bin_log_start_alter(THD *thd, bool& partial_alter,
                                uint64 start_alter_id, bool log_if_exists);
 
+int online_alter_savepoint_set(THD *thd, LEX_CSTRING name);
+int online_alter_savepoint_rollback(THD *thd, LEX_CSTRING name);
 
 #endif /* LOG_H */
diff --git a/sql/log_event.cc b/sql/log_event.cc
index 50124dae04c..1938b03dc81 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -719,6 +719,9 @@ const char* Log_event::get_type_str()
 Log_event::Log_event(const uchar *buf,
                      const Format_description_log_event* description_event)
   :temp_buf(0), exec_time(0), cache_type(Log_event::EVENT_INVALID_CACHE),
+#ifndef MYSQL_CLIENT
+    slave_exec_mode(SLAVE_EXEC_MODE_STRICT),
+#endif
     checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF)
 {
 #ifndef MYSQL_CLIENT
@@ -761,17 +764,13 @@ Log_event::Log_event(const uchar *buf,
 
 int Log_event::read_log_event(IO_CACHE* file, String* packet,
                               const Format_description_log_event *fdle,
-                              enum enum_binlog_checksum_alg checksum_alg_arg)
+                              enum enum_binlog_checksum_alg checksum_alg_arg,
+                              size_t max_allowed_packet)
 {
   ulong data_len;
   char buf[LOG_EVENT_MINIMAL_HEADER_LEN];
   uchar ev_offset= packet->length();
-#if !defined(MYSQL_CLIENT)
-  THD *thd=current_thd;
-  ulong max_allowed_packet= thd ? thd->slave_thread ? slave_max_allowed_packet
-                                                    : thd->variables.max_allowed_packet
-                                : ~(uint)0;
-#endif
+
   DBUG_ENTER("Log_event::read_log_event(IO_CACHE*,String*...)");
 
   if (my_b_read(file, (uchar*) buf, sizeof(buf)))
@@ -882,7 +881,8 @@ int Log_event::read_log_event(IO_CACHE* file, String* packet,
 
 Log_event* Log_event::read_log_event(IO_CACHE* file,
                                      const Format_description_log_event *fdle,
-                                     my_bool crc_check)
+                                     my_bool crc_check,
+                                     size_t max_allowed_packet)
 {
   DBUG_ENTER("Log_event::read_log_event(IO_CACHE*,Format_description_log_event*...)");
   DBUG_ASSERT(fdle != 0);
@@ -890,7 +890,8 @@ Log_event* Log_event::read_log_event(IO_CACHE* file,
   const char *error= 0;
   Log_event *res= 0;
 
-  switch (read_log_event(file, &event, fdle, BINLOG_CHECKSUM_ALG_OFF))
+  switch (read_log_event(file, &event, fdle, BINLOG_CHECKSUM_ALG_OFF,
+                         max_allowed_packet))
   {
     case 0:
       break;
@@ -2956,31 +2957,27 @@ Rows_log_event::Rows_log_event(const uchar *buf, uint event_len,
                                const Format_description_log_event
                                *description_event)
   : Log_event(buf, description_event),
-    m_row_count(0),
 #ifndef MYSQL_CLIENT
     m_table(NULL),
 #endif
     m_table_id(0), m_rows_buf(0), m_rows_cur(0), m_rows_end(0),
-    m_extra_row_data(0)
+    m_row_count(0), m_extra_row_data(0)
 #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
     , m_curr_row(NULL), m_curr_row_end(NULL),
     m_key(NULL), m_key_info(NULL), m_key_nr(0),
-    master_had_triggers(0)
+    m_usable_key_parts(0), master_had_triggers(0)
 #endif
 {
   DBUG_ENTER("Rows_log_event::Rows_log_event(const char*,...)");
   uint8 const common_header_len= description_event->common_header_len;
   Log_event_type event_type= (Log_event_type)(uchar)buf[EVENT_TYPE_OFFSET];
   m_type= event_type;
-  m_cols_ai.bitmap= 0;
+  m_cols_ai.bitmap= 0; // Set to invalid, so it can be processed in is_valid().
 
   uint8 const post_header_len= description_event->post_header_len[event_type-1];
 
   if (event_len < (uint)(common_header_len + post_header_len))
-  {
-    m_cols.bitmap= 0;
     DBUG_VOID_RETURN;
-  }
 
   DBUG_PRINT("enter",("event_len: %u  common_header_len: %d  "
 		      "post_header_len: %d",
@@ -3089,8 +3086,6 @@ Rows_log_event::Rows_log_event(const uchar *buf, uint event_len,
     DBUG_VOID_RETURN;
   }
 
-  m_cols_ai.bitmap= m_cols.bitmap; /* See explanation in is_valid() */
-
   if (LOG_EVENT_IS_UPDATE_ROW(event_type))
   {
     DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
@@ -3109,11 +3104,21 @@ Rows_log_event::Rows_log_event(const uchar *buf, uint event_len,
     }
     else
     {
-      // Needed because my_bitmap_init() does not set it to null on failure
-      m_cols_ai.bitmap= 0;
+      DBUG_ASSERT(m_cols_ai.bitmap == NULL);
       DBUG_VOID_RETURN;
     }
   }
+  else
+  {
+    m_cols_ai= m_cols; /* Safety */
+#ifdef DBUG_OFF
+    /*
+      m_cols_ai should only be usable for update events. Make sure nobody
+      successfully manipulates it in debug builds.
+    */
+    m_cols_ai.bitmap= (my_bitmap_map*)1;
+#endif
+  }
 
   const uchar* const ptr_rows_data= (const uchar*) ptr_after_width;
 
diff --git a/sql/log_event.h b/sql/log_event.h
index a88acb15535..1ace9609104 100644
--- a/sql/log_event.h
+++ b/sql/log_event.h
@@ -54,6 +54,8 @@
 #include "rpl_record.h"
 #include "rpl_reporting.h"
 #include "sql_class.h"                          /* THD */
+#else
+typedef ulong enum_slave_exec_mode;
 #endif
 
 #include "rpl_gtid.h"
@@ -1249,12 +1251,6 @@ class Log_event
      event's type, and its content is distributed in the event-specific fields.
   */
   uchar *temp_buf;
-  
-  /*
-    TRUE <=> this event 'owns' temp_buf and should call my_free() when done
-    with it
-  */
-  bool event_owns_temp_buf;
 
   /*
     Timestamp on the master(for debugging and replication of
@@ -1284,13 +1280,29 @@ class Log_event
   */
   uint16 flags;
 
+  /**
+    true <=> this event 'owns' temp_buf and should call my_free() when done
+    with it
+  */
+  bool event_owns_temp_buf;
+
   enum_event_cache_type cache_type;
 
   /**
     A storage to cache the global system variable's value.
     Handling of a separate event will be governed its member.
   */
-  ulong slave_exec_mode;
+  enum_slave_exec_mode slave_exec_mode;
+
+  /**
+     The value is set by caller of FD constructor and
+     Log_event::write_header() for the rest.
+     In the FD case it's propagated into the last byte
+     of post_header_len[] at FD::write().
+     On the slave side the value is assigned from post_header_len[last]
+     of the last seen FD event.
+  */
+  enum enum_binlog_checksum_alg checksum_alg;
 
   Log_event_writer *writer;
 
@@ -1363,6 +1375,20 @@ class Log_event
 #endif
 #endif
 
+private:
+  static size_t get_max_packet()
+  {
+    size_t max_packet= ~0UL;
+    #if !defined(MYSQL_CLIENT)
+    THD *thd=current_thd;
+    if (thd)
+      max_packet= thd->slave_thread ? slave_max_allowed_packet
+                                    : thd->variables.max_allowed_packet;
+    #endif
+    return max_packet;
+  }
+public:
+
   /*
     read_log_event() functions read an event from a binlog or relay
     log; used by SHOW BINLOG EVENTS, the binlog_dump thread on the
@@ -1377,7 +1403,15 @@ class Log_event
   static Log_event* read_log_event(IO_CACHE* file,
                                    const Format_description_log_event
                                    *description_event,
-                                   my_bool crc_check);
+                                   my_bool crc_check,
+                                   size_t max_allowed_packet);
+  static Log_event* read_log_event(IO_CACHE* file,
+                                   const Format_description_log_event
+                                   *description_event,
+                                   my_bool crc_check)
+  {
+    return read_log_event(file, description_event, crc_check, get_max_packet());
+  }
 
   /**
     Reads an event from a binlog or relay log. Used by the dump thread
@@ -1404,16 +1438,15 @@ class Log_event
    */
   static int read_log_event(IO_CACHE* file, String* packet,
                             const Format_description_log_event *fdle,
-                            enum enum_binlog_checksum_alg checksum_alg_arg);
-  /* 
-     The value is set by caller of FD constructor and
-     Log_event::write_header() for the rest.
-     In the FD case it's propagated into the last byte 
-     of post_header_len[] at FD::write().
-     On the slave side the value is assigned from post_header_len[last] 
-     of the last seen FD event.
-  */
-  enum enum_binlog_checksum_alg checksum_alg;
+                            enum enum_binlog_checksum_alg checksum_alg_arg,
+                            size_t max_allowed_packet);
+
+  static int read_log_event(IO_CACHE* file, String* packet,
+                            const Format_description_log_event *fdle,
+                            enum enum_binlog_checksum_alg checksum_alg)
+  {
+    return read_log_event(file, packet, fdle, checksum_alg, get_max_packet());
+  }
 
   static void *operator new(size_t size)
   {
@@ -1533,6 +1566,12 @@ class Log_event
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
 
+  /**
+    Increase or decrease the rows inserted during ALTER TABLE based on the event
+    type.
+  */
+  virtual void online_alter_update_row_count(ha_rows *) const {}
+
   /**
      Apply the event to the database.
 
@@ -3751,6 +3790,8 @@ class Annotate_rows_log_event: public Log_event
   bool  m_used_query_txt;
 };
 
+class table_def;
+
 /**
   @class Table_map_log_event
 
@@ -4402,6 +4443,7 @@ class Table_map_log_event : public Log_event
   virtual bool print(FILE *file, PRINT_EVENT_INFO *print_event_info);
 #endif
 
+  table_def get_table_def();
 
 private:
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
@@ -4545,7 +4587,7 @@ class Rows_log_event : public Log_event
   typedef uint16 flag_set;
 
   /* Special constants representing sets of flags */
-  enum 
+  enum
   {
       RLE_NO_FLAGS = 0U
   };
@@ -4559,7 +4601,7 @@ class Rows_log_event : public Log_event
 
   Log_event_type get_type_code() { return m_type; } /* Specific type (_V1 etc) */
   enum_logged_status logged_status() { return LOGGED_ROW_EVENT; }
-  virtual Log_event_type get_general_type_code() = 0; /* General rows op type, no version */
+  virtual Log_event_type get_general_type_code() const = 0; /* General rows op type, no version */
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
   virtual void pack_info(Protocol *protocol);
@@ -4587,7 +4629,7 @@ class Rows_log_event : public Log_event
 #ifdef MYSQL_SERVER
   int add_row_data(uchar *data, size_t length)
   {
-    return do_add_row_data(data,length); 
+    return do_add_row_data(data,length);
   }
 #endif
 
@@ -4665,12 +4707,10 @@ class Rows_log_event : public Log_event
   }
   bool is_part_of_group() { return get_flags(STMT_END_F) != 0; }
 
-  uint     m_row_count;         /* The number of rows added to the event */
-
   const uchar* get_extra_row_data() const   { return m_extra_row_data; }
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
-  virtual uint8 get_trg_event_map()= 0;
+  virtual uint8 get_trg_event_map() const = 0;
 
   inline bool do_invoke_trigger()
   {
@@ -4706,7 +4746,7 @@ class Rows_log_event : public Log_event
 #endif
   ulonglong       m_table_id;	/* Table ID */
   MY_BITMAP   m_cols;		/* Bitmap denoting columns available */
-  ulong       m_width;          /* The width of the columns bitmap */
+  uint        m_width;          /* The width of the columns bitmap */
   /*
     Bitmap for columns available in the after image, if present. These
     fields are only available for Update_rows events. Observe that the
@@ -4733,20 +4773,24 @@ class Rows_log_event : public Log_event
 
   Log_event_type m_type;        /* Actual event type */
 
+  bool m_vers_from_plain;
+
+  uint     m_row_count;         /* The number of rows added to the event */
+
   uchar    *m_extra_row_data;   /* Pointer to extra row data if any */
                                 /* If non null, first byte is length */
 
-  bool m_vers_from_plain;
-
 
   /* helper functions */
 
+
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
   const uchar *m_curr_row;     /* Start of the row being processed */
   const uchar *m_curr_row_end; /* One-after the end of the current row */
   uchar    *m_key;      /* Buffer to keep key value during searches */
   KEY      *m_key_info; /* Pointer to KEY info for m_key_nr */
   uint      m_key_nr;   /* Key number */
+  uint      m_usable_key_parts; /* A number of key_parts suited to lookup */
   bool master_had_triggers;     /* set after tables opening */
 
   /*
@@ -4794,7 +4838,9 @@ class Rows_log_event : public Log_event
     friend class Rows_log_event;
   };
 
-  int find_key(); // Find a best key to use in find_row()
+  int find_key(const rpl_group_info *); // Find a best key to use in find_row()
+  uint find_key_parts(const KEY *key) const;
+  bool use_pk_position() const;
   int find_row(rpl_group_info *);
   int write_row(rpl_group_info *, const bool);
   int update_sequence();
@@ -4807,7 +4853,7 @@ class Rows_log_event : public Log_event
 
     ASSERT_OR_RETURN_ERROR(m_curr_row <= m_rows_end, HA_ERR_CORRUPT_EVENT);
     return ::unpack_row(rgi, m_table, m_width, m_curr_row, cols,
-                                   &m_curr_row_end, &m_master_reclength, m_rows_end);
+                        &m_curr_row_end, &m_master_reclength, m_rows_end);
   }
 
   // Unpack the current row into m_table->record[0]
@@ -4817,10 +4863,9 @@ class Rows_log_event : public Log_event
 
     ASSERT_OR_RETURN_ERROR(m_curr_row <= m_rows_end, HA_ERR_CORRUPT_EVENT);
     return ::unpack_row(rgi, m_table, m_width, m_curr_row, &m_cols,
-                                   &m_curr_row_end, &m_master_reclength, m_rows_end);
+                        &m_curr_row_end, &m_master_reclength, m_rows_end);
   }
-  bool process_triggers(trg_event_type event,
-                        trg_action_time_type time_type,
+  bool process_triggers(trg_event_type event, trg_action_time_type time_type,
                         bool old_row_is_record1);
 
   /**
@@ -4845,8 +4890,8 @@ class Rows_log_event : public Log_event
   virtual int do_update_pos(rpl_group_info *rgi);
   virtual enum_skip_reason do_shall_skip(rpl_group_info *rgi);
 
-  /*
-    Primitive to prepare for a sequence of row executions.
+  /**
+    @brief Primitive to prepare for a sequence of row executions.
 
     DESCRIPTION
 
@@ -4856,16 +4901,15 @@ class Rows_log_event : public Log_event
       space for any buffers that are needed for the two member
       functions mentioned above.
 
-    RETURN VALUE
-
+    @return
       The member function will return 0 if all went OK, or a non-zero
       error code otherwise.
   */
-  virtual 
-  int do_before_row_operations(const Slave_reporting_capability *const log) = 0;
+  virtual
+  int do_before_row_operations(const rpl_group_info *) = 0;
 
-  /*
-    Primitive to clean up after a sequence of row executions.
+  /**
+    @brief Primitive to clean up after a sequence of row executions.
 
     DESCRIPTION
     
@@ -4878,11 +4922,10 @@ class Rows_log_event : public Log_event
       function is successful, it should return the error code given in the argument.
   */
   virtual 
-  int do_after_row_operations(const Slave_reporting_capability *const log,
-                              int error) = 0;
+  int do_after_row_operations(int error) = 0;
 
-  /*
-    Primitive to do the actual execution necessary for a row.
+  /**
+    @brief Primitive to do the actual execution necessary for a row.
 
     DESCRIPTION
       The member function will do the actual execution needed to handle a row.
@@ -4890,7 +4933,7 @@ class Rows_log_event : public Log_event
       m_curr_row_end should point at the next row (one byte after the end
       of the current row).    
 
-    RETURN VALUE
+    @return
       0 if execution succeeded, 1 if execution failed.
       
   */
@@ -4910,11 +4953,8 @@ class Rows_log_event : public Log_event
 class Write_rows_log_event : public Rows_log_event
 {
 public:
-  enum 
-  {
-    /* Support interface to THD::binlog_prepare_pending_rows_event */
-    TYPE_CODE = WRITE_ROWS_EVENT
-  };
+  /* Support interface to THD::binlog_prepare_pending_rows_event */
+  static constexpr Log_event_type TYPE_CODE = WRITE_ROWS_EVENT;
 
 #if defined(MYSQL_SERVER)
   Write_rows_log_event(THD*, TABLE*, ulong table_id,
@@ -4926,31 +4966,39 @@ class Write_rows_log_event : public Rows_log_event
 #endif
 #if defined(MYSQL_SERVER) 
   static bool binlog_row_logging_function(THD *thd, TABLE *table,
-                                          bool is_transactional,
+                                          Event_log *bin_log,
+                                          binlog_cache_data *cache_data,
+                                          bool is_transactional, ulong,
                                           const uchar *before_record
                                           __attribute__((unused)),
                                           const uchar *after_record)
   {
     DBUG_ASSERT(!table->versioned(VERS_TRX_ID));
-    return thd->binlog_write_row(table, is_transactional, after_record);
+    return thd->binlog_write_row(table, bin_log, cache_data, is_transactional,
+                                 after_record);
   }
 #endif
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
-  uint8 get_trg_event_map();
+  uint8 get_trg_event_map() const override;
+
+  void online_alter_update_row_count(ha_rows *rows) const override
+  {
+    *rows += m_row_count;
+  }
 #endif
 
 private:
-  virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
+  Log_event_type get_general_type_code() const override { return TYPE_CODE; }
 
 #ifdef MYSQL_CLIENT
-  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info) override;
 #endif
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
-  virtual int do_before_row_operations(const Slave_reporting_capability *const);
-  virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
-  virtual int do_exec_row(rpl_group_info *);
+  int do_before_row_operations(const rpl_group_info *) override;
+  int do_after_row_operations(int) override;
+  int do_exec_row(rpl_group_info *) override;
 #endif
 };
 
@@ -4968,7 +5016,7 @@ class Write_rows_compressed_log_event : public Write_rows_log_event
 #endif
 private:
 #if defined(MYSQL_CLIENT)
-  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info) override;
 #endif
 };
 
@@ -4987,11 +5035,8 @@ class Write_rows_compressed_log_event : public Write_rows_log_event
 class Update_rows_log_event : public Rows_log_event
 {
 public:
-  enum 
-  {
-    /* Support interface to THD::binlog_prepare_pending_rows_event */
-    TYPE_CODE = UPDATE_ROWS_EVENT
-  };
+  /* Support interface to THD::binlog_prepare_pending_rows_event */
+  static constexpr Log_event_type TYPE_CODE = UPDATE_ROWS_EVENT;
 
 #ifdef MYSQL_SERVER
   Update_rows_log_event(THD*, TABLE*, ulong table_id,
@@ -5000,7 +5045,7 @@ class Update_rows_log_event : public Rows_log_event
   void init(MY_BITMAP const *cols);
 #endif
 
-  virtual ~Update_rows_log_event();
+  ~Update_rows_log_event() override;
 
 #ifdef HAVE_REPLICATION
   Update_rows_log_event(const uchar *buf, uint event_len,
@@ -5009,36 +5054,40 @@ class Update_rows_log_event : public Rows_log_event
 
 #ifdef MYSQL_SERVER
   static bool binlog_row_logging_function(THD *thd, TABLE *table,
+                                          Event_log *bin_log,
+                                          binlog_cache_data *cache_data,
                                           bool is_transactional,
+                                          ulong row_image,
                                           const uchar *before_record,
                                           const uchar *after_record)
   {
     DBUG_ASSERT(!table->versioned(VERS_TRX_ID));
-    return thd->binlog_update_row(table, is_transactional,
+    return thd->binlog_update_row(table, bin_log, cache_data, is_transactional,
+                                  (enum_binlog_row_image)row_image,
                                   before_record, after_record);
   }
 #endif
 
-  virtual bool is_valid() const
+  bool is_valid() const override
   {
     return Rows_log_event::is_valid() && m_cols_ai.bitmap;
   }
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
-  uint8 get_trg_event_map();
+  uint8 get_trg_event_map() const override;
 #endif
 
 protected:
-  virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
+  Log_event_type get_general_type_code() const override { return TYPE_CODE; }
 
 #ifdef MYSQL_CLIENT
-  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info) override;
 #endif
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
-  virtual int do_before_row_operations(const Slave_reporting_capability *const);
-  virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
-  virtual int do_exec_row(rpl_group_info *);
+  int do_before_row_operations(const rpl_group_info *) override;
+  int do_after_row_operations(int) override;
+  int do_exec_row(rpl_group_info *) override;
 #endif /* defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) */
 };
 
@@ -5056,7 +5105,7 @@ class Update_rows_compressed_log_event : public Update_rows_log_event
 #endif
 private:
 #if defined(MYSQL_CLIENT)
-  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info) override;
 #endif
 };
 
@@ -5083,11 +5132,8 @@ class Update_rows_compressed_log_event : public Update_rows_log_event
 class Delete_rows_log_event : public Rows_log_event
 {
 public:
-  enum 
-  {
-    /* Support interface to THD::binlog_prepare_pending_rows_event */
-    TYPE_CODE = DELETE_ROWS_EVENT
-  };
+  /* Support interface to THD::binlog_prepare_pending_rows_event */
+  static constexpr Log_event_type TYPE_CODE = DELETE_ROWS_EVENT;
 
 #ifdef MYSQL_SERVER
   Delete_rows_log_event(THD*, TABLE*, ulong, bool is_transactional);
@@ -5098,32 +5144,41 @@ class Delete_rows_log_event : public Rows_log_event
 #endif
 #ifdef MYSQL_SERVER
   static bool binlog_row_logging_function(THD *thd, TABLE *table,
+                                          Event_log *bin_log,
+                                          binlog_cache_data *cache_data,
                                           bool is_transactional,
+                                          ulong row_image,
                                           const uchar *before_record,
                                           const uchar *after_record
                                           __attribute__((unused)))
   {
     DBUG_ASSERT(!table->versioned(VERS_TRX_ID));
-    return thd->binlog_delete_row(table, is_transactional,
+    return thd->binlog_delete_row(table, bin_log, cache_data, is_transactional,
+                                  (enum_binlog_row_image)row_image,
                                   before_record);
   }
 #endif
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
-  uint8 get_trg_event_map();
+  uint8 get_trg_event_map() const override;
+
+  void online_alter_update_row_count(ha_rows *rows) const override
+  {
+    *rows -= m_row_count;
+  }
 #endif
 
 protected:
-  virtual Log_event_type get_general_type_code() { return (Log_event_type)TYPE_CODE; }
+  Log_event_type get_general_type_code() const override { return TYPE_CODE; }
 
 #ifdef MYSQL_CLIENT
-  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info) override;
 #endif
 
 #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
-  virtual int do_before_row_operations(const Slave_reporting_capability *const);
-  virtual int do_after_row_operations(const Slave_reporting_capability *const,int);
-  virtual int do_exec_row(rpl_group_info *);
+  int do_before_row_operations(const rpl_group_info *const) override;
+  int do_after_row_operations(int) override;
+  int do_exec_row(rpl_group_info *) override;
 #endif
 };
 
@@ -5140,7 +5195,7 @@ class Delete_rows_compressed_log_event : public Delete_rows_log_event
 #endif
 private:
 #if defined(MYSQL_CLIENT)
-  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info);
+  bool print(FILE *file, PRINT_EVENT_INFO *print_event_info) override;
 #endif
 };
 
diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc
index fbac27e7c1f..59f238aadf1 100644
--- a/sql/log_event_server.cc
+++ b/sql/log_event_server.cc
@@ -53,6 +53,7 @@
 #include "wsrep_mysqld.h"
 #include "sql_insert.h"
 #include "sql_table.h"
+#include <mysql/service_wsrep.h>
 
 #include <my_bitmap.h>
 #include "rpl_utility.h"
@@ -543,8 +544,9 @@ int append_query_string(CHARSET_INFO *csinfo, String *to,
 **************************************************************************/
 
 Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans)
-  :log_pos(0), temp_buf(0), exec_time(0), thd(thd_arg),
-   checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF)
+  :log_pos(0), temp_buf(0), exec_time(0),
+   slave_exec_mode(SLAVE_EXEC_MODE_STRICT),
+   checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF), thd(thd_arg)
 {
   server_id=	thd->variables.server_id;
   when=         thd->start_time;
@@ -568,7 +570,8 @@ Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans)
 
 Log_event::Log_event()
   :temp_buf(0), exec_time(0), flags(0), cache_type(EVENT_INVALID_CACHE),
-   thd(0), checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF)
+   slave_exec_mode(SLAVE_EXEC_MODE_STRICT),
+   checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF), thd(0)
 {
   server_id=	global_system_variables.server_id;
   /*
@@ -4582,12 +4585,11 @@ Rows_log_event::Rows_log_event(THD *thd_arg, TABLE *tbl_arg, ulong tid,
                                MY_BITMAP const *cols, bool is_transactional,
                                Log_event_type event_type)
   : Log_event(thd_arg, 0, is_transactional),
-    m_row_count(0),
     m_table(tbl_arg),
     m_table_id(tid),
     m_width(tbl_arg ? tbl_arg->s->fields : 1),
     m_rows_buf(0), m_rows_cur(0), m_rows_end(0), m_flags(0),
-    m_type(event_type), m_extra_row_data(0)
+    m_type(event_type), m_row_count(0), m_extra_row_data(0)
 #ifdef HAVE_REPLICATION
     , m_curr_row(NULL), m_curr_row_end(NULL),
     m_key(NULL), m_key_info(NULL), m_key_nr(0),
@@ -4741,6 +4743,7 @@ inline void restore_empty_query_table_list(LEX *lex)
 
 int Rows_log_event::do_apply_event(rpl_group_info *rgi)
 {
+  DBUG_ASSERT(rgi);
   Relay_log_info const *rli= rgi->rli;
   TABLE* table;
   DBUG_ENTER("Rows_log_event::do_apply_event(Relay_log_info*)");
@@ -4781,12 +4784,11 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
   thd->set_query_timer();
 
   /*
-    If there is no locks taken, this is the first binrow event seen
-    after the table map events.  We should then lock all the tables
-    used in the transaction and proceed with execution of the actual
-    event.
+    If there are no tables open, this must be the first row event seen
+    after the table map events. We should then open and lock all tables
+    used in the transaction and proceed with execution of the actual event.
   */
-  if (!thd->lock)
+  if (!thd->open_tables)
   {
     /*
       Lock_tables() reads the contents of thd->lex, so they must be
@@ -5027,17 +5029,13 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
       }
     }
 
-#ifdef HAVE_QUERY_CACHE
     /*
       Moved invalidation right before the call to rows_event_stmt_cleanup(),
       to avoid query cache being polluted with stale entries,
+      Query cache is not invalidated on wsrep applier here
     */
-# ifdef WITH_WSREP
-    /* Query cache is not invalidated on wsrep applier here */
     if (!(WSREP(thd) && wsrep_thd_is_applying(thd)))
-# endif /* WITH_WSREP */
       query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock);
-#endif /* HAVE_QUERY_CACHE */
   }
 
   table= m_table= rgi->m_table_map.get_table(m_table_id);
@@ -5062,6 +5060,8 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
      if (m_width == table->s->fields && bitmap_is_set_all(&m_cols))
       set_flags(COMPLETE_ROWS_F);
 
+    Rpl_table_data rpl_data= *(RPL_TABLE_LIST*)table->pos_in_table_list;
+
     /* 
       Set tables write and read sets.
       
@@ -5075,33 +5075,36 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
     DBUG_PRINT_BITSET("debug", "Setting table's read_set from: %s", &m_cols);
     
     bitmap_set_all(table->read_set);
-    if (get_general_type_code() == DELETE_ROWS_EVENT ||
-        get_general_type_code() == UPDATE_ROWS_EVENT)
+    bitmap_set_all(table->write_set);
+    table->rpl_write_set= table->write_set;
+
+    if (rpl_data.copy_fields)
+      /* always full rows, all bits set */;
+    else
+    if (get_general_type_code() == WRITE_ROWS_EVENT)
+      bitmap_copy(table->write_set, &m_cols); // for sequences
+    else // If online alter, leave all columns set (i.e. skip intersects)
+    if (!thd->slave_thread || !table->s->online_alter_binlog)
     {
       bitmap_intersect(table->read_set,&m_cols);
+      if (get_general_type_code() == UPDATE_ROWS_EVENT)
+        bitmap_intersect(table->write_set, &m_cols_ai);
       table->mark_columns_per_binlog_row_image();
       if (table->vfield)
         table->mark_virtual_columns_for_write(0);
     }
 
-    bitmap_set_all(table->write_set);
-    table->rpl_write_set= table->write_set;
-
-    /* WRITE ROWS EVENTS store the bitmap in m_cols instead of m_cols_ai */
-    MY_BITMAP *after_image= ((get_general_type_code() == UPDATE_ROWS_EVENT) ?
-                             &m_cols_ai : &m_cols);
-    bitmap_intersect(table->write_set, after_image);
-
     if (table->versioned())
     {
       bitmap_set_bit(table->write_set, table->s->vers.start_fieldno);
       bitmap_set_bit(table->write_set, table->s->vers.end_fieldno);
     }
 
-    this->slave_exec_mode= slave_exec_mode_options; // fix the mode
+    if (!rpl_data.is_online_alter())
+      this->slave_exec_mode= (enum_slave_exec_mode)slave_exec_mode_options;
 
     // Do event specific preparations 
-    error= do_before_row_operations(rli);
+    error= do_before_row_operations(rgi);
 
     /*
       Bug#56662 Assertion failed: next_insert_id == 0, file handler.cc
@@ -5113,7 +5116,8 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
     */
     sql_mode_t saved_sql_mode= thd->variables.sql_mode;
     if (!is_auto_inc_in_extra_columns())
-      thd->variables.sql_mode= MODE_NO_AUTO_VALUE_ON_ZERO;
+      thd->variables.sql_mode= (rpl_data.copy_fields ? saved_sql_mode : 0)
+                               | MODE_NO_AUTO_VALUE_ON_ZERO;
 
     // row processing loop
 
@@ -5126,10 +5130,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
     THD_STAGE_INFO(thd, stage_executing);
     do
     {
-      /* in_use can have been set to NULL in close_tables_for_reopen */
-      THD* old_thd= table->in_use;
-      if (!table->in_use)
-        table->in_use= thd;
+      DBUG_ASSERT(table->in_use);
 
       error= do_exec_row(rgi);
 
@@ -5137,8 +5138,6 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
         DBUG_PRINT("info", ("error: %s", HA_ERR(error)));
       DBUG_ASSERT(error != HA_ERR_RECORD_DELETED);
 
-      table->in_use = old_thd;
-
       if (unlikely(error))
       {
         int actual_error= convert_handler_error(error, thd, table);
@@ -5148,7 +5147,8 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
                              ignored_error_code(actual_error) : 0);
 
 #ifdef WITH_WSREP
-        if (WSREP(thd) && wsrep_ignored_error_code(this, actual_error))
+        if (WSREP(thd) && wsrep_thd_is_applying(thd) &&
+            wsrep_ignored_error_code(this, actual_error))
         {
           idempotent_error= true;
           thd->wsrep_has_ignored_error= true;
@@ -5187,6 +5187,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
           thd->transaction->stmt.modified_non_trans_table= TRUE;
       if (likely(error == 0))
       {
+        m_row_count++;
         error= thd->killed_errno();
         if (error && !thd->is_error())
           my_error(error, MYF(0));
@@ -5210,7 +5211,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
                         const_cast<Relay_log_info*>(rli)->abort_slave= 1;);
     }
 
-    if (unlikely(error= do_after_row_operations(rli, error)) &&
+    if (unlikely(error= do_after_row_operations(error)) &&
         ignored_error_code(convert_handler_error(error, thd, table)))
     {
 
@@ -5221,34 +5222,40 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
       thd->clear_error(1);
       error= 0;
     }
-  } // if (table)
 
+    if (unlikely(error))
+    {
+      if (rpl_data.is_online_alter())
+        goto err;
+      slave_rows_error_report(ERROR_LEVEL, error, rgi, thd, table,
+                              get_type_str(),
+                              RPL_LOG_NAME, log_pos);
+      /*
+        @todo We should probably not call
+        reset_current_stmt_binlog_format_row() from here.
   
-  if (unlikely(error))
-  {
-    slave_rows_error_report(ERROR_LEVEL, error, rgi, thd, table,
-                             get_type_str(),
-                             RPL_LOG_NAME, log_pos);
-    /*
-      @todo We should probably not call
-      reset_current_stmt_binlog_format_row() from here.
+        Note: this applies to log_event_old.cc too.
+        /Sven
+      */
+      thd->reset_current_stmt_binlog_format_row();
+      thd->is_slave_error= 1;
+      /* remove trigger's tables */
+      goto err;
+    }
+  } // if (table)
 
-      Note: this applies to log_event_old.cc too.
-      /Sven
-    */
-    thd->reset_current_stmt_binlog_format_row();
-    thd->is_slave_error= 1;
-    /* remove trigger's tables */
-    goto err;
-  }
+  DBUG_ASSERT(error == 0);
 
-  /* remove trigger's tables */
-  restore_empty_query_table_list(thd->lex);
+  /*
+    Remove trigger's tables. In case of ONLINE ALTER TABLE, event doesn't own
+    the table (hence, no tables are locked), and therefore no cleanup should be
+    done after each event.
+  */
+  if (rgi->tables_to_lock_count)
+    restore_empty_query_table_list(thd->lex);
 
-#if defined(WITH_WSREP) && defined(HAVE_QUERY_CACHE)
   if (WSREP(thd) && wsrep_thd_is_applying(thd))
-    query_cache.invalidate_locked_for_write(thd, rgi->tables_to_lock);
-#endif /* WITH_WSREP && HAVE_QUERY_CACHE */
+    query_cache_invalidate_locked_for_write(thd, rgi->tables_to_lock);
 
   if (get_flags(STMT_END_F))
   {
@@ -5264,8 +5271,11 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
   DBUG_RETURN(error);
 
 err:
-  restore_empty_query_table_list(thd->lex);
-  rgi->slave_close_thread_tables(thd);
+  if (rgi->tables_to_lock_count)
+  {
+    restore_empty_query_table_list(thd->lex);
+    rgi->slave_close_thread_tables(thd);
+  }
   thd->reset_query_timer();
   DBUG_RETURN(error);
 }
@@ -5833,6 +5843,13 @@ check_table_map(rpl_group_info *rgi, RPL_TABLE_LIST *table_list)
   DBUG_RETURN(res);
 }
 
+table_def Table_map_log_event::get_table_def()
+{
+  return table_def(m_coltype, m_colcnt,
+                   m_field_metadata, m_field_metadata_size,
+                   m_null_bits, m_flags);
+}
+
 int Table_map_log_event::do_apply_event(rpl_group_info *rgi)
 {
   RPL_TABLE_LIST *table_list;
@@ -5871,16 +5888,26 @@ int Table_map_log_event::do_apply_event(rpl_group_info *rgi)
   LEX_CSTRING tmp_db_name=  {db_mem, db_mem_length };
   LEX_CSTRING tmp_tbl_name= {tname_mem, tname_mem_length };
 
-  table_list->init_one_table(&tmp_db_name, &tmp_tbl_name, 0, TL_WRITE);
-  table_list->table_id= DBUG_IF("inject_tblmap_same_id_maps_diff_table") ? 0 : m_table_id;
-  table_list->updating= 1;
+  /*
+    The memory allocated by the table_def structure (i.e., not the
+    memory allocated *for* the table_def structure) is released
+    inside rpl_group_info::clear_tables_to_lock() by calling the
+    table_def destructor explicitly.
+  */
+  new(table_list) RPL_TABLE_LIST(&tmp_db_name, &tmp_tbl_name, TL_WRITE,
+                                 get_table_def(),
+                                 m_flags & TM_BIT_HAS_TRIGGERS_F);
+
+  table_list->table_id= DBUG_IF("inject_tblmap_same_id_maps_diff_table") ?
+                                         0: m_table_id;
   table_list->required_type= TABLE_TYPE_NORMAL;
+  table_list->open_type= OT_BASE_ONLY;
+  DBUG_ASSERT(table_list->updating);
 
   DBUG_PRINT("debug", ("table: %s is mapped to %llu",
                        table_list->table_name.str,
                        table_list->table_id));
-  table_list->master_had_triggers= ((m_flags & TM_BIT_HAS_TRIGGERS_F) ? 1 : 0);
-  DBUG_PRINT("debug", ("table->master_had_triggers=%d", 
+  DBUG_PRINT("debug", ("table->master_had_triggers=%d",
                        (int)table_list->master_had_triggers));
 
   enum_tbl_map_status tblmap_status= check_table_map(rgi, table_list);
@@ -5888,23 +5915,6 @@ int Table_map_log_event::do_apply_event(rpl_group_info *rgi)
   {
     DBUG_ASSERT(thd->lex->query_tables != table_list);
 
-    /*
-      Use placement new to construct the table_def instance in the
-      memory allocated for it inside table_list.
-
-      The memory allocated by the table_def structure (i.e., not the
-      memory allocated *for* the table_def structure) is released
-      inside Relay_log_info::clear_tables_to_lock() by calling the
-      table_def destructor explicitly.
-    */
-    new (&table_list->m_tabledef)
-      table_def(m_coltype, m_colcnt,
-                m_field_metadata, m_field_metadata_size,
-                m_null_bits, m_flags);
-    table_list->m_tabledef_valid= TRUE;
-    table_list->m_conv_table= NULL;
-    table_list->open_type= OT_BASE_ONLY;
-
     /*
       We record in the slave's information that the table should be
       locked by linking the table into the list of tables to lock.
@@ -5949,8 +5959,9 @@ int Table_map_log_event::do_apply_event(rpl_group_info *rgi)
           execute in a user session 
          */
         my_error(ER_SLAVE_FATAL_ERROR, MYF(0), buf);
-    } 
-    
+    }
+
+    table_list->~RPL_TABLE_LIST();
     my_free(memory);
   }
 
@@ -6457,7 +6468,7 @@ bool Write_rows_compressed_log_event::write()
 
 #if defined(HAVE_REPLICATION)
 int 
-Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability *const)
+Write_rows_log_event::do_before_row_operations(const rpl_group_info *)
 {
   int error= 0;
 
@@ -6535,8 +6546,7 @@ Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability
 }
 
 int 
-Write_rows_log_event::do_after_row_operations(const Slave_reporting_capability *const,
-                                              int error)
+Write_rows_log_event::do_after_row_operations(int error)
 {
   int local_error= 0;
 
@@ -6667,12 +6677,11 @@ is_duplicate_key_error(int errcode)
   @c ha_update_row() or first deleted and then new record written.
 */ 
 
-int
-Rows_log_event::write_row(rpl_group_info *rgi,
-                          const bool overwrite)
+int Rows_log_event::write_row(rpl_group_info *rgi, const bool overwrite)
 {
   DBUG_ENTER("write_row");
-  DBUG_ASSERT(m_table != NULL && thd != NULL);
+  DBUG_ASSERT(m_table != NULL);
+  DBUG_ASSERT(thd != NULL);
 
   TABLE *table= m_table;  // pointer to event's table
   int error;
@@ -6745,14 +6754,12 @@ Rows_log_event::write_row(rpl_group_info *rgi,
     TODO: Add safety measures against infinite looping. 
    */
 
-  if (table->s->sequence)
+  if (unlikely(table->s->sequence))
     error= update_sequence();
   else while (unlikely(error= table->file->ha_write_row(table->record[0])))
   {
-    if (error == HA_ERR_LOCK_DEADLOCK ||
-        error == HA_ERR_LOCK_WAIT_TIMEOUT ||
-        (keynum= table->file->get_dup_key(error)) < 0 ||
-        !overwrite)
+    if (error == HA_ERR_LOCK_DEADLOCK || error == HA_ERR_LOCK_WAIT_TIMEOUT ||
+        (keynum= table->file->get_dup_key(error)) < 0 || !overwrite)
     {
       DBUG_PRINT("info",("get_dup_key returns %d)", keynum));
       /*
@@ -7016,7 +7023,7 @@ Write_rows_log_event::do_exec_row(rpl_group_info *rgi)
 
 
 #if defined(HAVE_REPLICATION)
-uint8 Write_rows_log_event::get_trg_event_map()
+uint8 Write_rows_log_event::get_trg_event_map() const
 {
   return trg2bit(TRG_EVENT_INSERT) | trg2bit(TRG_EVENT_UPDATE) |
          trg2bit(TRG_EVENT_DELETE);
@@ -7028,16 +7035,19 @@ uint8 Write_rows_log_event::get_trg_event_map()
 **************************************************************************/
 
 #if defined(HAVE_REPLICATION)
-/*
-  Compares table->record[0] and table->record[1]
+/**
+  @brief Compares table->record[0] and table->record[1]
 
-  Returns TRUE if different.
+  @returns true if different.
 */
 static bool record_compare(TABLE *table, bool vers_from_plain= false)
 {
-  bool result= FALSE;
+  bool result= false;
+  bool all_values_set= bitmap_is_set_all(&table->has_value_set);
+
   /**
     Compare full record only if:
+    - all fields were given values
     - there are no blob fields (otherwise we would also need 
       to compare blobs contents as well);
     - there are no varchar fields (otherwise we would also need
@@ -7048,24 +7058,23 @@ static bool record_compare(TABLE *table, bool vers_from_plain= false)
     */
   if ((table->s->blob_fields + 
        table->s->varchar_fields + 
-       table->s->null_fields) == 0)
+       table->s->null_fields) == 0
+      && all_values_set)
   {
-    result= cmp_record(table,record[1]);
+    result= cmp_record(table, record[1]);
     goto record_compare_exit;
   }
 
   /* Compare null bits */
-  if (memcmp(table->null_flags,
-	     table->null_flags+table->s->rec_buff_length,
-	     table->s->null_bytes))
-  {
-    result= TRUE;				// Diff in NULL value
-    goto record_compare_exit;
-  }
+  if (all_values_set && memcmp(table->null_flags,
+                               table->null_flags + table->s->rec_buff_length,
+                               table->s->null_bytes))
+    goto record_compare_differ;                         // Diff in NULL value
 
   /* Compare fields */
   for (Field **ptr=table->field ; *ptr ; ptr++)
   {
+    Field *f= *ptr;
     /*
       If the table is versioned, don't compare using the version if there is a
       primary key. If there isn't a primary key, we need the version to
@@ -7075,27 +7084,118 @@ static bool record_compare(TABLE *table, bool vers_from_plain= false)
       because the implicit row_end value will be set to the maximum value for
       the latest row update (which is what we care about).
     */
-    if (table->versioned() && (*ptr)->vers_sys_field() &&
+    if (table->versioned() && f->vers_sys_field() &&
         (table->s->primary_key < MAX_KEY ||
-         (vers_from_plain && table->vers_start_field() == (*ptr))))
+         (vers_from_plain && table->vers_start_field() == f)))
       continue;
-    /**
-      We only compare field contents that are not null.
-      NULL fields (i.e., their null bits) were compared 
-      earlier.
+
+    /*
+      We only compare fields that exist on the master (or in ONLINE
+      ALTER case, that were in the original table).
     */
-    if (!(*(ptr))->is_null())
+    if (!all_values_set)
     {
-      if ((*ptr)->cmp_binary_offset(table->s->rec_buff_length))
-      {
-        result= TRUE;
-        goto record_compare_exit;
-      }
+      if (!f->has_explicit_value() &&
+          /* Don't skip row_end if replicating unversioned -> versioned */
+          !(vers_from_plain && table->vers_end_field() == f))
+        continue;
+      if (f->is_null() != f->is_null(table->s->rec_buff_length))
+        goto record_compare_differ;
     }
+
+    if (!f->is_null() && !f->vcol_info &&
+        f->cmp_binary_offset(table->s->rec_buff_length))
+      goto record_compare_differ;
   }
 
 record_compare_exit:
   return result;
+record_compare_differ:
+  return true;
+}
+/**
+  Traverses default item expr of a field, and underlying field's default values.
+  If it is an extra field and has no value replicated, then its default expr
+  should be also checked.
+ */
+class Rpl_key_part_checker: public Field_enumerator
+{
+  bool online_alter;
+  Field *next_number_field;
+  bool field_usable;
+public:
+
+
+  void visit_field(Item_field *item) override
+  {
+    if (!field_usable)
+      return;
+    field_usable= check_field(item->field);
+  }
+
+  bool check_field(Field *f)
+  {
+    if (f->has_explicit_value())
+      return true;
+
+    if ((!f->vcol_info && !online_alter) || f == next_number_field)
+      return false;
+
+    Virtual_column_info *computed= f->vcol_info ? f->vcol_info
+                                   : f->default_value;
+
+    if (computed == NULL)
+      return true; // No DEFAULT, or constant DEFAULT
+
+    // Deterministic DEFAULT or vcol expression
+    return !(computed->flags & VCOL_NOT_STRICTLY_DETERMINISTIC)
+           && !computed->expr->walk(&Item::enumerate_field_refs_processor,
+                                    false, this)
+           && field_usable;
+  }
+
+  Rpl_key_part_checker(bool online_alter, Field *next_number_field):
+    online_alter(online_alter), next_number_field(next_number_field),
+    field_usable(true) {}
+};
+
+
+/**
+  Newly added fields with non-deterministic defaults (i.e. DEFAULT(RANDOM()),
+  CURRENT_TIMESTAMP, AUTO_INCREMENT) should be excluded from key search.
+  Basically we exclude all the default-filled fields based on
+  has_explicit_value bitmap.
+*/
+uint Rows_log_event::find_key_parts(const KEY *key) const
+{
+  RPL_TABLE_LIST *tl= (RPL_TABLE_LIST*)m_table->pos_in_table_list;
+  const bool online_alter= tl->m_online_alter_copy_fields;
+  uint p;
+
+  if (!m_table->s->keys_in_use.is_set(uint(key - m_table->key_info)))
+    return 0;
+
+  if (!online_alter)
+  {
+    if (m_cols.n_bits >= m_table->s->fields) // replicated more than slave has // deleted columns
+      return key->user_defined_key_parts;
+    if (m_table->s->virtual_fields == 0)
+    {
+      for (p= 0; p < key->user_defined_key_parts; p++)
+        if (key->key_part[p].fieldnr > m_cols.n_bits) // extra
+          break;
+      return p;
+    }
+  }
+
+  Rpl_key_part_checker key_part_checker(online_alter,
+                                        m_table->found_next_number_field);
+  for (p= 0; p < key->user_defined_key_parts; p++)
+  {
+    if (!key_part_checker.check_field(key->key_part[p].field))
+      break;
+  }
+  return p;
 }
 
 
@@ -7105,74 +7205,127 @@ static bool record_compare(TABLE *table, bool vers_from_plain= false)
   A primary key is preferred if it exists; otherwise a unique index is
   preferred. Else we pick the index with the smalles rec_per_key value.
 
-  If a suitable key is found, set @c m_key, @c m_key_nr and @c m_key_info
-  member fields appropriately.
+  If a suitable key is found, set @c m_key, @c m_key_nr, @c m_key_info,
+  and @c m_usable_key_parts member fields appropriately.
 
   @returns Error code on failure, 0 on success.
 */
-int Rows_log_event::find_key()
+int Rows_log_event::find_key(const rpl_group_info *rgi)
 {
-  uint i, best_key_nr, last_part;
-  KEY *key, *UNINIT_VAR(best_key);
+  DBUG_ASSERT(m_table);
+  RPL_TABLE_LIST *tl= (RPL_TABLE_LIST*)m_table->pos_in_table_list;
+  uint i, best_key_nr= 0, best_usable_key_parts= 0;
+  KEY *key;
   ulong UNINIT_VAR(best_rec_per_key), tmp;
   DBUG_ENTER("Rows_log_event::find_key");
-  DBUG_ASSERT(m_table);
-
-  best_key_nr= MAX_KEY;
 
-  /*
-    Keys are sorted so that any primary key is first, followed by unique keys,
-    followed by any other. So we will automatically pick the primary key if
-    it exists.
-  */
-  for (i= 0, key= m_table->key_info; i < m_table->s->keys; i++, key++)
+  if ((best_key_nr= tl->cached_key_nr) != ~0U)
   {
-    if (!m_table->s->keys_in_use.is_set(i))
-      continue;
+    DBUG_ASSERT(best_key_nr <= MAX_KEY); // use the cached value
+    best_usable_key_parts= tl->cached_usable_key_parts;
+  }
+  else
+  {
+    best_key_nr= MAX_KEY;
+
     /*
-      We cannot use a unique key with NULL-able columns to uniquely identify
-      a row (but we can still select it for range scan below if nothing better
-      is available).
+      if the source (in the row event) and destination (in m_table) records
+      don't have the same structure, some keys below might be unusable
+      for find_row().
+
+      If it's a replication and slave table (m_table) has less columns
+      than the master's - easy, all keys are usable.
+
+      If slave's table has more columns, but none of them are generated -
+      then any column beyond m_cols.n_bits makes an index unusable.
+
+      If slave's table has generated columns or it's the online alter table
+      where arbitrary structure conversion is possible (in the replication case
+      one table must be a prefix of the other, see table_def::compatible_with)
+      we cannot deduce what destination columns will be affected by m_cols,
+      we have to actually unpack one row and examine has_explicit_value()
     */
-    if ((key->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME)
+
+    if (tl->m_online_alter_copy_fields ||
+        (m_cols.n_bits < m_table->s->fields &&
+         m_table->s->virtual_fields))
     {
-      best_key_nr= i;
-      best_key= key;
-      break;
+      const uchar *curr_row_end= m_curr_row_end;
+      Check_level_instant_set clis(m_table->in_use, CHECK_FIELD_IGNORE);
+      if (int err= unpack_row(rgi, m_table, m_width, m_curr_row, &m_cols,
+                              &curr_row_end, &m_master_reclength, m_rows_end))
+        DBUG_RETURN(err);
     }
+
     /*
-      We can only use a non-unique key if it allows range scans (ie. skip
-      FULLTEXT indexes and such).
+      Keys are sorted so that any primary key is first, followed by unique keys,
+      followed by any other. So we will automatically pick the primary key if
+      it exists.
     */
-    last_part= key->user_defined_key_parts - 1;
-    DBUG_PRINT("info", ("Index %s rec_per_key[%u]= %lu",
-                        key->name.str, last_part, key->rec_per_key[last_part]));
-    if (!(m_table->file->index_flags(i, last_part, 1) & HA_READ_NEXT))
-      continue;
-
-    tmp= key->rec_per_key[last_part];
-    if (best_key_nr == MAX_KEY || (tmp > 0 && tmp < best_rec_per_key))
+    for (i= 0, key= m_table->key_info; i < m_table->s->keys; i++, key++)
     {
-      best_key_nr= i;
-      best_key= key;
-      best_rec_per_key= tmp;
+      uint usable_key_parts= find_key_parts(key);
+      if (usable_key_parts == 0)
+        continue;
+      /*
+        We cannot use a unique key with NULL-able columns to uniquely identify
+        a row (but we can still select it for range scan below if nothing better
+        is available).
+      */
+      if ((key->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME &&
+           usable_key_parts == key->user_defined_key_parts)
+      {
+        best_key_nr= i;
+        best_usable_key_parts= usable_key_parts;
+        break;
+      }
+      /*
+        We can only use a non-unique key if it allows range scans (ie. skip
+        FULLTEXT indexes and such).
+      */
+      uint last_part= usable_key_parts - 1;
+      DBUG_PRINT("info", ("Index %s rec_per_key[%u]= %lu",
+                          key->name.str, last_part, key->rec_per_key[last_part]));
+      if (!(m_table->file->index_flags(i, last_part, 1) & HA_READ_NEXT))
+        continue;
+
+      tmp= key->rec_per_key[last_part];
+      if (best_key_nr == MAX_KEY || (tmp > 0 && tmp < best_rec_per_key))
+      {
+        best_key_nr= i;
+        best_usable_key_parts= usable_key_parts;
+        best_rec_per_key= tmp;
+      }
     }
+    tl->cached_key_nr= best_key_nr;
+    tl->cached_usable_key_parts= best_usable_key_parts;
   }
 
+  m_key_nr= best_key_nr;
+  m_usable_key_parts= best_usable_key_parts;
   if (best_key_nr == MAX_KEY)
-  {
     m_key_info= NULL;
-    DBUG_RETURN(0);
+  else
+  {
+    m_key_info= m_table->key_info + best_key_nr;
+
+    if (!use_pk_position())
+    {
+      // Allocate buffer for key searches
+      m_key= (uchar *) my_malloc(PSI_INSTRUMENT_ME, m_key_info->key_length, MYF(MY_WME));
+      if (m_key == NULL)
+        DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+    }
   }
 
-  // Allocate buffer for key searches
-  m_key= (uchar *) my_malloc(PSI_INSTRUMENT_ME, best_key->key_length, MYF(MY_WME));
-  if (m_key == NULL)
-    DBUG_RETURN(HA_ERR_OUT_OF_MEM);
-  m_key_info= best_key;
-  m_key_nr= best_key_nr;
+  DBUG_EXECUTE_IF("rpl_report_chosen_key",
+                  push_warning_printf(m_table->in_use,
+                                      Sql_condition::WARN_LEVEL_NOTE,
+                                      ER_UNKNOWN_ERROR, "Key chosen: %d",
+                                      m_key_nr == MAX_KEY ?
+                                      -1 : m_key_nr););
 
-  DBUG_RETURN(0);;
+  DBUG_RETURN(0);
 }
 
 
@@ -7233,6 +7386,14 @@ static int row_not_found_error(rpl_group_info *rgi)
          ? HA_ERR_KEY_NOT_FOUND : HA_ERR_RECORD_CHANGED;
 }
 
+bool Rows_log_event::use_pk_position() const
+{
+  return m_table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION
+      && m_table->s->primary_key < MAX_KEY
+      && m_key_nr == m_table->s->primary_key
+      && m_usable_key_parts == m_table->key_info->user_defined_key_parts;
+}
+
 /**
   Locate the current row in event's table.
 
@@ -7268,11 +7429,13 @@ int Rows_log_event::find_row(rpl_group_info *rgi)
 {
   DBUG_ENTER("Rows_log_event::find_row");
 
-  DBUG_ASSERT(m_table && m_table->in_use != NULL);
+  DBUG_ASSERT(m_table);
+  DBUG_ASSERT(m_table->in_use != NULL);
 
   TABLE *table= m_table;
   int error= 0;
   bool is_table_scan= false, is_index_scan= false;
+  Check_level_instant_set clis(table->in_use, CHECK_FIELD_IGNORE);
 
   /*
     rpl_row_tabledefs.test specifies that
@@ -7303,8 +7466,7 @@ int Rows_log_event::find_row(rpl_group_info *rgi)
   DBUG_PRINT("info",("looking for the following record"));
   DBUG_DUMP("record[0]", table->record[0], table->s->reclength);
 
-  if ((table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) &&
-      table->s->primary_key < MAX_KEY)
+  if (use_pk_position())
   {
     /*
       Use a more efficient method to fetch the record given by
@@ -7325,7 +7487,6 @@ int Rows_log_event::find_row(rpl_group_info *rgi)
                                  table->s->reclength) == 0);
 
     */
-    int error;
     DBUG_PRINT("info",("locating record using primary key (position)"));
 
     error= table->file->ha_rnd_pos_by_record(table->record[0]);
@@ -7387,10 +7548,13 @@ int Rows_log_event::find_row(rpl_group_info *rgi)
       table->record[0][table->s->null_bytes - 1]|=
         256U - (1U << table->s->last_null_bit_pos);
 
-    if (unlikely((error= table->file->ha_index_read_map(table->record[0],
-                                                        m_key,
-                                                        HA_WHOLE_KEY,
-                                                        HA_READ_KEY_EXACT))))
+    const enum ha_rkey_function find_flag=
+      m_usable_key_parts == m_key_info->user_defined_key_parts
+      ? HA_READ_KEY_EXACT : HA_READ_KEY_OR_NEXT;
+    error= table->file->ha_index_read_map(table->record[0], m_key,
+                                          make_keypart_map(m_usable_key_parts),
+                                          find_flag);
+    if (unlikely(error))
     {
       DBUG_PRINT("info",("no record matching the key found in the table"));
       if (error == HA_ERR_KEY_NOT_FOUND)
@@ -7422,10 +7586,10 @@ int Rows_log_event::find_row(rpl_group_info *rgi)
       found.  I can see no scenario where it would be incorrect to
       chose the row to change only using a PK or an UNNI.
     */
-    if (table->key_info->flags & HA_NOSAME)
+    if (find_flag == HA_READ_KEY_EXACT && table->key_info->flags & HA_NOSAME)
     {
       /* Unique does not have non nullable part */
-      if (!(table->key_info->flags & (HA_NULL_PART_KEY)))
+      if (!(table->key_info->flags & HA_NULL_PART_KEY))
       {
         error= 0;
         goto end;
@@ -7496,9 +7660,7 @@ int Rows_log_event::find_row(rpl_group_info *rgi)
     /* Continue until we find the right record or have made a full loop */
     do
     {
-      error= table->file->ha_rnd_next(table->record[0]);
-
-      if (unlikely(error))
+      if (unlikely((error= table->file->ha_rnd_next(table->record[0]))))
         DBUG_PRINT("info", ("error: %s", HA_ERR(error)));
       switch (error) {
 
@@ -7567,7 +7729,7 @@ bool Delete_rows_compressed_log_event::write()
 #if defined(HAVE_REPLICATION)
 
 int 
-Delete_rows_log_event::do_before_row_operations(const Slave_reporting_capability *const)
+Delete_rows_log_event::do_before_row_operations(const rpl_group_info *rgi)
 {
   /*
     Increment the global status delete count variable
@@ -7575,23 +7737,14 @@ Delete_rows_log_event::do_before_row_operations(const Slave_reporting_capability
   if (get_flags(STMT_END_F))
     status_var_increment(thd->status_var.com_stat[SQLCOM_DELETE]);
 
-  if ((m_table->file->ha_table_flags() & HA_PRIMARY_KEY_REQUIRED_FOR_POSITION) &&
-      m_table->s->primary_key < MAX_KEY)
-  {
-    /*
-      We don't need to allocate any memory for m_key since it is not used.
-    */
-    return 0;
-  }
   if (do_invoke_trigger())
     m_table->prepare_triggers_for_delete_stmt_or_event();
 
-  return find_key();
+  return find_key(rgi);
 }
 
 int 
-Delete_rows_log_event::do_after_row_operations(const Slave_reporting_capability *const, 
-                                               int error)
+Delete_rows_log_event::do_after_row_operations(int error)
 {
   m_table->file->ha_index_or_rnd_end();
   my_free(m_key);
@@ -7662,7 +7815,6 @@ int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
       {
         error= m_table->file->ha_delete_row(m_table->record[0]);
       }
-      m_table->default_column_bitmaps();
     }
     if (invoke_triggers && likely(!error) &&
         unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE)))
@@ -7676,7 +7828,7 @@ int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
 #endif /* defined(HAVE_REPLICATION) */
 
 #if defined(HAVE_REPLICATION)
-uint8 Delete_rows_log_event::get_trg_event_map()
+uint8 Delete_rows_log_event::get_trg_event_map() const
 {
   return trg2bit(TRG_EVENT_DELETE);
 }
@@ -7731,7 +7883,7 @@ void Update_rows_log_event::init(MY_BITMAP const *cols)
 #if defined(HAVE_REPLICATION)
 
 int 
-Update_rows_log_event::do_before_row_operations(const Slave_reporting_capability *const)
+Update_rows_log_event::do_before_row_operations(const rpl_group_info *rgi)
 {
   /*
     Increment the global status update count variable
@@ -7740,7 +7892,7 @@ Update_rows_log_event::do_before_row_operations(const Slave_reporting_capability
     status_var_increment(thd->status_var.com_stat[SQLCOM_UPDATE]);
 
   int err;
-  if ((err= find_key()))
+  if ((err= find_key(rgi)))
     return err;
 
   if (do_invoke_trigger())
@@ -7750,8 +7902,7 @@ Update_rows_log_event::do_before_row_operations(const Slave_reporting_capability
 }
 
 int 
-Update_rows_log_event::do_after_row_operations(const Slave_reporting_capability *const, 
-                                               int error)
+Update_rows_log_event::do_after_row_operations(int error)
 {
   /*error= ToDo:find out what this should really be, this triggers close_scan in nbd, returning error?*/
   m_table->file->ha_index_or_rnd_end();
@@ -7893,7 +8044,7 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
 
 
 #if defined(HAVE_REPLICATION)
-uint8 Update_rows_log_event::get_trg_event_map()
+uint8 Update_rows_log_event::get_trg_event_map() const
 {
   return trg2bit(TRG_EVENT_UPDATE);
 }
diff --git a/sql/records.cc b/sql/records.cc
index 5b2ebefe14f..eb4d5f4d958 100644
--- a/sql/records.cc
+++ b/sql/records.cc
@@ -197,8 +197,7 @@ bool init_read_record(READ_RECORD *info,THD *thd, TABLE *table,
   info->table=table;
   info->sort_info= filesort;
   
-  if ((table->s->tmp_table == INTERNAL_TMP_TABLE) &&
-      !using_addon_fields)
+  if ((table->s->tmp_table == INTERNAL_TMP_TABLE) && !using_addon_fields)
     (void) table->file->extra(HA_EXTRA_MMAP);
   
   if (using_addon_fields)
diff --git a/sql/rpl_record.cc b/sql/rpl_record.cc
index 1a9c02a167b..6098dc7daff 100644
--- a/sql/rpl_record.cc
+++ b/sql/rpl_record.cc
@@ -144,7 +144,132 @@ pack_row(TABLE *table, MY_BITMAP const* cols,
 #endif
 
 #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
-static int fill_extra_persistent_columns(TABLE *table, int master_cols);
+
+struct Unpack_record_state
+{
+  uchar const *const row_data;
+  uchar const *const row_end;
+  size_t const master_null_byte_count;
+  uchar const *null_ptr;
+  uchar const *pack_ptr;
+  /** Mask to mask out the correct bit among the null bits */
+  unsigned int null_mask;
+  /** The "current" null bits */
+  unsigned int null_bits;
+  Unpack_record_state(uchar const *const row_data,
+                      uchar const *const row_end,
+                      size_t const master_null_byte_count)
+  : row_data(row_data), row_end(row_end),
+    master_null_byte_count(master_null_byte_count),
+    null_ptr(row_data), pack_ptr(row_data + master_null_byte_count)
+  {}
+  void next_null_byte()
+  {
+    DBUG_ASSERT(null_ptr < row_data + master_null_byte_count);
+    null_mask= 1U;
+    null_bits= *null_ptr++;
+  }
+};
+
+
+static bool unpack_field(const table_def *tabledef, Field *f,
+                         Unpack_record_state *st, uint field_idx)
+{
+  if ((st->null_mask & 0xFF) == 0)
+    st->next_null_byte();
+
+  DBUG_ASSERT(st->null_mask & 0xFF); // One of the 8 LSB should be set
+
+  if (st->null_bits & st->null_mask)
+  {
+    if (f->maybe_null())
+    {
+      DBUG_PRINT("debug", ("Was NULL; null mask: 0x%x; null bits: 0x%x",
+                 st->null_mask, st->null_bits));
+      /**
+        Calling reset just in case one is unpacking on top a
+        record with data.
+
+        This could probably go into set_null() but doing so,
+        (i) triggers assertion in other parts of the code at
+        the moment; (ii) it would make us reset the field,
+        always when setting null, which right now doesn't seem
+        needed anywhere else except here.
+
+        TODO: maybe in the future we should consider moving
+              the reset to make it part of set_null. But then
+              the assertions triggered need to be
+              addressed/revisited.
+       */
+      f->reset();
+      f->set_null();
+    }
+    else
+    {
+      THD *thd= f->table->in_use;
+
+      f->set_default();
+      push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
+                          ER_BAD_NULL_ERROR,
+                          ER_THD(thd, ER_BAD_NULL_ERROR),
+                          f->field_name.str);
+    }
+  }
+  else
+  {
+    f->set_notnull();
+
+    /*
+      We only unpack the field if it was non-null.
+      Use the master's size information if available else call
+      normal unpack operation.
+    */
+    uint16 const metadata = tabledef->field_metadata(field_idx);
+#ifdef DBUG_TRACE
+    uchar const *const old_pack_ptr= st->pack_ptr;
+#endif
+
+    st->pack_ptr= f->unpack(f->ptr, st->pack_ptr, st->row_end, metadata);
+    DBUG_PRINT("debug", ("field: %s; metadata: 0x%x;"
+                         " pack_ptr: %p; pack_ptr': %p; bytes: %d",
+                         f->field_name.str, metadata,
+                         old_pack_ptr, st->pack_ptr,
+                         (int) (st->pack_ptr - old_pack_ptr)));
+    if (!st->pack_ptr)
+      return false;
+  }
+  st->null_mask <<= 1;
+  return true;
+}
+
+static void convert_field(Field *f, Field *result_field, Field *conv_field)
+{
+#ifndef DBUG_OFF
+  char type_buf[MAX_FIELD_WIDTH];
+  char value_buf[MAX_FIELD_WIDTH];
+  String source_type(type_buf, sizeof(type_buf), system_charset_info);
+  String value_string(value_buf, sizeof(value_buf), system_charset_info);
+  conv_field->sql_type(source_type);
+  conv_field->val_str(&value_string);
+  DBUG_PRINT("debug", ("Copying field '%s' of type '%s' with value '%s'",
+                       result_field->field_name.str,
+                       source_type.c_ptr_safe(), value_string.c_ptr_safe()));
+#endif
+
+  Copy_field copy;
+  copy.set(result_field, f, TRUE);
+  (*copy.do_copy)(&copy);
+
+#ifndef DBUG_OFF
+  String target_type(type_buf, sizeof(type_buf), system_charset_info);
+  result_field->sql_type(target_type);
+  result_field->val_str(&value_string);
+  DBUG_PRINT("debug", ("Value of field '%s' of type '%s' is now '%s'",
+                       result_field->field_name.str,
+                       target_type.c_ptr_safe(), value_string.c_ptr_safe()));
+#endif
+}
+
 
 /**
    Unpack a row into @c table->record[0].
@@ -167,11 +292,11 @@ static int fill_extra_persistent_columns(TABLE *table, int master_cols);
    unpack a row from from the backup image, but can be used for other
    purposes as well.
 
-   @param rli     Relay log info, which can be NULL
+   @param rgi     Relay group info
    @param table   Table to unpack into
    @param colcnt  Number of columns to read from record
    @param row_data
-                  Packed row data
+                  Packed row datanull_ptr
    @param cols    Pointer to bitset describing columns to fill in
    @param curr_row_end
                   Pointer to variable that will hold the value of the
@@ -190,25 +315,18 @@ static int fill_extra_persistent_columns(TABLE *table, int master_cols);
    @retval HA_ERR_CORRUPT_EVENT
    Found error when trying to unpack fields.
  */
-int
-unpack_row(rpl_group_info *rgi,
-           TABLE *table, uint const colcnt,
-           uchar const *const row_data, MY_BITMAP const *cols,
-           uchar const **const current_row_end, ulong *const master_reclength,
-           uchar const *const row_end)
+int unpack_row(const rpl_group_info *rgi, TABLE *table, uint const colcnt,
+               uchar const *const row_data, MY_BITMAP const *cols,
+               uchar const **const current_row_end,
+               ulong *const master_reclength, uchar const *const row_end)
 {
   int error;
   DBUG_ENTER("unpack_row");
   DBUG_ASSERT(row_data);
   DBUG_ASSERT(table);
-  size_t const master_null_byte_count= (bitmap_bits_set(cols) + 7) / 8;
+  DBUG_ASSERT(rgi);
 
-  uchar const *null_ptr= row_data;
-  uchar const *pack_ptr= row_data + master_null_byte_count;
-
-  Field **const begin_ptr = table->field;
-  Field **field_ptr;
-  Field **const end_ptr= begin_ptr + colcnt;
+  Unpack_record_state st(row_data, row_end, (bitmap_bits_set(cols) + 7) / 8);
 
   if (bitmap_is_clear_all(cols))
   {
@@ -216,129 +334,55 @@ unpack_row(rpl_group_info *rgi,
        There was no data sent from the master, so there is
        nothing to unpack.
      */
-    *current_row_end= pack_ptr;
+    *current_row_end= st.pack_ptr;
     *master_reclength= 0;
     DBUG_RETURN(0);
   }
-  DBUG_ASSERT(null_ptr < row_data + master_null_byte_count);
 
-  // Mask to mask out the correct bit among the null bits
-  unsigned int null_mask= 1U;
-  // The "current" null bits
-  unsigned int null_bits= *null_ptr++;
+  Rpl_table_data rpl_data= *(RPL_TABLE_LIST*)table->pos_in_table_list;
+  const table_def *tabledef= rpl_data.tabledef;
+  const TABLE *conv_table= rpl_data.conv_table;
+  DBUG_PRINT("debug", ("Table data: tabldef: %p, conv_table: %p",
+                       tabledef, conv_table));
   uint i= 0;
-  table_def *tabledef= NULL;
-  TABLE *conv_table= NULL;
-  bool table_found= rgi && rgi->get_table_data(table, &tabledef, &conv_table);
-  DBUG_PRINT("debug", ("Table data: table_found: %d, tabldef: %p, conv_table: %p",
-                       table_found, tabledef, conv_table));
-  DBUG_ASSERT(table_found);
 
-  /*
-    If rgi is NULL it means that there is no source table and that the
-    row shall just be unpacked without doing any checks. This feature
-    is used by MySQL Backup, but can be used for other purposes as
-    well.
-   */
-  if (rgi && !table_found)
-    DBUG_RETURN(HA_ERR_GENERIC);
-
-  for (field_ptr= begin_ptr ; field_ptr < end_ptr && *field_ptr ; ++field_ptr)
+  st.next_null_byte();
+  if (!rpl_data.is_online_alter())
   {
-    /*
-      If there is a conversion table, we pick up the field pointer to
-      the conversion table.  If the conversion table or the field
-      pointer is NULL, no conversions are necessary.
-     */
-    Field *conv_field=
-      conv_table ? conv_table->field[field_ptr - begin_ptr] : NULL;
-    Field *const f=
-      conv_field ? conv_field : *field_ptr;
-    DBUG_PRINT("debug", ("Conversion %srequired for field '%s' (#%ld)",
-                         conv_field ? "" : "not ",
-                         (*field_ptr)->field_name.str,
-                         (long) (field_ptr - begin_ptr)));
-    DBUG_ASSERT(f != NULL);
-
-    /*
-      No need to bother about columns that does not exist: they have
-      gotten default values when being emptied above.
-     */
-    if (bitmap_is_set(cols, (uint)(field_ptr -  begin_ptr)))
+    Field *result_field;
+    for (; i < colcnt && (result_field= table->field[i]); i++)
     {
-      if ((null_mask & 0xFF) == 0)
-      {
-        DBUG_ASSERT(null_ptr < row_data + master_null_byte_count);
-        null_mask= 1U;
-        null_bits= *null_ptr++;
-      }
-
-      DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set
+      /*
+        If there is a conversion table, we pick up the field pointer to
+        the conversion table.  If the conversion table or the field
+        pointer is NULL, no conversions are necessary.
+       */
+      Field *conv_field= conv_table ? conv_table->field[i] : NULL;
+      Field *const f= conv_field ? conv_field : result_field;
+
+      DBUG_PRINT("debug", ("Conversion %srequired for field '%s' (#%u)",
+                           conv_field ? "" : "not ",
+                           result_field->field_name.str, i));
+      DBUG_ASSERT(f != NULL);
 
-      if (null_bits & null_mask)
-      {
-        if (f->maybe_null())
-        {
-          DBUG_PRINT("debug", ("Was NULL; null mask: 0x%x; null bits: 0x%x",
-                               null_mask, null_bits));
-          /** 
-            Calling reset just in case one is unpacking on top a 
-            record with data. 
-
-            This could probably go into set_null() but doing so, 
-            (i) triggers assertion in other parts of the code at 
-            the moment; (ii) it would make us reset the field,
-            always when setting null, which right now doesn't seem 
-            needed anywhere else except here.
-
-            TODO: maybe in the future we should consider moving 
-                  the reset to make it part of set_null. But then
-                  the assertions triggered need to be 
-                  addressed/revisited.
-           */
-          f->reset();
-          f->set_null();
-        }
-        else
-        {
-          THD *thd= f->table->in_use;
-
-          f->set_default();
-          push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
-                              ER_BAD_NULL_ERROR,
-                              ER_THD(thd, ER_BAD_NULL_ERROR),
-                              f->field_name.str);
-        }
-      }
-      else
-      {
-        f->set_notnull();
+      /*
+        No need to bother about columns that does not exist: they have
+        gotten default values when being emptied above.
+       */
+      if (!bitmap_is_set(cols, i))
+        continue;
 
-        /*
-          We only unpack the field if it was non-null.
-          Use the master's size information if available else call
-          normal unpack operation.
-        */
-        uint16 const metadata= tabledef->field_metadata(i);
-#ifdef DBUG_TRACE
-        uchar const *const old_pack_ptr= pack_ptr;
-#endif
+      result_field->set_has_explicit_value();
 
-        pack_ptr= f->unpack(f->ptr, pack_ptr, row_end, metadata);
-	DBUG_PRINT("debug", ("field: %s; metadata: 0x%x;"
-                             " pack_ptr: %p; pack_ptr': %p; bytes: %d",
-                             f->field_name.str, metadata,
-                             old_pack_ptr, pack_ptr,
-                             (int) (pack_ptr - old_pack_ptr)));
-        if (!pack_ptr)
-        {
-          rgi->rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT,
-                      rgi->gtid_info(),
-                      "Could not read field '%s' of table '%s.%s'",
-                      f->field_name.str, table->s->db.str,
-                      table->s->table_name.str);
-          DBUG_RETURN(HA_ERR_CORRUPT_EVENT);
-        }
+      bool unpack_result= unpack_field(tabledef, f, &st, i);
+      if (!unpack_result)
+      {
+        rgi->rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT,
+                    rgi->gtid_info(),
+                    "Could not read field '%s' of table '%s.%s'",
+                    f->field_name.str, table->s->db.str,
+                    table->s->table_name.str);
+        DBUG_RETURN(HA_ERR_CORRUPT_EVENT);
       }
 
       /*
@@ -346,87 +390,102 @@ unpack_row(rpl_group_info *rgi,
         case, we have unpacked the master data to the conversion
         table, so we need to copy the value stored in the conversion
         table into the final table and do the conversion at the same time.
-      */
-      if (conv_field)
-      {
-        Copy_field copy;
-#ifndef DBUG_OFF
-        char source_buf[MAX_FIELD_WIDTH];
-        char value_buf[MAX_FIELD_WIDTH];
-        String source_type(source_buf, sizeof(source_buf), system_charset_info);
-        String value_string(value_buf, sizeof(value_buf), system_charset_info);
-        conv_field->sql_type(source_type);
-        conv_field->val_str(&value_string);
-        DBUG_PRINT("debug", ("Copying field '%s' of type '%s' with value '%s'",
-                             (*field_ptr)->field_name.str,
-                             source_type.c_ptr_safe(), value_string.c_ptr_safe()));
-#endif
-        copy.set(*field_ptr, f, TRUE);
-        (*copy.do_copy)(&copy);
-#ifndef DBUG_OFF
-        char target_buf[MAX_FIELD_WIDTH];
-        String target_type(target_buf, sizeof(target_buf), system_charset_info);
-        (*field_ptr)->sql_type(target_type);
-        (*field_ptr)->val_str(&value_string);
-        DBUG_PRINT("debug", ("Value of field '%s' of type '%s' is now '%s'",
-                             (*field_ptr)->field_name.str,
-                             target_type.c_ptr_safe(), value_string.c_ptr_safe()));
-#endif
-      }
 
-      null_mask <<= 1;
+        If copy_fields is set, it means we are doing an online alter table,
+        and will use copy_fields set up in copy_data_between_tables
+       */
+      if (conv_field)
+        convert_field(f, result_field, conv_field);
     }
-    i++;
-  }
 
-  /*
-    throw away master's extra fields
-  */
-  uint max_cols= MY_MIN(tabledef->size(), cols->n_bits);
-  for (; i < max_cols; i++)
-  {
-    if (bitmap_is_set(cols, i))
+    /*
+      Throw away master's extra fields
+     */
+    uint max_cols= MY_MIN(tabledef->size(), cols->n_bits);
+    for (; i < max_cols; i++)
     {
-      if ((null_mask & 0xFF) == 0)
+      if (bitmap_is_set(cols, i))
       {
-        DBUG_ASSERT(null_ptr < row_data + master_null_byte_count);
-        null_mask= 1U;
-        null_bits= *null_ptr++;
+        if ((st.null_mask & 0xFF) == 0)
+          st.next_null_byte();
+        DBUG_ASSERT(st.null_mask & 0xFF); // One of the 8 LSB should be set
+
+        if (!((st.null_bits & st.null_mask) && tabledef->maybe_null(i))) {
+          uint32 len= tabledef->calc_field_size(i, (uchar *) st.pack_ptr);
+          DBUG_DUMP("field_data", st.pack_ptr, len);
+          st.pack_ptr+= len;
+        }
+        st.null_mask <<= 1;
       }
-      DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set
+    }
 
-      if (!((null_bits & null_mask) && tabledef->maybe_null(i))) {
-        uint32 len= tabledef->calc_field_size(i, (uchar *) pack_ptr);
-        DBUG_DUMP("field_data", pack_ptr, len);
-        pack_ptr+= len;
-      }
-      null_mask <<= 1;
+    if (master_reclength)
+    {
+      if (result_field)
+        *master_reclength = (ulong)(result_field->ptr - table->record[0]);
+      else
+        *master_reclength = table->s->reclength;
     }
   }
+  else
+  {
+    /*
+      For Online Alter, iterate through old table fields to unpack,
+      then iterate through copy_field array to copy to the new table's record.
+     */
 
-  /*
-    Add Extra slave persistent columns
-  */
-  if (unlikely(error= fill_extra_persistent_columns(table, cols->n_bits)))
-    DBUG_RETURN(error);
+    DBUG_ASSERT(colcnt == conv_table->s->fields);
+    for (;i < colcnt; i++)
+    {
+      DBUG_ASSERT(bitmap_is_set(cols, i));
+      Field *f= conv_table->field[i];
+      bool result= unpack_field(tabledef, f, &st, i);
+      DBUG_ASSERT(result);
+    }
+
+    for (const auto *copy=rpl_data.copy_fields;
+         copy != rpl_data.copy_fields_end; copy++)
+    {
+      copy->to_field->set_has_explicit_value();
+      copy->do_copy(copy);
+    }
+    if (master_reclength)
+      *master_reclength = conv_table->s->reclength;
+  } // if (rpl_data.is_online_alter())
 
   /*
     We should now have read all the null bytes, otherwise something is
     really wrong.
    */
-  DBUG_ASSERT(null_ptr == row_data + master_null_byte_count);
+  DBUG_ASSERT(st.null_ptr == row_data + st.master_null_byte_count);
+  DBUG_DUMP("row_data", row_data, st.pack_ptr - row_data);
 
-  DBUG_DUMP("row_data", row_data, pack_ptr - row_data);
+  *current_row_end = st.pack_ptr;
 
-  *current_row_end = pack_ptr;
-  if (master_reclength)
+  if (table->default_field && (rpl_data.is_online_alter() ||
+      LOG_EVENT_IS_WRITE_ROW(rgi->current_event->get_type_code())))
   {
-    if (*field_ptr)
-      *master_reclength = (ulong)((*field_ptr)->ptr - table->record[0]);
-    else
-      *master_reclength = table->s->reclength;
+    error= table->update_default_fields(table->in_use->lex->ignore);
+    if (unlikely(error))
+      DBUG_RETURN(error);
+  }
+  if (table->vfield)
+  {
+    error= table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE);
+    if (unlikely(error))
+      DBUG_RETURN(error);
   }
-  
+
+  if (rpl_data.is_online_alter())
+  {
+    /* we only check constraints for ALTER TABLE */
+    DBUG_ASSERT(table->in_use->lex->ignore == FALSE);
+    error = table->verify_constraints(false);
+    DBUG_ASSERT(error != VIEW_CHECK_SKIP);
+    if (error)
+      DBUG_RETURN(HA_ERR_GENERIC);
+  }
+
   DBUG_RETURN(0);
 }
 
@@ -483,31 +542,4 @@ int prepare_record(TABLE *const table, const uint skip, const bool check)
 
   DBUG_RETURN(0);
 }
-/**
-  Fills @c table->record[0] with computed values of extra  persistent  column
-  which are present on slave but not on master.
-
-  @param table         Table whose record[0] buffer is prepared.
-  @param master_cols   No of columns on master 
-  @returns 0 on        success
- */
-static int fill_extra_persistent_columns(TABLE *table, int master_cols)
-{
-  int error= 0;
-  Field **vfield_ptr, *vfield;
-
-  if (!table->vfield)
-    return 0;
-  for (vfield_ptr= table->vfield; *vfield_ptr; ++vfield_ptr)
-  {
-    vfield= *vfield_ptr;
-    if (vfield->field_index >= master_cols && (vfield->stored_in_db() ||
-               (vfield->flags & (PART_KEY_FLAG | PART_INDIRECT_KEY_FLAG))))
-    {
-      bitmap_set_bit(table->write_set, vfield->field_index);
-      error= vfield->vcol_info->expr->save_in_field(vfield,0);
-    }
-  }
-  return error;
-}
 #endif // HAVE_REPLICATION
diff --git a/sql/rpl_record.h b/sql/rpl_record.h
index e93cfb63193..0f486c51bda 100644
--- a/sql/rpl_record.h
+++ b/sql/rpl_record.h
@@ -29,7 +29,7 @@ size_t pack_row(TABLE* table, MY_BITMAP const* cols,
 #endif
 
 #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
-int unpack_row(rpl_group_info *rgi,
+int unpack_row(const rpl_group_info *rgi,
                TABLE *table, uint const colcnt,
                uchar const *const row_data, MY_BITMAP const *cols,
                uchar const **const curr_row_end, ulong *const master_reclength,
diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc
index bd55c14e447..da018d0e6fb 100644
--- a/sql/rpl_rli.cc
+++ b/sql/rpl_rli.cc
@@ -2473,7 +2473,7 @@ rpl_group_info::mark_start_commit()
   If no GTID is available, then NULL is returned.
 */
 char *
-rpl_group_info::gtid_info()
+rpl_group_info::gtid_info() const
 {
   if (!gtid_sub_id || !current_gtid.seq_no)
     return NULL;
diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h
index 0fd9070426b..6d6c098847b 100644
--- a/sql/rpl_rli.h
+++ b/sql/rpl_rli.h
@@ -669,6 +669,22 @@ struct start_alter_info
   mysql_cond_t start_alter_cond;
 };
 
+struct Rpl_table_data
+{
+  const table_def *tabledef;
+  TABLE *conv_table;
+  const Copy_field *copy_fields;
+  const Copy_field *copy_fields_end;
+  Rpl_table_data(const RPL_TABLE_LIST &rpl_table_list)
+  {
+    tabledef= &rpl_table_list.m_tabledef;
+    conv_table= rpl_table_list.m_conv_table;
+    copy_fields= rpl_table_list.m_online_alter_copy_fields;
+    copy_fields_end= rpl_table_list.m_online_alter_copy_fields_end;
+  }
+  bool is_online_alter() const { return copy_fields != NULL; }
+};
+
 /*
   This is data for various state needed to be kept for the processing of
   one event group (transaction) during replication.
@@ -814,7 +830,7 @@ struct rpl_group_info
   longlong row_stmt_start_timestamp;
   bool long_find_row_note_printed;
   /* Needs room for "Gtid D-S-N\x00". */
-  char gtid_info_buf[5+10+1+10+1+20+1];
+  mutable char gtid_info_buf[5+10+1+10+1+20+1];
 
   /*
     The timestamp, from the master, of the commit event.
@@ -941,29 +957,12 @@ struct rpl_group_info
     }
   }
 
-  bool get_table_data(TABLE *table_arg, table_def **tabledef_var, TABLE **conv_table_var) const
-  {
-    DBUG_ASSERT(tabledef_var && conv_table_var);
-    for (TABLE_LIST *ptr= tables_to_lock ; ptr != NULL ; ptr= ptr->next_global)
-      if (ptr->table == table_arg)
-      {
-        *tabledef_var= &static_cast<RPL_TABLE_LIST*>(ptr)->m_tabledef;
-        *conv_table_var= static_cast<RPL_TABLE_LIST*>(ptr)->m_conv_table;
-        DBUG_PRINT("debug", ("Fetching table data for table %s.%s:"
-                             " tabledef: %p, conv_table: %p",
-                             table_arg->s->db.str, table_arg->s->table_name.str,
-                             *tabledef_var, *conv_table_var));
-        return true;
-      }
-    return false;
-  }
-
   void clear_tables_to_lock();
   void cleanup_context(THD *, bool);
   void slave_close_thread_tables(THD *);
   void mark_start_commit_no_lock();
   void mark_start_commit();
-  char *gtid_info();
+  char *gtid_info() const;
   void unmark_start_commit();
 
   longlong get_row_stmt_start_timestamp()
diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h
index ec74fd26dc0..92a2c00e2ec 100644
--- a/sql/rpl_utility.h
+++ b/sql/rpl_utility.h
@@ -43,6 +43,7 @@ struct rpl_group_info;
 
 class table_def
 {
+  table_def(const table_def&) = default;
 public:
   /**
     Constructor.
@@ -56,6 +57,18 @@ class table_def
   table_def(unsigned char *types, ulong size, uchar *field_metadata,
             int metadata_size, uchar *null_bitmap, uint16 flags);
 
+
+  /**
+    Move constructor
+    Since it deallocates a memory during destruction, we can't safely copy it.
+    We should instead move it to zero m_memory in an old object
+   */
+  table_def(table_def &&tabledef)
+  : table_def(tabledef)
+  {
+    tabledef.m_memory= NULL;
+  }
+
   ~table_def();
 
   /**
@@ -236,13 +249,37 @@ class table_def
    Extend the normal table list with a few new fields needed by the
    slave thread, but nowhere else.
  */
-struct RPL_TABLE_LIST
-  : public TABLE_LIST
+struct RPL_TABLE_LIST : public TABLE_LIST
 {
-  bool m_tabledef_valid;
   table_def m_tabledef;
   TABLE *m_conv_table;
+  const Copy_field *m_online_alter_copy_fields;
+  const Copy_field *m_online_alter_copy_fields_end;
+  uint cached_key_nr;                  // [0..MAX_KEY] if set, ~0U if unset
+  uint cached_usable_key_parts;
+  bool m_tabledef_valid;
   bool master_had_triggers;
+
+  RPL_TABLE_LIST(const LEX_CSTRING *db_arg, const LEX_CSTRING *table_name_arg,
+                 thr_lock_type thr_lock_type,
+                 table_def &&tabledef, bool master_had_trigers)
+    : TABLE_LIST(db_arg, table_name_arg, NULL, thr_lock_type),
+      m_tabledef(std::move(tabledef)), m_conv_table(NULL),
+      m_online_alter_copy_fields(NULL), m_online_alter_copy_fields_end(NULL),
+      cached_key_nr(~0U), m_tabledef_valid(true),
+      master_had_triggers(master_had_trigers)
+  {}
+
+  RPL_TABLE_LIST(TABLE *table, thr_lock_type lock_type, TABLE *conv_table,
+                 table_def &&tabledef,
+                 const Copy_field online_alter_copy_fields[],
+                 const Copy_field *online_alter_copy_fields_end)
+    : TABLE_LIST(table, lock_type),
+      m_tabledef(std::move(tabledef)), m_conv_table(conv_table),
+      m_online_alter_copy_fields(online_alter_copy_fields),
+      m_online_alter_copy_fields_end(online_alter_copy_fields_end),
+      cached_key_nr(~0U), m_tabledef_valid(true), master_had_triggers(false)
+  {}
 };
 
 
diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc
index 5959203b36d..78a43945557 100644
--- a/sql/sql_alter.cc
+++ b/sql/sql_alter.cc
@@ -177,8 +177,8 @@ bool Alter_info::supports_algorithm(THD *thd,
 }
 
 
-bool Alter_info::supports_lock(THD *thd,
-                               const Alter_inplace_info *ha_alter_info)
+bool Alter_info::supports_lock(THD *thd, bool online,
+                               Alter_inplace_info *ha_alter_info)
 {
   switch (ha_alter_info->inplace_supported) {
   case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
@@ -207,8 +207,13 @@ bool Alter_info::supports_lock(THD *thd,
   case HA_ALTER_INPLACE_SHARED_LOCK:
     if (requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
     {
-      ha_alter_info->report_unsupported_error("LOCK=NONE", "LOCK=SHARED");
-      return true;
+      if (online)
+        ha_alter_info->inplace_supported= HA_ALTER_INPLACE_NOT_SUPPORTED;
+      else
+      {
+        ha_alter_info->report_unsupported_error("LOCK=NONE", "LOCK=SHARED");
+        return true;
+      }
     }
     return false;
   case HA_ALTER_ERROR:
@@ -255,6 +260,14 @@ Alter_info::algorithm(const THD *thd) const
   return requested_algorithm;
 }
 
+bool Alter_info::algorithm_is_nocopy(const THD *thd) const
+{
+  auto alg= algorithm(thd);
+  return alg == ALTER_TABLE_ALGORITHM_INPLACE
+         || alg == ALTER_TABLE_ALGORITHM_INSTANT
+         || alg == ALTER_TABLE_ALGORITHM_NOCOPY;
+}
+
 
 Alter_table_ctx::Alter_table_ctx()
   : db(null_clex_str), table_name(null_clex_str), alias(null_clex_str),
diff --git a/sql/sql_alter.h b/sql/sql_alter.h
index 99e717d50b2..1220db18924 100644
--- a/sql/sql_alter.h
+++ b/sql/sql_alter.h
@@ -226,13 +226,14 @@ class Alter_info
      @retval false  Supported lock type
      @retval true   Not supported value
   */
-  bool supports_lock(THD *thd, const Alter_inplace_info *ha_alter_info);
+  bool supports_lock(THD *thd, bool, Alter_inplace_info *ha_alter_info);
 
   /**
     Return user requested algorithm. If user does not specify
     algorithm then return alter_algorithm variable value.
    */
   enum_alter_table_algorithm algorithm(const THD *thd) const;
+  bool algorithm_is_nocopy(const THD *thd) const;
 
 private:
   Alter_info &operator=(const Alter_info &rhs); // not implemented
diff --git a/sql/sql_cache.h b/sql/sql_cache.h
index a02034764a7..30522e9e8ff 100644
--- a/sql/sql_cache.h
+++ b/sql/sql_cache.h
@@ -584,6 +584,8 @@ struct Query_cache_query_flags
   query_cache.send_result_to_client(A, B, C)
 #define query_cache_invalidate_by_MyISAM_filename_ref \
   &query_cache_invalidate_by_MyISAM_filename
+#define query_cache_invalidate_locked_for_write(A, B) \
+  query_cache.invalidate_locked_for_write(A, B)
 /* note the "maybe": it's a read without mutex */
 #define query_cache_maybe_disabled(T)                                 \
   (T->variables.query_cache_type == 0 || query_cache.query_cache_size == 0)
@@ -601,6 +603,7 @@ struct Query_cache_query_flags
 #define query_cache_invalidate1(A,B)      do { } while(0)
 #define query_cache_send_result_to_client(A, B, C) 0
 #define query_cache_invalidate_by_MyISAM_filename_ref NULL
+#define query_cache_invalidate_locked_for_write(A, B) do { } while(0)
 
 #define query_cache_abort(A,B)            do { } while(0)
 #define query_cache_end_of_result(A)      do { } while(0)
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 18e796655bd..ac631a64b08 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -1138,31 +1138,31 @@ Sql_condition* THD::raise_condition(const Sql_condition *cond)
 }
 
 extern "C"
-void *thd_alloc(MYSQL_THD thd, size_t size)
+void *thd_alloc(const MYSQL_THD thd, size_t size)
 {
   return thd->alloc(size);
 }
 
 extern "C"
-void *thd_calloc(MYSQL_THD thd, size_t size)
+void *thd_calloc(const MYSQL_THD thd, size_t size)
 {
   return thd->calloc(size);
 }
 
 extern "C"
-char *thd_strdup(MYSQL_THD thd, const char *str)
+char *thd_strdup(const MYSQL_THD thd, const char *str)
 {
   return thd->strdup(str);
 }
 
 extern "C"
-char *thd_strmake(MYSQL_THD thd, const char *str, size_t size)
+char *thd_strmake(const MYSQL_THD thd, const char *str, size_t size)
 {
   return thd->strmake(str, size);
 }
 
 extern "C"
-LEX_CSTRING *thd_make_lex_string(THD *thd, LEX_CSTRING *lex_str,
+LEX_CSTRING *thd_make_lex_string(const THD *thd, LEX_CSTRING *lex_str,
                                 const char *str, size_t size,
                                 int allocate_lex_string)
 {
@@ -1171,7 +1171,7 @@ LEX_CSTRING *thd_make_lex_string(THD *thd, LEX_CSTRING *lex_str,
 }
 
 extern "C"
-void *thd_memdup(MYSQL_THD thd, const void* str, size_t size)
+void *thd_memdup(const MYSQL_THD thd, const void* str, size_t size)
 {
   return thd->memdup(str, size);
 }
@@ -2345,7 +2345,7 @@ void THD::cleanup_after_query()
 
 bool THD::convert_string(LEX_STRING *to, CHARSET_INFO *to_cs,
 			 const char *from, size_t from_length,
-			 CHARSET_INFO *from_cs)
+			 CHARSET_INFO *from_cs) const
 {
   DBUG_ENTER("THD::convert_string");
   size_t new_length= to_cs->mbmaxlen * from_length;
@@ -2382,7 +2382,7 @@ bool THD::convert_string(LEX_STRING *to, CHARSET_INFO *to_cs,
 */
 
 bool THD::reinterpret_string_from_binary(LEX_CSTRING *to, CHARSET_INFO *cs,
-                                         const char *str, size_t length)
+                                         const char *str, size_t length) const
 {
   /*
     When reinterpreting from binary to tricky character sets like
@@ -2424,7 +2424,7 @@ bool THD::reinterpret_string_from_binary(LEX_CSTRING *to, CHARSET_INFO *cs,
 */
 bool THD::convert_fix(CHARSET_INFO *dstcs, LEX_STRING *dst,
                       CHARSET_INFO *srccs, const char *src, size_t src_length,
-                      String_copier *status)
+                      String_copier *status) const
 {
   DBUG_ENTER("THD::convert_fix");
   size_t dst_length= dstcs->mbmaxlen * src_length;
@@ -2442,7 +2442,7 @@ bool THD::convert_fix(CHARSET_INFO *dstcs, LEX_STRING *dst,
 */
 bool THD::copy_fix(CHARSET_INFO *dstcs, LEX_STRING *dst,
                    CHARSET_INFO *srccs, const char *src, size_t src_length,
-                   String_copier *status)
+                   String_copier *status) const
 {
   DBUG_ENTER("THD::copy_fix");
   size_t dst_length= dstcs->mbmaxlen * src_length;
@@ -2474,7 +2474,7 @@ class String_copier_with_error: public String_copier
 
 bool THD::convert_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst,
                              CHARSET_INFO *srccs,
-                             const char *src, size_t src_length)
+                             const char *src, size_t src_length) const
 {
   String_copier_with_error status;
   return convert_fix(dstcs, dst, srccs, src, src_length, &status) ||
@@ -2484,7 +2484,7 @@ bool THD::convert_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst,
 
 bool THD::copy_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst,
                           CHARSET_INFO *srccs,
-                          const char *src, size_t src_length)
+                          const char *src, size_t src_length) const
 {
   String_copier_with_error status;
   return copy_fix(dstcs, dst, srccs, src, src_length, &status) ||
@@ -2507,7 +2507,8 @@ bool THD::copy_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst,
    !0   out of memory
 */
 
-bool THD::convert_string(String *s, CHARSET_INFO *from_cs, CHARSET_INFO *to_cs)
+bool THD::convert_string(String *s, CHARSET_INFO *from_cs,
+                         CHARSET_INFO *to_cs)
 {
   uint dummy_errors;
   if (unlikely(convert_buffer.copy(s->ptr(), s->length(), from_cs, to_cs,
@@ -2539,7 +2540,8 @@ bool THD::check_string_for_wellformedness(const char *str,
 }
 
 
-bool THD::to_ident_sys_alloc(Lex_ident_sys_st *to, const Lex_ident_cli_st *ident)
+bool THD::to_ident_sys_alloc(Lex_ident_sys_st *to,
+                             const Lex_ident_cli_st *ident) const
 {
   if (ident->is_quoted())
   {
@@ -6899,97 +6901,6 @@ bool THD::binlog_table_should_be_logged(const LEX_CSTRING *db)
            binlog_filter->db_ok(db->str)));
 }
 
-/*
-  Template member function for ensuring that there is an rows log
-  event of the apropriate type before proceeding.
-
-  PRE CONDITION:
-    - Events of type 'RowEventT' have the type code 'type_code'.
-
-  POST CONDITION:
-    If a non-NULL pointer is returned, the pending event for thread 'thd' will
-    be an event of type 'RowEventT' (which have the type code 'type_code')
-    will either empty or have enough space to hold 'needed' bytes.  In
-    addition, the columns bitmap will be correct for the row, meaning that
-    the pending event will be flushed if the columns in the event differ from
-    the columns suppled to the function.
-
-  RETURNS
-    If no error, a non-NULL pending event (either one which already existed or
-    the newly created one).
-    If error, NULL.
- */
-
-template <class RowsEventT> Rows_log_event*
-THD::binlog_prepare_pending_rows_event(TABLE* table, uint32 serv_id,
-                                       size_t needed,
-                                       bool is_transactional,
-                                       RowsEventT *hint __attribute__((unused)))
-{
-  DBUG_ENTER("binlog_prepare_pending_rows_event");
-  /* Pre-conditions */
-  DBUG_ASSERT(table->s->table_map_id != ~0UL);
-
-  /* Fetch the type code for the RowsEventT template parameter */
-  int const general_type_code= RowsEventT::TYPE_CODE;
-
-  /* Ensure that all events in a GTID group are in the same cache */
-  if (variables.option_bits & OPTION_GTID_BEGIN)
-    is_transactional= 1;
-
-  /*
-    There is no good place to set up the transactional data, so we
-    have to do it here.
-  */
-  if (binlog_setup_trx_data() == NULL)
-    DBUG_RETURN(NULL);
-
-  Rows_log_event* pending= binlog_get_pending_rows_event(is_transactional);
-
-  if (unlikely(pending && !pending->is_valid()))
-    DBUG_RETURN(NULL);
-
-  /*
-    Check if the current event is non-NULL and a write-rows
-    event. Also check if the table provided is mapped: if it is not,
-    then we have switched to writing to a new table.
-    If there is no pending event, we need to create one. If there is a pending
-    event, but it's not about the same table id, or not of the same type
-    (between Write, Update and Delete), or not the same affected columns, or
-    going to be too big, flush this event to disk and create a new pending
-    event.
-  */
-  if (!pending ||
-      pending->server_id != serv_id ||
-      pending->get_table_id() != table->s->table_map_id ||
-      pending->get_general_type_code() != general_type_code ||
-      pending->get_data_size() + needed > opt_binlog_rows_event_max_size ||
-      pending->read_write_bitmaps_cmp(table) == FALSE)
-  {
-    /* Create a new RowsEventT... */
-    Rows_log_event* const
-        ev= new RowsEventT(this, table, table->s->table_map_id,
-                           is_transactional);
-    if (unlikely(!ev))
-      DBUG_RETURN(NULL);
-    ev->server_id= serv_id; // I don't like this, it's too easy to forget.
-    /*
-      flush the pending event and replace it with the newly created
-      event...
-    */
-    if (unlikely(
-        mysql_bin_log.flush_and_set_pending_rows_event(this, ev,
-                                                       is_transactional)))
-    {
-      delete ev;
-      DBUG_RETURN(NULL);
-    }
-
-    DBUG_RETURN(ev);               /* This is the new pending event */
-  }
-  DBUG_RETURN(pending);        /* This is the current pending event */
-}
-
 /* Declare in unnamed namespace. */
 CPP_UNNAMED_NS_START
   /**
@@ -7114,13 +7025,10 @@ CPP_UNNAMED_NS_START
 
 CPP_UNNAMED_NS_END
 
-int THD::binlog_write_row(TABLE* table, bool is_trans,
+int THD::binlog_write_row(TABLE* table, Event_log *bin_log,
+                          binlog_cache_data *cache_data, bool is_trans,
                           uchar const *record)
 {
-
-  DBUG_ASSERT(is_current_stmt_binlog_format_row());
-  DBUG_ASSERT((WSREP_NNULL(this) && wsrep_emulate_bin_log) ||
-              mysql_bin_log.is_open());
   /*
     Pack records into format for transfer. We are allocating more
     memory than needed, but that doesn't matter.
@@ -7134,21 +7042,13 @@ int THD::binlog_write_row(TABLE* table, bool is_trans,
 
   size_t const len= pack_row(table, table->rpl_write_set, row_data, record);
 
-  /* Ensure that all events in a GTID group are in the same cache */
-  if (variables.option_bits & OPTION_GTID_BEGIN)
-    is_trans= 1;
+  auto creator= binlog_should_compress(len) ?
+                Rows_event_factory::get<Write_rows_compressed_log_event>() :
+                Rows_event_factory::get<Write_rows_log_event>();
 
-  Rows_log_event* ev;
-  if (binlog_should_compress(len))
-    ev =
-    binlog_prepare_pending_rows_event(table, variables.server_id,
-                                      len, is_trans,
-                                      static_cast<Write_rows_compressed_log_event*>(0));
-  else
-    ev =
-    binlog_prepare_pending_rows_event(table, variables.server_id,
-                                      len, is_trans,
-                                      static_cast<Write_rows_log_event*>(0));
+  auto *ev= bin_log->prepare_pending_rows_event(this, table, cache_data,
+                                                variables.server_id,
+                                                len, is_trans, creator);
 
   if (unlikely(ev == 0))
     return HA_ERR_OUT_OF_MEM;
@@ -7156,14 +7056,12 @@ int THD::binlog_write_row(TABLE* table, bool is_trans,
   return ev->add_row_data(row_data, len);
 }
 
-int THD::binlog_update_row(TABLE* table, bool is_trans,
+int THD::binlog_update_row(TABLE* table,  Event_log *bin_log,
+                           binlog_cache_data *cache_data, bool is_trans,
+                           enum_binlog_row_image row_image,
                            const uchar *before_record,
                            const uchar *after_record)
 {
-  DBUG_ASSERT(is_current_stmt_binlog_format_row());
-  DBUG_ASSERT((WSREP_NNULL(this) && wsrep_emulate_bin_log) ||
-              mysql_bin_log.is_open());
-
   /**
     Save a reference to the original read bitmaps
     We will need this to restore the bitmaps at the end as
@@ -7175,10 +7073,9 @@ int THD::binlog_update_row(TABLE* table, bool is_trans,
 
   /**
      This will remove spurious fields required during execution but
-     not needed for binlogging. This is done according to the:
-     binlog-row-image option.
+     not needed for binlogging, according to the row_image argument.
    */
-  binlog_prepare_row_images(table);
+  binlog_prepare_row_images(table, row_image);
 
   size_t const before_maxlen= max_row_length(table, table->read_set,
                                              before_record);
@@ -7196,11 +7093,6 @@ int THD::binlog_update_row(TABLE* table, bool is_trans,
                                      before_record);
   size_t const after_size= pack_row(table, table->rpl_write_set, after_row,
                                     after_record);
-
-  /* Ensure that all events in a GTID group are in the same cache */
-  if (variables.option_bits & OPTION_GTID_BEGIN)
-    is_trans= 1;
-
   /*
     Don't print debug messages when running valgrind since they can
     trigger false warnings.
@@ -7212,17 +7104,13 @@ int THD::binlog_update_row(TABLE* table, bool is_trans,
   DBUG_DUMP("after_row",     after_row, after_size);
 #endif
 
-  Rows_log_event* ev;
-  if(binlog_should_compress(before_size + after_size))
-    ev =
-      binlog_prepare_pending_rows_event(table, variables.server_id,
-                                      before_size + after_size, is_trans,
-                                      static_cast<Update_rows_compressed_log_event*>(0));
-  else
-    ev =
-      binlog_prepare_pending_rows_event(table, variables.server_id,
-                                      before_size + after_size, is_trans,
-                                      static_cast<Update_rows_log_event*>(0));
+  auto creator= binlog_should_compress(before_size + after_size) ?
+                Rows_event_factory::get<Update_rows_compressed_log_event>() :
+                Rows_event_factory::get<Update_rows_log_event>();
+  auto *ev= bin_log->prepare_pending_rows_event(this, table, cache_data,
+                                                variables.server_id,
+                                                before_size + after_size,
+                                                is_trans, creator);
 
   if (unlikely(ev == 0))
     return HA_ERR_OUT_OF_MEM;
@@ -7237,12 +7125,11 @@ int THD::binlog_update_row(TABLE* table, bool is_trans,
 
 }
 
-int THD::binlog_delete_row(TABLE* table, bool is_trans, 
+int THD::binlog_delete_row(TABLE* table, Event_log *bin_log,
+                           binlog_cache_data *cache_data, bool is_trans,
+                           enum_binlog_row_image row_image,
                            uchar const *record)
 {
-  DBUG_ASSERT(is_current_stmt_binlog_format_row());
-  DBUG_ASSERT((WSREP_NNULL(this) && wsrep_emulate_bin_log) ||
-              mysql_bin_log.is_open());
   /**
     Save a reference to the original read bitmaps
     We will need this to restore the bitmaps at the end as
@@ -7257,7 +7144,7 @@ int THD::binlog_delete_row(TABLE* table, bool is_trans,
      not needed for binlogging. This is done according to the:
      binlog-row-image option.
    */
-  binlog_prepare_row_images(table);
+  binlog_prepare_row_images(table, row_image);
 
   /*
      Pack records into format for transfer. We are allocating more
@@ -7273,21 +7160,12 @@ int THD::binlog_delete_row(TABLE* table, bool is_trans,
   DBUG_DUMP("table->read_set", (uchar*) table->read_set->bitmap, (table->s->fields + 7) / 8);
   size_t const len= pack_row(table, table->read_set, row_data, record);
 
-  /* Ensure that all events in a GTID group are in the same cache */
-  if (variables.option_bits & OPTION_GTID_BEGIN)
-    is_trans= 1;
-
-  Rows_log_event* ev;
-  if(binlog_should_compress(len))
-    ev =
-      binlog_prepare_pending_rows_event(table, variables.server_id,
-                                      len, is_trans,
-                                      static_cast<Delete_rows_compressed_log_event*>(0));
-  else
-    ev =
-      binlog_prepare_pending_rows_event(table, variables.server_id,
-                                      len, is_trans,
-                                      static_cast<Delete_rows_log_event*>(0));
+  auto creator= binlog_should_compress(len) ?
+                Rows_event_factory::get<Delete_rows_compressed_log_event>() :
+                Rows_event_factory::get<Delete_rows_log_event>();
+  auto *ev= bin_log->prepare_pending_rows_event(this, table, cache_data,
+                                                variables.server_id,
+                                                len, is_trans, creator);
 
   if (unlikely(ev == 0))
     return HA_ERR_OUT_OF_MEM;
@@ -7307,14 +7185,12 @@ int THD::binlog_delete_row(TABLE* table, bool is_trans,
    Remove from read_set spurious columns. The write_set has been
    handled before in table->mark_columns_needed_for_update.
 */
-
-void THD::binlog_prepare_row_images(TABLE *table)
+void binlog_prepare_row_images(TABLE *table, enum_binlog_row_image row_image)
 {
   DBUG_ENTER("THD::binlog_prepare_row_images");
 
   DBUG_PRINT_BITSET("debug", "table->read_set (before preparing): %s",
                     table->read_set);
-  THD *thd= table->in_use;
 
   /**
     if there is a primary key in the table (ie, user declared PK or a
@@ -7322,7 +7198,7 @@ void THD::binlog_prepare_row_images(TABLE *table)
     and the handler involved supports this.
    */
   if (table->s->primary_key < MAX_KEY &&
-      (thd->variables.binlog_row_image < BINLOG_ROW_IMAGE_FULL) &&
+      row_image < BINLOG_ROW_IMAGE_FULL &&
       !ha_check_storage_engine_flag(table->s->db_type(),
                                     HTON_NO_BINLOG_ROW_OPT))
   {
@@ -7332,7 +7208,7 @@ void THD::binlog_prepare_row_images(TABLE *table)
     */
     DBUG_ASSERT(table->read_set != &table->tmp_set);
 
-    switch (thd->variables.binlog_row_image)
+    switch (row_image)
     {
       case BINLOG_ROW_IMAGE_MINIMAL:
         /* MINIMAL: Mark only PK */
@@ -7367,28 +7243,6 @@ void THD::binlog_prepare_row_images(TABLE *table)
   DBUG_VOID_RETURN;
 }
 
-
-
-int THD::binlog_remove_pending_rows_event(bool reset_stmt,
-                                          bool is_transactional)
-{
-  DBUG_ENTER("THD::binlog_remove_pending_rows_event");
-
-  if(!WSREP_EMULATE_BINLOG_NNULL(this) && !mysql_bin_log.is_open())
-    DBUG_RETURN(0);
-
-  /* Ensure that all events in a GTID group are in the same cache */
-  if (variables.option_bits & OPTION_GTID_BEGIN)
-    is_transactional= 1;
-
-  mysql_bin_log.remove_pending_rows_event(this, is_transactional);
-
-  if (reset_stmt)
-    reset_binlog_for_next_statement();
-  DBUG_RETURN(0);
-}
-
-
 int THD::binlog_flush_pending_rows_event(bool stmt_end, bool is_transactional)
 {
   DBUG_ENTER("THD::binlog_flush_pending_rows_event");
@@ -7404,22 +7258,15 @@ int THD::binlog_flush_pending_rows_event(bool stmt_end, bool is_transactional)
   if (variables.option_bits & OPTION_GTID_BEGIN)
     is_transactional= 1;
 
-  /*
-    Mark the event as the last event of a statement if the stmt_end
-    flag is set.
-  */
-  int error= 0;
-  if (Rows_log_event *pending= binlog_get_pending_rows_event(is_transactional))
-  {
-    if (stmt_end)
-    {
-      pending->set_flags(Rows_log_event::STMT_END_F);
-      reset_binlog_for_next_statement();
-    }
-    error= mysql_bin_log.flush_and_set_pending_rows_event(this, 0,
-                                                          is_transactional);
-  }
+  auto *cache_mngr= binlog_get_cache_mngr();
+  if (!cache_mngr)
+    DBUG_RETURN(0);
+  auto *cache= binlog_get_cache_data(cache_mngr,
+                                     use_trans_cache(this, is_transactional));
 
+  int error=
+    ::binlog_flush_pending_rows_event(this, stmt_end, is_transactional,
+                                      mysql_bin_log.as_event_log(), cache);
   DBUG_RETURN(error);
 }
 
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 861d5eb55a2..a7841b80e4c 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -72,6 +72,7 @@ void set_thd_stage_info(void *thd,
 #include "wsrep_on.h"
 #ifdef WITH_WSREP
 #include <inttypes.h>
+#include <ilist.h>
 /* wsrep-lib */
 #include "wsrep_client_service.h"
 #include "wsrep_client_state.h"
@@ -201,6 +202,7 @@ enum enum_binlog_row_image {
 #define OLD_MODE_UTF8_IS_UTF8MB3      (1 << 3)
 #define OLD_MODE_IGNORE_INDEX_ONLY_FOR_JOIN          (1 << 4)
 #define OLD_MODE_COMPAT_5_1_CHECKSUM    (1 << 5)
+#define OLD_MODE_LOCK_ALTER_TABLE_COPY                   (1 << 6)
 
 extern char internal_table_name[2];
 extern char empty_c_string[1];
@@ -1219,21 +1221,31 @@ class Query_arena
   inline bool is_conventional() const
   { return state == STMT_CONVENTIONAL_EXECUTION; }
 
-  inline void* alloc(size_t size) { return alloc_root(mem_root,size); }
-  inline void* calloc(size_t size)
+  inline void* alloc(size_t size) const { return alloc_root(mem_root,size); }
+  inline void* calloc(size_t size) const
   {
     void *ptr;
     if (likely((ptr=alloc_root(mem_root,size))))
       bzero(ptr, size);
     return ptr;
   }
-  inline char *strdup(const char *str)
+  inline char *strdup(const char *str) const
   { return strdup_root(mem_root,str); }
-  inline char *strmake(const char *str, size_t size)
+  inline char *strmake(const char *str, size_t size) const
   { return strmake_root(mem_root,str,size); }
-  inline void *memdup(const void *str, size_t size)
+  inline LEX_CSTRING strcat(const LEX_CSTRING &a, const LEX_CSTRING &b) const
+  {
+    char *buf= (char*)alloc(a.length + b.length + 1);
+    if (unlikely(!buf))
+      return null_clex_str;
+    memcpy(buf, a.str, a.length);
+    memcpy(buf + a.length, b.str, b.length);
+    buf[a.length + b.length] = 0;
+    return {buf, a.length + b.length};
+  }
+  inline void *memdup(const void *str, size_t size) const
   { return memdup_root(mem_root,str,size); }
-  inline void *memdup_w_gap(const void *str, size_t size, size_t gap)
+  inline void *memdup_w_gap(const void *str, size_t size, size_t gap) const
   {
     void *ptr;
     if (likely((ptr= alloc_root(mem_root,size+gap))))
@@ -2693,7 +2705,7 @@ class THD: public THD_count, /* this must be first */
   Protocol_binary protocol_binary;	// Binary protocol
   HASH    user_vars;			// hash for user variables
   String  packet;			// dynamic buffer for network I/O
-  String  convert_buffer;               // buffer for charset conversions
+  String  convert_buffer;		// buffer for charset conversions
   struct  my_rnd_struct rand;		// used for authentication
   struct  system_variables variables;	// Changeable local variables
   struct  system_status_var status_var; // Per thread statistic vars
@@ -2945,43 +2957,39 @@ class THD: public THD_count, /* this must be first */
   */
   void binlog_start_trans_and_stmt();
   void binlog_set_stmt_begin();
-  int binlog_write_row(TABLE* table, bool is_transactional,
+  int binlog_write_row(TABLE* table, Event_log *bin_log,
+                       binlog_cache_data *cache_data, bool is_transactional,
                        const uchar *buf);
-  int binlog_delete_row(TABLE* table, bool is_transactional,
-                        const uchar *buf);
-  int binlog_update_row(TABLE* table, bool is_transactional,
+  int binlog_delete_row(TABLE* table,  Event_log *bin_log,
+                        binlog_cache_data *cache_data, bool is_transactional,
+                        enum_binlog_row_image row_image, const uchar *buf);
+  int binlog_update_row(TABLE* table, Event_log *bin_log,
+                        binlog_cache_data *cache_data, bool is_transactional,
+                        enum_binlog_row_image row_image,
                         const uchar *old_data, const uchar *new_data);
   bool prepare_handlers_for_update(uint flag);
   bool binlog_write_annotated_row(Log_event_writer *writer);
   void binlog_prepare_for_row_logging();
   bool binlog_write_table_maps();
-  bool binlog_write_table_map(TABLE *table, bool with_annotate);
-  static void binlog_prepare_row_images(TABLE* table);
 
   void set_server_id(uint32 sid) { variables.server_id = sid; }
 
   /*
     Member functions to handle pending event for row-level logging.
   */
-  template <class RowsEventT> Rows_log_event*
-    binlog_prepare_pending_rows_event(TABLE* table, uint32 serv_id,
-                                      size_t needed,
-                                      bool is_transactional,
-                                      RowsEventT* hint);
-  Rows_log_event* binlog_get_pending_rows_event(bool is_transactional) const;
-  void binlog_set_pending_rows_event(Rows_log_event* ev, bool is_transactional);
+  binlog_cache_mngr *binlog_get_cache_mngr() const;
   inline int binlog_flush_pending_rows_event(bool stmt_end)
   {
     return (binlog_flush_pending_rows_event(stmt_end, FALSE) || 
             binlog_flush_pending_rows_event(stmt_end, TRUE));
   }
   int binlog_flush_pending_rows_event(bool stmt_end, bool is_transactional);
-  int binlog_remove_pending_rows_event(bool clear_maps, bool is_transactional);
 
   bool binlog_need_stmt_format(bool is_transactional) const
   {
     return log_current_statement() &&
-           !binlog_get_pending_rows_event(is_transactional);
+           !binlog_get_pending_rows_event(binlog_get_cache_mngr(),
+              use_trans_cache(this, is_transactional));
   }
 
   bool binlog_for_noop_dml(bool transactional_table);
@@ -4150,24 +4158,25 @@ class THD: public THD_count, /* this must be first */
   {
     return !stmt_arena->is_stmt_prepare();
   }
-  inline void* trans_alloc(size_t size)
+  inline void* trans_alloc(size_t size) const
   {
     return alloc_root(&transaction->mem_root,size);
   }
 
-  LEX_CSTRING strmake_lex_cstring(const char *str, size_t length)
+  LEX_CSTRING strmake_lex_cstring(const char *str, size_t length) const
   {
     const char *tmp= strmake_root(mem_root, str, length);
     if (!tmp)
       return {0,0};
     return {tmp, length};
   }
-  LEX_CSTRING strmake_lex_cstring(const LEX_CSTRING &from)
+  LEX_CSTRING strmake_lex_cstring(const LEX_CSTRING &from) const
   {
     return strmake_lex_cstring(from.str, from.length);
   }
 
-  LEX_STRING *make_lex_string(LEX_STRING *lex_str, const char* str, size_t length)
+  LEX_STRING *make_lex_string(LEX_STRING *lex_str, const char* str,
+                              size_t length) const
   {
     if (!(lex_str->str= strmake_root(mem_root, str, length)))
     {
@@ -4177,7 +4186,8 @@ class THD: public THD_count, /* this must be first */
     lex_str->length= length;
     return lex_str;
   }
-  LEX_CSTRING *make_lex_string(LEX_CSTRING *lex_str, const char* str, size_t length)
+  LEX_CSTRING *make_lex_string(LEX_CSTRING *lex_str, const char* str,
+                               size_t length) const
   {
     if (!(lex_str->str= strmake_root(mem_root, str, length)))
     {
@@ -4188,7 +4198,8 @@ class THD: public THD_count, /* this must be first */
     return lex_str;
   }
   // Remove double quotes:  aaa""bbb -> aaa"bbb
-  bool quote_unescape(LEX_CSTRING *dst, const LEX_CSTRING *src, char quote)
+  bool quote_unescape(LEX_CSTRING *dst, const LEX_CSTRING *src,
+                      char quote) const
   {
     const char *tmp= src->str;
     const char *tmpend= src->str + src->length;
@@ -4208,7 +4219,7 @@ class THD: public THD_count, /* this must be first */
     return false;
   }
 
-  LEX_CSTRING *make_clex_string(const char* str, size_t length)
+  LEX_CSTRING *make_clex_string(const char* str, size_t length) const
   {
     LEX_CSTRING *lex_str;
     char *tmp;
@@ -4223,13 +4234,13 @@ class THD: public THD_count, /* this must be first */
     lex_str->length= length;
     return lex_str;
   }
-  LEX_CSTRING *make_clex_string(const LEX_CSTRING from)
+  LEX_CSTRING *make_clex_string(const LEX_CSTRING from) const
   {
     return make_clex_string(from.str, from.length);
   }
 
   // Allocate LEX_STRING for character set conversion
-  bool alloc_lex_string(LEX_STRING *dst, size_t length)
+  bool alloc_lex_string(LEX_STRING *dst, size_t length) const
   {
     if (likely((dst->str= (char*) alloc(length))))
       return false;
@@ -4238,12 +4249,13 @@ class THD: public THD_count, /* this must be first */
   }
   bool convert_string(LEX_STRING *to, CHARSET_INFO *to_cs,
 		      const char *from, size_t from_length,
-		      CHARSET_INFO *from_cs);
+		      CHARSET_INFO *from_cs) const;
   bool reinterpret_string_from_binary(LEX_CSTRING *to, CHARSET_INFO *to_cs,
-                                      const char *from, size_t from_length);
+                                      const char *from, size_t from_length)
+                                      const;
   bool convert_string(LEX_CSTRING *to, CHARSET_INFO *to_cs,
                       const char *from, size_t from_length,
-                      CHARSET_INFO *from_cs)
+                      CHARSET_INFO *from_cs) const
   {
     LEX_STRING tmp;
     bool rc= convert_string(&tmp, to_cs, from, from_length, from_cs);
@@ -4253,7 +4265,7 @@ class THD: public THD_count, /* this must be first */
   }
   bool convert_string(LEX_CSTRING *to, CHARSET_INFO *tocs,
                       const LEX_CSTRING *from, CHARSET_INFO *fromcs,
-                      bool simple_copy_is_possible)
+                      bool simple_copy_is_possible) const
   {
     if (!simple_copy_is_possible)
       return unlikely(convert_string(to, tocs, from->str, from->length, fromcs));
@@ -4269,7 +4281,7 @@ class THD: public THD_count, /* this must be first */
   */
   bool convert_fix(CHARSET_INFO *dstcs, LEX_STRING *dst,
                    CHARSET_INFO *srccs, const char *src, size_t src_length,
-                   String_copier *status);
+                   String_copier *status) const;
 
   /*
     Same as above, but additionally sends ER_INVALID_CHARACTER_STRING
@@ -4277,7 +4289,7 @@ class THD: public THD_count, /* this must be first */
   */
   bool convert_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst,
                           CHARSET_INFO *srccs,
-                          const char *src, size_t src_length);
+                          const char *src, size_t src_length) const;
   /*
     If either "dstcs" or "srccs" is &my_charset_bin,
     then performs native copying using copy_fix().
@@ -4285,16 +4297,18 @@ class THD: public THD_count, /* this must be first */
   */
   bool copy_fix(CHARSET_INFO *dstcs, LEX_STRING *dst,
                 CHARSET_INFO *srccs, const char *src, size_t src_length,
-                String_copier *status);
+                String_copier *status) const;
 
   /*
     Same as above, but additionally sends ER_INVALID_CHARACTER_STRING
     in case of bad byte sequences or Unicode conversion problems.
   */
   bool copy_with_error(CHARSET_INFO *dstcs, LEX_STRING *dst,
-                       CHARSET_INFO *srccs, const char *src, size_t src_length);
+                       CHARSET_INFO *srccs, const char *src, size_t src_length)
+                       const;
 
-  bool convert_string(String *s, CHARSET_INFO *from_cs, CHARSET_INFO *to_cs);
+  bool convert_string(String *s, CHARSET_INFO *from_cs,
+                      CHARSET_INFO *to_cs);
 
   /*
     Check if the string is wellformed, raise an error if not wellformed.
@@ -4305,7 +4319,8 @@ class THD: public THD_count, /* this must be first */
                                        size_t length,
                                        CHARSET_INFO *cs) const;
 
-  bool to_ident_sys_alloc(Lex_ident_sys_st *to, const Lex_ident_cli_st *from);
+  bool to_ident_sys_alloc(Lex_ident_sys_st *to,
+                          const Lex_ident_cli_st *from) const;
 
   /*
     Create a string literal with optional client->connection conversion.
@@ -4315,7 +4330,8 @@ class THD: public THD_count, /* this must be first */
   */
   Item_basic_constant *make_string_literal(const char *str, size_t length,
                                            my_repertoire_t repertoire);
-  Item_basic_constant *make_string_literal(const Lex_string_with_metadata_st &str)
+  Item_basic_constant *
+  make_string_literal(const Lex_string_with_metadata_st &str)
   {
     my_repertoire_t repertoire= str.repertoire(variables.character_set_client);
     return make_string_literal(str.str, str.length, repertoire);
@@ -5558,6 +5574,8 @@ class THD: public THD_count, /* this must be first */
   Item *sp_prepare_func_item(Item **it_addr, uint cols);
   bool sp_eval_expr(Field *result_field, Item **expr_item_ptr);
 
+  ilist<online_alter_cache_data> online_alter_cache_list;
+
   bool sql_parser(LEX *old_lex, LEX *lex,
                   char *str, uint str_len, bool stmt_prepare_mode);
 
@@ -7561,6 +7579,22 @@ inline bool handler::has_long_unique()
   return table->s->long_unique_table;
 }
 
+/**
+  Return whether the handler is root.
+  @return false if table is maintained by different handlerton, true otherwise.
+  @note The implementation supposes that the same handler can't be found as both
+  root and non-root.
+
+  There are two known cases when it's non-root:
+  1. under partition's ha_write_row() (also true for copy_partitions())
+  2. under ha_mroonga::wrapper_write_row();
+  same applies for ha_delete_row/ha_update_row.
+*/
+inline bool handler::is_root_handler() const
+{
+  return ht == table->file->ht;
+}
+
 extern pthread_attr_t *get_connection_attrib(void);
 
 /**
@@ -7614,6 +7648,8 @@ inline bool binlog_should_compress(size_t len)
     len >= opt_bin_log_compress_min_len;
 }
 
+void binlog_prepare_row_images(TABLE* table,
+                               enum_binlog_row_image row_image);
 
 /**
    Save thd sql_mode on instantiation.
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index 3fe08ba658f..0b36b604126 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -9683,18 +9683,18 @@ Item *LEX::create_item_qualified_asterisk(THD *thd,
 }
 
 
-bool Lex_ident_sys_st::copy_ident_cli(THD *thd, const Lex_ident_cli_st *str)
+bool Lex_ident_sys_st::copy_ident_cli(const THD *thd, const Lex_ident_cli_st *str)
 {
   return thd->to_ident_sys_alloc(this, str);
 }
 
-bool Lex_ident_sys_st::copy_keyword(THD *thd, const Lex_ident_cli_st *str)
+bool Lex_ident_sys_st::copy_keyword(const THD *thd, const Lex_ident_cli_st *str)
 {
   return thd->make_lex_string(static_cast<LEX_CSTRING*>(this),
                               str->str, str->length) == NULL;
 }
 
-bool Lex_ident_sys_st::copy_or_convert(THD *thd,
+bool Lex_ident_sys_st::copy_or_convert(const THD *thd,
                                        const Lex_ident_cli_st *src,
                                        CHARSET_INFO *cs)
 {
@@ -9704,7 +9704,7 @@ bool Lex_ident_sys_st::copy_or_convert(THD *thd,
 }
 
 
-bool Lex_ident_sys_st::copy_sys(THD *thd, const LEX_CSTRING *src)
+bool Lex_ident_sys_st::copy_sys(const THD *thd, const LEX_CSTRING *src)
 {
   if (thd->check_string_for_wellformedness(src->str, src->length,
                                            system_charset_info))
@@ -9713,7 +9713,7 @@ bool Lex_ident_sys_st::copy_sys(THD *thd, const LEX_CSTRING *src)
 }
 
 
-bool Lex_ident_sys_st::convert(THD *thd,
+bool Lex_ident_sys_st::convert(const THD *thd,
                                const LEX_CSTRING *src, CHARSET_INFO *cs)
 {
   LEX_STRING tmp;
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index 081051e26de..f4c61daa0c4 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -148,11 +148,12 @@ class Lex_ident_cli: public Lex_ident_cli_st
 struct Lex_ident_sys_st: public LEX_CSTRING
 {
 public:
-  bool copy_ident_cli(THD *thd, const Lex_ident_cli_st *str);
-  bool copy_keyword(THD *thd, const Lex_ident_cli_st *str);
-  bool copy_sys(THD *thd, const LEX_CSTRING *str);
-  bool convert(THD *thd, const LEX_CSTRING *str, CHARSET_INFO *cs);
-  bool copy_or_convert(THD *thd, const Lex_ident_cli_st *str, CHARSET_INFO *cs);
+  bool copy_ident_cli(const THD *thd, const Lex_ident_cli_st *str);
+  bool copy_keyword(const THD *thd, const Lex_ident_cli_st *str);
+  bool copy_sys(const THD *thd, const LEX_CSTRING *str);
+  bool convert(const THD *thd, const LEX_CSTRING *str, CHARSET_INFO *cs);
+  bool copy_or_convert(const THD *thd, const Lex_ident_cli_st *str,
+                       CHARSET_INFO *cs);
   bool is_null() const { return str == NULL; }
   bool to_size_number(ulonglong *to) const;
   void set_valid_utf8(const LEX_CSTRING *name)
@@ -167,7 +168,7 @@ struct Lex_ident_sys_st: public LEX_CSTRING
 class Lex_ident_sys: public Lex_ident_sys_st
 {
 public:
-  Lex_ident_sys(THD *thd, const Lex_ident_cli_st *str)
+  Lex_ident_sys(const THD *thd, const Lex_ident_cli_st *str)
   {
     if (copy_ident_cli(thd, str))
       ((LEX_CSTRING &) *this)= null_clex_str;
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index b8ae790b17f..c6a514da03c 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -4898,10 +4898,8 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
     }
     else
     {
-#ifdef HAVE_QUERY_CACHE
       if (thd->variables.query_cache_wlock_invalidate)
-	query_cache.invalidate_locked_for_write(thd, first_table);
-#endif /*HAVE_QUERY_CACHE*/
+	query_cache_invalidate_locked_for_write(thd, first_table);
       my_ok(thd);
     }
     break;
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 1138aabfa75..38faaf6beaa 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -55,6 +55,7 @@
 #include "sql_audit.h"
 #include "sql_sequence.h"
 #include "tztime.h"
+#include "rpl_rli.h"
 #include "sql_insert.h"                // binlog_drop_table
 #include "ddl_log.h"
 #include "debug.h"                     // debug_crash_here()
@@ -85,7 +86,7 @@ static int copy_data_between_tables(THD *, TABLE *,TABLE *,
                                     List<Create_field> &, bool, uint, ORDER *,
                                     ha_rows *, ha_rows *,
                                     Alter_info::enum_enable_or_disable,
-                                    Alter_table_ctx *);
+                                    Alter_table_ctx *, bool, uint64);
 static int append_system_key_parts(THD *thd, HA_CREATE_INFO *create_info,
                                    Key *key);
 static int mysql_prepare_create_table(THD *, HA_CREATE_INFO *, Alter_info *,
@@ -4324,7 +4325,7 @@ handler *mysql_create_frm_image(THD *thd, const LEX_CSTRING &db,
     {
       if (key->type == Key::FOREIGN_KEY)
       {
-        my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, MYF(0), 
+        my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, MYF(0),
                  "FOREIGN KEY");
         goto err;
       }
@@ -9846,6 +9847,152 @@ static uint64 get_start_alter_id(THD *thd)
 }
 
 
+static
+bool online_alter_check_autoinc(const THD *thd, const Alter_info *alter_info,
+                                const TABLE *table)
+{
+  /*
+    We can't go online, if all of the following is presented:
+    * Autoinc is added to existing field
+    * Disabled NO_AUTO_VALUE_ON_ZERO
+    * No non-nullable unique key in the old table, that has all the key parts
+      remaining unchanged.
+  */
+
+  // Exit earlier when possible
+  if (thd->variables.sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO)
+    return true;
+  if ((alter_info->flags | ALTER_CHANGE_COLUMN) != alter_info->flags)
+    return true;
+
+  /*
+    Find at least one unique index (without NULLs), all columns of which
+    remain in the table unchanged to presume it's a safe ALTER TABLE.
+  */
+  for (uint k= 0; k < table->s->keys; k++)
+  {
+    const KEY &key= table->key_info[k];
+    if ((key.flags & HA_NOSAME) == 0 || key.flags & HA_NULL_PART_KEY)
+      continue;
+    bool key_parts_good= true;
+    for (uint kp= 0; kp < key.user_defined_key_parts && key_parts_good; kp++)
+    {
+      const Field *f= key.key_part[kp].field;
+      // tmp_set contains dropped fields after mysql_prepare_alter_table
+      key_parts_good= !bitmap_is_set(&table->tmp_set, f->field_index);
+
+      if (key_parts_good)
+        for (const auto &c: alter_info->create_list)
+          if (c.field == f)
+          {
+            key_parts_good= f->is_equal(c);
+            break;
+          }
+    }
+    if (key_parts_good)
+      return true;
+  }
+
+  for (const auto &c: alter_info->create_list)
+  {
+    if (c.flags & AUTO_INCREMENT_FLAG)
+    {
+      if (c.field && !(c.field->flags & AUTO_INCREMENT_FLAG))
+        return false;
+      break;
+    }
+  }
+  return true;
+}
+
+static
+const char *online_alter_check_supported(const THD *thd,
+                                         const Alter_info *alter_info,
+                                         const TABLE *table,
+                                         const TABLE *new_table, bool *online)
+{
+  DBUG_ASSERT(*online);
+
+  *online= thd->locked_tables_mode != LTM_LOCK_TABLES && !table->s->tmp_table;
+  if (!*online)
+    return NULL;
+
+  *online= (new_table->file->ha_table_flags() & HA_NO_ONLINE_ALTER) == 0;
+  if (!*online)
+    return new_table->file->engine_name()->str;
+
+  *online= table->s->sequence == NULL;
+  if (!*online)
+    return "SEQUENCE";
+
+  *online= (alter_info->flags & ALTER_DROP_SYSTEM_VERSIONING) == 0;
+  if (!*online)
+    return "DROP SYSTEM VERSIONING";
+
+  *online= !thd->lex->ignore;
+  if (!*online)
+    return "ALTER IGNORE TABLE";
+
+  *online= !table->versioned(VERS_TRX_ID);
+  if (!*online)
+    return "BIGINT GENERATED ALWAYS AS ROW_START";
+
+  List<FOREIGN_KEY_INFO> fk_list;
+  table->file->get_foreign_key_list(thd, &fk_list);
+  for (auto &fk: fk_list)
+  {
+    if (fk_modifies_child(fk.delete_method) ||
+        fk_modifies_child(fk.update_method))
+    {
+      *online= false;
+      // Don't fall to a common unsupported case to avoid heavy string ops.
+      if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
+      {
+        return fk_modifies_child(fk.delete_method)
+               ? thd->strcat({STRING_WITH_LEN("ON DELETE ")},
+                             *fk_option_name(fk.delete_method)).str
+               : thd->strcat({STRING_WITH_LEN("ON UPDATE ")},
+                             *fk_option_name(fk.update_method)).str;
+      }
+      return NULL;
+    }
+  }
+
+  for (auto &c: alter_info->create_list)
+  {
+    *online= c.field || !(c.flags & AUTO_INCREMENT_FLAG);
+    if (!*online)
+      return "ADD COLUMN ... AUTO_INCREMENT";
+
+    auto *def= c.default_value;
+    *online= !(def && def->flags & VCOL_NEXTVAL
+            // either it's a new field, or a NULL -> NOT NULL change
+             && (!c.field || (!(c.field->flags & NOT_NULL_FLAG)
+                              && (c.flags & NOT_NULL_FLAG))));
+    if (!*online)
+    {
+      if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_NONE)
+        return NULL; // Avoid heavy string op
+      const char *fmt= ER_THD(thd, ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED);
+
+      LEX_CSTRING dflt{STRING_WITH_LEN("DEFAULT")};
+      LEX_CSTRING nxvl{STRING_WITH_LEN("NEXTVAL()")};
+      size_t len= strlen(fmt) + nxvl.length + c.field_name.length + dflt.length;
+      char *resp= (char*)thd->alloc(len);
+      // expression %s cannot be used in the %s clause of %`s
+      my_snprintf(resp, len, fmt, nxvl.str, dflt.str, c.field_name.str);
+      return resp;
+    }
+  }
+
+  *online= online_alter_check_autoinc(thd, alter_info, table);
+  if (!*online)
+    return "CHANGE COLUMN ... AUTO_INCREMENT";
+
+  return NULL;
+}
+
+
 /**
   Alter table
 
@@ -9938,6 +10085,11 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db,
   MDL_request target_mdl_request;
   MDL_ticket *mdl_ticket= 0;
   Alter_table_prelocking_strategy alter_prelocking_strategy;
+#ifdef HAVE_REPLICATION
+  bool online= order == NULL && !opt_bootstrap;
+#else
+  bool online= false;
+#endif
   TRIGGER_RENAME_PARAM trigger_param;
 
   /*
@@ -10020,6 +10172,18 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db,
   */
   table_list->required_type= TABLE_TYPE_NORMAL;
 
+  if ((alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_DEFAULT
+       && (thd->variables.old_behavior & OLD_MODE_LOCK_ALTER_TABLE_COPY))
+      || alter_info->requested_lock > Alter_info::ALTER_TABLE_LOCK_NONE
+      || thd->lex->sql_command == SQLCOM_OPTIMIZE
+      || alter_info->algorithm(thd) > Alter_info::ALTER_TABLE_ALGORITHM_COPY)
+    online= false;
+
+  if (online)
+  {
+    table_list->lock_type= TL_READ;
+  }
+
   DEBUG_SYNC(thd, "alter_table_before_open_tables");
 
   thd->open_options|= HA_OPEN_FOR_ALTER;
@@ -10050,7 +10214,7 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db,
 
   table= table_list->table;
   bool is_reg_table= table->s->tmp_table == NO_TMP_TABLE;
-
+  
 #ifdef WITH_WSREP
   if (WSREP(thd) &&
       (thd->lex->sql_command == SQLCOM_ALTER_TABLE ||
@@ -10111,8 +10275,7 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db,
       a new one if needed.
     */
     table->s->tdc->flushed= 1;         // Force close of all instances
-    if (thd->mdl_context.upgrade_shared_lock(mdl_ticket,
-                                             MDL_EXCLUSIVE,
+    if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_EXCLUSIVE,
                                              thd->variables.lock_wait_timeout))
       DBUG_RETURN(1);
     quick_rm_table(thd, table->file->ht, &table_list->db,
@@ -10121,8 +10284,7 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db,
     goto end_inplace;
   }
   if (!if_exists &&
-      (table->file->partition_ht()->flags &
-       HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE))
+      (table->file->partition_ht()->flags & HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE))
   {
     /*
       Table is a shared table that may not exist on the slave.
@@ -10552,31 +10714,25 @@ do_continue:;
 #endif
 
   /*
-    Use copy algorithm if:
-    - old_alter_table system variable is set without in-place requested using
-      the ALGORITHM clause.
-    - Or if in-place is impossible for given operation.
+    We can use only copy algorithm if one of the following is true:
+    - In-place is impossible for given operation.
     - Changes to partitioning which were not handled by fast_alter_part_table()
       needs to be handled using table copying algorithm unless the engine
       supports auto-partitioning as such engines can do some changes
       using in-place API.
   */
-  if ((thd->variables.alter_algorithm == Alter_info::ALTER_TABLE_ALGORITHM_COPY &&
-       alter_info->algorithm(thd) !=
-       Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
-      || is_inplace_alter_impossible(table, create_info, alter_info)
+  if (is_inplace_alter_impossible(table, create_info, alter_info)
       || IF_PARTITIONING((partition_changed &&
-          !(old_db_type->partition_flags() & HA_USE_AUTO_PARTITION)), 0))
+                          !(old_db_type->partition_flags() & HA_USE_AUTO_PARTITION)), 0))
   {
-    if (alter_info->algorithm(thd) ==
-        Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
+    if (alter_info->algorithm_is_nocopy(thd))
     {
       my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
-               "ALGORITHM=INPLACE", "ALGORITHM=COPY");
+               alter_info->algorithm_clause(thd), "ALGORITHM=COPY");
       DBUG_RETURN(true);
     }
-    alter_info->set_requested_algorithm(
-      Alter_info::ALTER_TABLE_ALGORITHM_COPY);
+
+    alter_info->set_requested_algorithm(Alter_info::ALTER_TABLE_ALGORITHM_COPY);
   }
 
   /*
@@ -10818,7 +10974,7 @@ do_continue:;
     }
 
     if (alter_info->supports_algorithm(thd, &ha_alter_info) ||
-        alter_info->supports_lock(thd, &ha_alter_info))
+        alter_info->supports_lock(thd, online, &ha_alter_info))
     {
       cleanup_table_after_inplace_alter(&altered_table);
       goto err_new_table_cleanup;
@@ -10876,16 +11032,6 @@ do_continue:;
 
   if (!table->s->tmp_table)
   {
-    // COPY algorithm doesn't work with concurrent writes.
-    if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
-    {
-      my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
-               "LOCK=NONE",
-               ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY),
-               "LOCK=SHARED");
-      goto err_new_table_cleanup;
-    }
-
     // If EXCLUSIVE lock is requested, upgrade already.
     if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE &&
         wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
@@ -10943,11 +11089,9 @@ do_continue:;
   DEBUG_SYNC(thd, "alter_table_intermediate_table_created");
 
   /* Open the table since we need to copy the data. */
-  new_table= thd->create_and_open_tmp_table(&frm,
-                                            alter_ctx.get_tmp_path(),
+  new_table= thd->create_and_open_tmp_table(&frm, alter_ctx.get_tmp_path(),
                                             alter_ctx.new_db.str,
-                                            alter_ctx.new_name.str,
-                                            true);
+                                            alter_ctx.new_name.str, true);
   if (!new_table)
     goto err_new_table_cleanup;
 
@@ -10957,6 +11101,21 @@ do_continue:;
     thd->session_tracker.state_change.mark_as_changed(thd);
   }
 
+  if (online)
+  {
+    const char *reason= online_alter_check_supported(thd, alter_info, table,
+                                                     new_table,
+                                                     &online);
+    if (reason &&
+        alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
+    {
+      DBUG_ASSERT(!online);
+      my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+               "LOCK=NONE", reason, "LOCK=SHARED");
+      goto err_new_table_cleanup;
+    }
+  }
+
   /*
     Note: In case of MERGE table, we do not attach children. We do not
     copy data for MERGE tables. Only the children have data.
@@ -11014,13 +11173,13 @@ do_continue:;
       binlog_as_create_select= 1;
       DBUG_ASSERT(new_table->file->row_logging);
       new_table->mark_columns_needed_for_insert();
-      thd->binlog_write_table_map(new_table, 1);
+      mysql_bin_log.write_table_map(thd, new_table, 1);
     }
     if (copy_data_between_tables(thd, table, new_table,
                                  alter_info->create_list, ignore,
                                  order_num, order, &copied, &deleted,
                                  alter_info->keys_onoff,
-                                 &alter_ctx))
+                                 &alter_ctx, online, start_alter_id))
       goto err_new_table_cleanup;
   }
   else
@@ -11514,14 +11673,74 @@ bool mysql_trans_commit_alter_copy_data(THD *thd)
   DBUG_RETURN(error);
 }
 
+#ifdef HAVE_REPLICATION
+/*
+  locking ALTER TABLE doesn't issue ER_NO_DEFAULT_FOR_FIELD, so online
+  ALTER shouldn't either
+*/
+class Has_default_error_handler : public Internal_error_handler
+{
+public:
+  bool handle_condition(THD *, uint sql_errno, const char *,
+                        Sql_condition::enum_warning_level *,
+                        const char *, Sql_condition **)
+  {
+    return sql_errno == ER_NO_DEFAULT_FOR_FIELD;
+  }
+};
+
+
+static int online_alter_read_from_binlog(THD *thd, rpl_group_info *rgi,
+                                         Cache_flip_event_log *log,
+                                         ha_rows *found_rows)
+{
+  int error= 0;
+
+  IO_CACHE *log_file= log->flip();
+
+  thd_progress_report(thd, 0, my_b_write_tell(log_file));
+
+  Has_default_error_handler hdeh;
+  thd->push_internal_handler(&hdeh);
+  do
+  {
+    const auto *descr_event= rgi->rli->relay_log.description_event_for_exec;
+    auto *ev= Log_event::read_log_event(log_file, descr_event, false, ~0UL);
+    error= log_file->error;
+    if (unlikely(!ev))
+    {
+      if (error)
+        my_error(ER_IO_READ_ERROR,MYF(0), (ulong)EIO, strerror(EIO), "");
+      break;
+    }
+    DBUG_ASSERT(!error);
+
+    ev->thd= thd;
+    error= ev->apply_event(rgi);
+
+    error= error || thd->is_error();
+    if(likely(!error))
+      ev->online_alter_update_row_count(found_rows);
+
+    if (ev != rgi->rli->relay_log.description_event_for_exec)
+      delete ev;
+    thd_progress_report(thd, my_b_tell(log_file), thd->progress.max_counter);
+    DEBUG_SYNC(thd, "alter_table_online_progress");
+  } while(!error);
+  thd->pop_internal_handler();
+
+  return MY_TEST(error);
+}
+#endif
 
 static int
 copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
-			 List<Create_field> &create, bool ignore,
-			 uint order_num, ORDER *order,
-			 ha_rows *copied, ha_rows *deleted,
+                         List<Create_field> &create, bool ignore,
+                         uint order_num, ORDER *order,
+                         ha_rows *copied, ha_rows *deleted,
                          Alter_info::enum_enable_or_disable keys_onoff,
-                         Alter_table_ctx *alter_ctx)
+                         Alter_table_ctx *alter_ctx, bool online,
+                         uint64 start_alter_id)
 {
   int error= 1;
   Copy_field *copy= NULL, *copy_end;
@@ -11546,8 +11765,13 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
   MYSQL_TIME query_start;
   DBUG_ENTER("copy_data_between_tables");
 
-  /* Two or 3 stages; Sorting, copying data and update indexes */
-  thd_progress_init(thd, 2 + MY_TEST(order));
+  /*
+    if ORDER BY: sorting
+    always: copying, building indexes.
+    if online: reading up the binlog (second binlog is being written)
+               reading up the second binlog under exclusive lock
+  */
+  thd_progress_init(thd, MY_TEST(order) + 2 + 2 * MY_TEST(online));
 
   if (!(copy= new (thd->mem_root) Copy_field[to->s->fields]))
     DBUG_RETURN(-1);
@@ -11587,6 +11811,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
   Create_field *def;
   copy_end=copy;
   to->s->default_fields= 0;
+  error= 1;
   for (Field **ptr=to->field ; *ptr ; ptr++)
   {
     def=it++;
@@ -11705,148 +11930,173 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
   if (!ignore) /* for now, InnoDB needs the undo log for ALTER IGNORE */
     to->file->extra(HA_EXTRA_BEGIN_ALTER_COPY);
 
-  while (likely(!(error= info.read_record())))
+  if (!(error= info.read_record()))
   {
-    if (unlikely(thd->killed))
+#ifdef HAVE_REPLICATION
+    if (online)
     {
-      thd->send_kill_message();
-      error= 1;
-      break;
-    }
+      from->s->online_alter_binlog= new Cache_flip_event_log();
+      if (!from->s->online_alter_binlog)
+        goto err;
+      from->s->online_alter_binlog->init_pthread_objects();
+      error= from->s->online_alter_binlog->open(WRITE_CACHE);
 
-    if (make_unversioned)
-    {
-      if (!from_row_end->is_max())
-        continue; // Drop history rows.
-    }
+      if (error)
+      {
+        from->s->online_alter_binlog->release();
+        from->s->online_alter_binlog= NULL;
+        goto err;
+      }
 
-    if (unlikely(++thd->progress.counter >= time_to_report_progress))
-    {
-      time_to_report_progress+= MY_HOW_OFTEN_TO_WRITE/10;
-      thd_progress_report(thd, thd->progress.counter,
-                          thd->progress.max_counter);
+      from->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE);
+      DEBUG_SYNC(thd, "alter_table_online_downgraded");
     }
+#else
+    DBUG_ASSERT(!online);
+#endif // HAVE_REPLICATION
 
-    /* Return error if source table isn't empty. */
-    if (unlikely(alter_ctx->error_if_not_empty))
+    do
     {
-      error= 1;
-      break;
-    }
+      if (unlikely(thd->killed))
+      {
+        thd->send_kill_message();
+        error= 1;
+        break;
+      }
 
-    for (Copy_field *copy_ptr=copy ; copy_ptr != copy_end ; copy_ptr++)
-    {
-      copy_ptr->do_copy(copy_ptr);
-    }
+      if (make_unversioned)
+      {
+        if (!from_row_end->is_max())
+          continue; // Drop history rows.
+      }
 
-    if (make_versioned)
-    {
-      to_row_start->set_notnull();
-      to_row_start->store_time(&query_start);
-      to_row_end->set_max();
-    }
+      if (unlikely(++thd->progress.counter >= time_to_report_progress))
+      {
+        time_to_report_progress+= MY_HOW_OFTEN_TO_WRITE/10;
+        thd_progress_report(thd, thd->progress.counter,
+                            thd->progress.max_counter);
+      }
 
-    prev_insert_id= to->file->next_insert_id;
-    if (to->default_field)
-      to->update_default_fields(ignore);
-    if (to->vfield)
-      to->update_virtual_fields(to->file, VCOL_UPDATE_FOR_WRITE);
+      /* Return error if source table isn't empty. */
+      if (unlikely(alter_ctx->error_if_not_empty))
+      {
+        error= 1;
+        break;
+      }
 
-    /* This will set thd->is_error() if fatal failure */
-    if (to->verify_constraints(ignore) == VIEW_CHECK_SKIP)
-      continue;
-    if (unlikely(thd->is_error()))
-    {
-      error= 1;
-      break;
-    }
-    if (keep_versioned && to->versioned(VERS_TRX_ID))
-      to->vers_write= false;
+      for (Copy_field *copy_ptr=copy ; copy_ptr != copy_end ; copy_ptr++)
+      {
+        copy_ptr->do_copy(copy_ptr);
+      }
 
-    if (to->next_number_field)
-    {
-      if (auto_increment_field_copied)
-        to->auto_increment_field_not_null= TRUE;
-      else
-        to->next_number_field->reset();
-    }
-    error= to->file->ha_write_row(to->record[0]);
-    to->auto_increment_field_not_null= FALSE;
-    if (unlikely(error))
-    {
-      if (to->file->is_fatal_error(error, HA_CHECK_DUP))
+      if (make_versioned)
+      {
+        to_row_start->set_notnull();
+        to_row_start->store_time(&query_start);
+        to_row_end->set_max();
+      }
+
+      prev_insert_id= to->file->next_insert_id;
+      if (to->default_field)
+        to->update_default_fields(ignore);
+      if (to->vfield)
+        to->update_virtual_fields(to->file, VCOL_UPDATE_FOR_WRITE);
+
+      /* This will set thd->is_error() if fatal failure */
+      if (to->verify_constraints(ignore) == VIEW_CHECK_SKIP)
+        continue;
+      if (unlikely(thd->is_error()))
       {
-        /* Not a duplicate key error. */
-	to->file->print_error(error, MYF(0));
         error= 1;
-	break;
+        break;
       }
-      else
+      if (keep_versioned && to->versioned(VERS_TRX_ID))
+        to->vers_write= false;
+
+      if (to->next_number_field)
+      {
+        if (auto_increment_field_copied)
+          to->auto_increment_field_not_null= TRUE;
+        else
+          to->next_number_field->reset();
+      }
+      error= to->file->ha_write_row(to->record[0]);
+      to->auto_increment_field_not_null= FALSE;
+      if (unlikely(error))
       {
-        /* Duplicate key error. */
-        if (unlikely(alter_ctx->fk_error_if_delete_row))
+        if (to->file->is_fatal_error(error, HA_CHECK_DUP))
         {
-          /*
-            We are trying to omit a row from the table which serves as parent
-            in a foreign key. This might have broken referential integrity so
-            emit an error. Note that we can't ignore this error even if we are
-            executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but
-            doesn't allow to break unique or foreign key constraints,
-          */
-          my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0),
-                   alter_ctx->fk_error_id,
-                   alter_ctx->fk_error_table);
+          /* Not a duplicate key error. */
+          to->file->print_error(error, MYF(0));
+          error= 1;
           break;
         }
-
-        if (ignore)
-        {
-          /* This ALTER IGNORE TABLE. Simply skip row and continue. */
-          to->file->restore_auto_increment(prev_insert_id);
-          delete_count++;
-        }
         else
         {
-          /* Ordinary ALTER TABLE. Report duplicate key error. */
-          uint key_nr= to->file->get_dup_key(error);
-          if ((int) key_nr >= 0)
+          /* Duplicate key error. */
+          if (unlikely(alter_ctx->fk_error_if_delete_row))
           {
-            const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME);
-            if (key_nr == 0 && to->s->keys > 0 &&
-                (to->key_info[0].key_part[0].field->flags &
-                 AUTO_INCREMENT_FLAG))
-              err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE);
-            print_keydup_error(to,
-                               key_nr >= to->s->keys ? NULL :
-                                   &to->key_info[key_nr],
-                               err_msg, MYF(0));
+            /*
+              We are trying to omit a row from the table which serves as parent
+              in a foreign key. This might have broken referential integrity so
+              emit an error. Note that we can't ignore this error even if we are
+              executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but
+              doesn't allow to break unique or foreign key constraints,
+            */
+            my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0),
+                     alter_ctx->fk_error_id,
+                     alter_ctx->fk_error_table);
+            break;
+          }
+
+          if (ignore)
+          {
+            /* This ALTER IGNORE TABLE. Simply skip row and continue. */
+            to->file->restore_auto_increment(prev_insert_id);
+            delete_count++;
           }
           else
-            to->file->print_error(error, MYF(0));
-          break;
+          {
+            /* Ordinary ALTER TABLE. Report duplicate key error. */
+            uint key_nr= to->file->get_dup_key(error);
+            if ((int) key_nr >= 0)
+            {
+              const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME);
+              if (key_nr == 0 && to->s->keys > 0 &&
+                  (to->key_info[0].key_part[0].field->flags &
+                   AUTO_INCREMENT_FLAG))
+                err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE);
+              print_keydup_error(to,
+                                 key_nr >= to->s->keys ? NULL :
+                                     &to->key_info[key_nr],
+                                 err_msg, MYF(0));
+            }
+            else
+              to->file->print_error(error, MYF(0));
+            break;
+          }
         }
       }
-    }
-    else
-    {
-      /* In case of alter ignore, notify the engine about it. */
-      if (ignore)
-        to->file->extra(HA_EXTRA_IGNORE_INSERT);
-      DEBUG_SYNC(thd, "copy_data_between_tables_before");
-      found_count++;
-      mysql_stage_set_work_completed(thd->m_stage_progress_psi, found_count);
-    }
-    thd->get_stmt_da()->inc_current_row_for_warning();
+      else
+      {
+        /* In case of alter ignore, notify the engine about it. */
+        if (ignore)
+          to->file->extra(HA_EXTRA_IGNORE_INSERT);
+        DEBUG_SYNC(thd, "copy_data_between_tables_before");
+        found_count++;
+        mysql_stage_set_work_completed(thd->m_stage_progress_psi, found_count);
+      }
+      thd->get_stmt_da()->inc_current_row_for_warning();
+    } while (!(error= info.read_record()));
   }
+  else
+    online= false;
+
+  DEBUG_SYNC(thd, "alter_table_copy_end");
 
   THD_STAGE_INFO(thd, stage_enabling_keys);
   thd_progress_next_stage(thd);
 
-  if (error > 0 && !from->s->tmp_table)
-  {
-    /* We are going to drop the temporary table */
-    to->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
-  }
   if (unlikely(to->file->ha_end_bulk_insert()) && error <= 0)
   {
     /* Give error, if not already given */
@@ -11854,6 +12104,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
       to->file->print_error(my_errno,MYF(0));
     error= 1;
   }
+
   bulk_insert_started= 0;
   if (!ignore)
     to->file->extra(HA_EXTRA_END_ALTER_COPY);
@@ -11861,6 +12112,96 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
   cleanup_done= 1;
   to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
 
+#ifdef HAVE_REPLICATION
+  if (online && error < 0)
+  {
+    MEM_UNDEFINED(from->record[0], from->s->rec_buff_length * 2);
+    MEM_UNDEFINED(to->record[0], to->s->rec_buff_length * 2);
+    thd_progress_next_stage(thd);
+    enum_sql_command saved_sql_command= thd->lex->sql_command;
+    Table_map_log_event table_event(thd, from, from->s->table_map_id,
+                                    from->file->has_transactions());
+    Relay_log_info rli(false);
+    rpl_group_info rgi(&rli);
+    RPL_TABLE_LIST rpl_table(to, TL_WRITE, from, table_event.get_table_def(),
+                             copy, copy_end);
+    DBUG_ASSERT(to->pos_in_table_list == NULL);
+    to->pos_in_table_list= &rpl_table;
+    rgi.thd= thd;
+    rgi.tables_to_lock= &rpl_table;
+
+    rgi.m_table_map.set_table(from->s->table_map_id, to);
+
+    Cache_flip_event_log *binlog= from->s->online_alter_binlog;
+    DBUG_ASSERT(binlog->is_open());
+
+    rli.relay_log.description_event_for_exec=
+                                            new Format_description_log_event(4);
+
+    // We'll be filling from->record[0] from row events
+    bitmap_set_all(from->write_set);
+    // We restore bitmaps, because update event is going to mess up with them.
+    to->default_column_bitmaps();
+
+    end_read_record(&info);
+    init_read_record_done= false;
+    mysql_unlock_tables(thd, thd->lock);
+    thd->lock= NULL;
+
+    error= online_alter_read_from_binlog(thd, &rgi, binlog, &found_count);
+    if (start_alter_id)
+    {
+      DBUG_ASSERT(thd->slave_thread);
+
+      int rpl_error= wait_for_master(thd);
+      if (rpl_error)
+        error= 1;
+    }
+
+    DEBUG_SYNC(thd, "alter_table_online_before_lock");
+
+    int lock_error=
+        thd->mdl_context.upgrade_shared_lock(from->mdl_ticket, MDL_EXCLUSIVE,
+                                     (double)thd->variables.lock_wait_timeout);
+    if (!error)
+      error= lock_error;
+
+    if (!error)
+    {
+      thd_progress_next_stage(thd);
+      error= online_alter_read_from_binlog(thd, &rgi, binlog, &found_count);
+    }
+    if (error)
+      from->s->tdc->flush_unused(1); // to free the binlog
+    to->pos_in_table_list= NULL; // Safety
+    DBUG_ASSERT(thd->lex->sql_command == saved_sql_command);
+    thd->lex->sql_command= saved_sql_command; // Just in case
+  }
+  else if (online) // error was on copy stage
+  {
+    /*
+       We can't free the resources properly now, as we can still be in
+       non-exclusive state. So this s->online_alter_binlog will be used
+       until all transactions will release it.
+       Once the transaction commits, it can release online_alter_binlog
+       by decreasing ref_count.
+
+       online_alter_binlog->ref_count can be reached 0 only once.
+       Proof:
+       If share exists, we'll always have ref_count >= 1.
+       Once it reaches destroy(), nobody can acquire it again,
+       therefore, only release() is possible at this moment.
+    */
+    from->s->tdc->flush_unused(1); // to free the binlog
+  }
+#endif
+
+  if (error > 0 && !from->s->tmp_table)
+  {
+    /* We are going to drop the temporary table */
+    to->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
+  }
+
   DEBUG_SYNC(thd, "copy_data_between_tables_before_reset_backup_lock");
   if (backup_reset_alter_copy_lock(thd))
     error= 1;
@@ -11872,7 +12213,6 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
   if (bulk_insert_started)
     (void) to->file->ha_end_bulk_insert();
 
-/* Free resources */
   if (init_read_record_done)
     end_read_record(&info);
   delete [] copy;
diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc
index 4843450352e..793a2eb1025 100644
--- a/sql/sys_vars.cc
+++ b/sql/sys_vars.cc
@@ -3899,6 +3899,7 @@ static const char *old_mode_names[]=
   "UTF8_IS_UTF8MB3",
   "IGNORE_INDEX_ONLY_FOR_JOIN",
   "COMPAT_5_1_CHECKSUM",
+  "LOCK_ALTER_TABLE_COPY",
   0
 };
 
diff --git a/sql/table.cc b/sql/table.cc
index 33d38041f5f..03705b50a4a 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -505,6 +505,14 @@ void TABLE_SHARE::destroy()
     }
   }
 
+#ifdef HAVE_REPLICATION
+  if (online_alter_binlog)
+  {
+    online_alter_binlog->release();
+    online_alter_binlog= NULL;
+  }
+#endif
+
 #ifdef WITH_PARTITION_STORAGE_ENGINE
   plugin_unlock(NULL, default_part_plugin);
 #endif /* WITH_PARTITION_STORAGE_ENGINE */
@@ -7681,6 +7689,17 @@ void TABLE::mark_columns_needed_for_delete()
     bitmap_set_bit(write_set, s->vers.end_fieldno);
     need_signal= true;
   }
+#ifdef HAVE_REPLICATION
+  if (s->online_alter_binlog)
+  {
+    /*
+      For online alter we have to read all columns, because we need PK columns
+      in the row event, and we don't know what columns will be in PK after ALTER
+    */
+    bitmap_set_all(read_set);
+    need_signal= true;
+  }
+#endif
 
   if (need_signal)
     file->column_bitmaps_signal();
@@ -7767,9 +7786,20 @@ void TABLE::mark_columns_needed_for_update()
       For System Versioning we have to read all columns since we store
       a copy of previous row with modified row_end back to a table.
     */
-    bitmap_union(read_set, &s->all_set);
+    bitmap_set_all(read_set);
     need_signal= true;
   }
+#ifdef HAVE_REPLICATION
+  if (s->online_alter_binlog)
+  {
+    /*
+      For online alter we have to read all columns, because we need PK columns
+      in the row event, and we don't know what columns will be in PK after ALTER
+    */
+    bitmap_set_all(read_set);
+    need_signal= true;
+  }
+#endif
   if (check_constraints)
   {
     mark_check_constraint_columns_for_read();
@@ -10586,7 +10616,7 @@ void Vers_history_point::print(String *str, enum_query_type query_type,
   item->print(str, query_type);
 }
 
-Field *TABLE::find_field_by_name(LEX_CSTRING *str) const
+Field *TABLE::find_field_by_name(const LEX_CSTRING *str) const
 {
   Field **tmp;
   size_t length= str->length;
diff --git a/sql/table.h b/sql/table.h
index 589903d7028..abcea65223c 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -66,6 +66,7 @@ struct TABLE_LIST;
 class ACL_internal_schema_access;
 class ACL_internal_table_access;
 class Field;
+class Copy_field;
 class Table_statistics;
 class With_element;
 struct TDC_element;
@@ -79,6 +80,7 @@ class Pushdown_derived;
 struct Name_resolution_context;
 class Table_function_json_table;
 class Open_table_context;
+class MYSQL_LOG;
 
 /*
   Used to identify NESTED_JOIN structures within a join (applicable only to
@@ -932,6 +934,10 @@ struct TABLE_SHARE
   plugin_ref default_part_plugin;
 #endif
 
+#ifdef HAVE_REPLICATION
+  Cache_flip_event_log *online_alter_binlog;
+#endif
+
   /**
     System versioning and application-time periods support.
   */
@@ -1624,6 +1630,8 @@ struct TABLE
   */
   Item *notnull_cond;
 
+  online_alter_cache_data *online_alter_cache;
+
   inline void reset() { bzero((void*)this, sizeof(*this)); }
   void init(THD *thd, TABLE_LIST *tl);
   bool fill_item_list(List<Item> *item_list) const;
@@ -1809,7 +1817,7 @@ struct TABLE
                                       bool with_cleanup);
   bool vcol_fix_expr(THD *thd);
   bool vcol_cleanup_expr(THD *thd);
-  Field *find_field_by_name(LEX_CSTRING *str) const;
+  Field *find_field_by_name(const LEX_CSTRING *str) const;
   bool export_structure(THD *thd, class Row_definition_list *defs);
   bool is_splittable() { return spl_opt_info != NULL; }
   void set_spl_opt_info(SplM_opt_info *spl_info);
@@ -2352,11 +2360,18 @@ struct TABLE_LIST
                      mdl_type, MDL_TRANSACTION);
   }
 
+  TABLE_LIST(const LEX_CSTRING *db_arg,
+             const LEX_CSTRING *table_name_arg,
+             const LEX_CSTRING *alias_arg,
+             enum thr_lock_type lock_type_arg)
+  {
+    init_one_table(db_arg, table_name_arg, alias_arg, lock_type_arg);
+  }
+
   TABLE_LIST(TABLE *table_arg, thr_lock_type lock_type)
+    : TABLE_LIST(&table_arg->s->db, &table_arg->s->table_name, NULL, lock_type)
   {
     DBUG_ASSERT(table_arg->s);
-    init_one_table(&table_arg->s->db, &table_arg->s->table_name,
-                   NULL, lock_type);
     table= table_arg;
     vers_conditions.name= table->s->vers.name;
   }
diff --git a/sql/temporary_tables.cc b/sql/temporary_tables.cc
index ecbfdde1878..c01591e9af7 100644
--- a/sql/temporary_tables.cc
+++ b/sql/temporary_tables.cc
@@ -627,8 +627,7 @@ bool THD::drop_temporary_table(TABLE *table, bool *is_trans, bool delete_table)
                           table->s->db.str, table->s->table_name.str));
 
   // close all handlers in case it is statement abort and some can be left
-  if (is_error())
-    table->file->ha_reset();
+  table->file->ha_reset();
 
   locked= lock_temporary_tables();
 
diff --git a/sql/transaction.cc b/sql/transaction.cc
index 94c3ec1d1e1..85a18410304 100644
--- a/sql/transaction.cc
+++ b/sql/transaction.cc
@@ -550,17 +550,17 @@ bool trans_rollback_stmt(THD *thd)
   DBUG_RETURN(FALSE);
 }
 
-/* Find a named savepoint in the current transaction. */
-static SAVEPOINT **
-find_savepoint(THD *thd, LEX_CSTRING name)
+/** Find a savepoint by name in a savepoint list */
+SAVEPOINT** find_savepoint_in_list(THD *thd, LEX_CSTRING name,
+                                   SAVEPOINT ** const list)
 {
-  SAVEPOINT **sv= &thd->transaction->savepoints;
+  SAVEPOINT **sv= list;
 
   while (*sv)
   {
     if (system_charset_info->strnncoll(
-                     (uchar *) name.str, name.length,
-                     (uchar *) (*sv)->name, (*sv)->length) == 0)
+            (uchar *) name.str, name.length,
+            (uchar *) (*sv)->name, (*sv)->length) == 0)
       break;
     sv= &(*sv)->prev;
   }
@@ -568,6 +568,43 @@ find_savepoint(THD *thd, LEX_CSTRING name)
   return sv;
 }
 
+/* Find a named savepoint in the current transaction. */
+static SAVEPOINT **
+find_savepoint(THD *thd, LEX_CSTRING name)
+{
+  return find_savepoint_in_list(thd, name, &thd->transaction->savepoints);
+}
+
+SAVEPOINT* savepoint_add(THD *thd, LEX_CSTRING name, SAVEPOINT **list,
+                         int (*release_old)(THD*, SAVEPOINT*))
+{
+  DBUG_ENTER("savepoint_add");
+
+  SAVEPOINT **sv= find_savepoint_in_list(thd, name, list);
+
+  SAVEPOINT *newsv;
+
+  if (*sv) /* old savepoint of the same name exists */
+  {
+    newsv= *sv;
+    if (release_old){
+      int error= release_old(thd, *sv);
+      if (error)
+        DBUG_RETURN(NULL);
+    }
+    *sv= (*sv)->prev;
+  }
+  else if ((newsv= (SAVEPOINT *) alloc_root(&thd->transaction->mem_root,
+                                            savepoint_alloc_size)) == NULL)
+  {
+    my_error(ER_OUT_OF_RESOURCES, MYF(0));
+    DBUG_RETURN(NULL);
+  }
+
+  newsv->name= strmake_root(&thd->transaction->mem_root, name.str, name.length);
+  newsv->length= (uint)name.length;
+  DBUG_RETURN(newsv);
+}
 
 /**
   Set a named transaction savepoint.
@@ -581,7 +618,6 @@ find_savepoint(THD *thd, LEX_CSTRING name)
 
 bool trans_savepoint(THD *thd, LEX_CSTRING name)
 {
-  SAVEPOINT **sv, *newsv;
   DBUG_ENTER("trans_savepoint");
 
   if (!(thd->in_multi_stmt_transaction_mode() || thd->in_sub_stmt) ||
@@ -591,23 +627,11 @@ bool trans_savepoint(THD *thd, LEX_CSTRING name)
   if (thd->transaction->xid_state.check_has_uncommitted_xa())
     DBUG_RETURN(TRUE);
 
-  sv= find_savepoint(thd, name);
+  SAVEPOINT *newsv= savepoint_add(thd, name, &thd->transaction->savepoints,
+                                  ha_release_savepoint);
 
-  if (*sv) /* old savepoint of the same name exists */
-  {
-    newsv= *sv;
-    ha_release_savepoint(thd, *sv);
-    *sv= (*sv)->prev;
-  }
-  else if ((newsv= (SAVEPOINT *) alloc_root(&thd->transaction->mem_root,
-                                            savepoint_alloc_size)) == NULL)
-  {
-    my_error(ER_OUT_OF_RESOURCES, MYF(0));
+  if (newsv == NULL)
     DBUG_RETURN(TRUE);
-  }
-
-  newsv->name= strmake_root(&thd->transaction->mem_root, name.str, name.length);
-  newsv->length= (uint)name.length;
 
   /*
     if we'll get an error here, don't add new savepoint to the list.
@@ -617,6 +641,10 @@ bool trans_savepoint(THD *thd, LEX_CSTRING name)
   if (unlikely(ha_savepoint(thd, newsv)))
     DBUG_RETURN(TRUE);
 
+  int error= online_alter_savepoint_set(thd, name);
+  if (unlikely(error))
+    DBUG_RETURN(error);
+
   newsv->prev= thd->transaction->savepoints;
   thd->transaction->savepoints= newsv;
 
@@ -676,6 +704,8 @@ bool trans_rollback_to_savepoint(THD *thd, LEX_CSTRING name)
                  ER_WARNING_NOT_COMPLETE_ROLLBACK,
                  ER_THD(thd, ER_WARNING_NOT_COMPLETE_ROLLBACK));
 
+  res= res || online_alter_savepoint_rollback(thd, name);
+
   thd->transaction->savepoints= sv;
 
   if (res)
diff --git a/sql/wsrep_client_service.cc b/sql/wsrep_client_service.cc
index d3b4a18195b..434ef5ff2f7 100644
--- a/sql/wsrep_client_service.cc
+++ b/sql/wsrep_client_service.cc
@@ -243,7 +243,8 @@ size_t Wsrep_client_service::bytes_generated() const
   if (cache)
   {
     size_t pending_rows_event_length= 0;
-    if (Rows_log_event* ev= m_thd->binlog_get_pending_rows_event(true))
+    auto *cache_mngr= m_thd->binlog_get_cache_mngr();
+    if (auto* ev= binlog_get_pending_rows_event(cache_mngr, true))
     {
       pending_rows_event_length= ev->get_data_size();
     }
diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc
index 71609e651a7..50bd85e141b 100644
--- a/sql/wsrep_mysqld.cc
+++ b/sql/wsrep_mysqld.cc
@@ -3463,8 +3463,8 @@ int wsrep_ignored_error_code(Log_event* ev, int error)
   const THD* thd= ev->thd;
 
   DBUG_ASSERT(error);
-  DBUG_ASSERT(wsrep_thd_is_applying(thd) &&
-              !wsrep_thd_is_local_toi(thd));
+  DBUG_ASSERT(wsrep_thd_is_applying(thd));
+  DBUG_ASSERT(!wsrep_thd_is_local_toi(thd));
 
   if ((wsrep_ignore_apply_errors & WSREP_IGNORE_ERRORS_ON_RECONCILING_DML))
   {
diff --git a/storage/connect/ha_connect.cc b/storage/connect/ha_connect.cc
index 502c1f4af6d..b8acf9571de 100644
--- a/storage/connect/ha_connect.cc
+++ b/storage/connect/ha_connect.cc
@@ -1174,7 +1174,7 @@ ulonglong ha_connect::table_flags() const
 //                   HA_FAST_KEY_READ |  causes error when sorting (???)
                      HA_NO_TRANSACTIONS | HA_DUPLICATE_KEY_NOT_IN_ORDER |
                      HA_NO_BLOBS | HA_MUST_USE_TABLE_CONDITION_PUSHDOWN |
-                     HA_REUSES_FILE_NAMES;
+                     HA_REUSES_FILE_NAMES | HA_NO_ONLINE_ALTER;
   ha_connect *hp= (ha_connect*)this;
   PTOS        pos= hp->GetTableOptionStruct();
 
diff --git a/storage/connect/mysql-test/connect/r/alter.result b/storage/connect/mysql-test/connect/r/alter.result
index 09f4584a75e..342d7c72791 100644
--- a/storage/connect/mysql-test/connect/r/alter.result
+++ b/storage/connect/mysql-test/connect/r/alter.result
@@ -272,3 +272,11 @@ line
           2Two
           3Three
 DROP TABLE t1, t2;
+# MDEV-31777 ER_GET_ERRNO upon online alter with concurrent DML on
+# CONNECT table
+CREATE TABLE t (a INT) ENGINE=CONNECT TABLE_TYPE=DOS;
+Warnings:
+Warning	1105	No file name. Table will use t.dos
+ALTER TABLE t FORCE, ALGORITHM=COPY, LOCK=NONE;
+ERROR 0A000: LOCK=NONE is not supported. Reason: CONNECT. Try LOCK=SHARED
+DROP TABLE t;
diff --git a/storage/connect/mysql-test/connect/t/alter.test b/storage/connect/mysql-test/connect/t/alter.test
index 0eda6355027..18688a37934 100644
--- a/storage/connect/mysql-test/connect/t/alter.test
+++ b/storage/connect/mysql-test/connect/t/alter.test
@@ -133,6 +133,14 @@ SELECT * from t2;
 
 DROP TABLE t1, t2;
 
+
+--echo # MDEV-31777 ER_GET_ERRNO upon online alter with concurrent DML on
+--echo # CONNECT table
+CREATE TABLE t (a INT) ENGINE=CONNECT TABLE_TYPE=DOS;
+--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
+ALTER TABLE t FORCE, ALGORITHM=COPY, LOCK=NONE;
+DROP TABLE t;
+
 #
 # Clean up
 #
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index eb5dcb3088f..b21019d90a6 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -15365,7 +15365,7 @@ static
 FOREIGN_KEY_INFO*
 get_foreign_key_info(
 /*=================*/
-	THD*		thd,	/*!< in: user thread handle */
+	const THD*	thd,	/*!< in: user thread handle */
 	dict_foreign_t* foreign)/*!< in: foreign key constraint */
 {
 	FOREIGN_KEY_INFO	f_key_info;
@@ -15501,7 +15501,7 @@ Gets the list of foreign keys in this table.
 int
 ha_innobase::get_foreign_key_list(
 /*==============================*/
-	THD*			thd,		/*!< in: user thread handle */
+	const THD*		thd,		/*!< in: user thread handle */
 	List<FOREIGN_KEY_INFO>*	f_key_list)	/*!< out: foreign key list */
 {
 	update_thd(ha_thd());
@@ -15539,7 +15539,7 @@ Gets the set of foreign keys where this table is the referenced table.
 int
 ha_innobase::get_parent_foreign_key_list(
 /*=====================================*/
-	THD*			thd,		/*!< in: user thread handle */
+	const THD*		thd,		/*!< in: user thread handle */
 	List<FOREIGN_KEY_INFO>*	f_key_list)	/*!< out: foreign key list */
 {
 	update_thd(ha_thd());
@@ -16436,7 +16436,10 @@ ha_innobase::store_lock(
 			    || sql_command == SQLCOM_REPLACE_SELECT
 			    || sql_command == SQLCOM_UPDATE
 			    || sql_command == SQLCOM_CREATE_SEQUENCE
-			    || sql_command == SQLCOM_CREATE_TABLE))) {
+			    || sql_command == SQLCOM_CREATE_TABLE))
+		    || (trx->isolation_level == TRX_ISO_REPEATABLE_READ
+		        && sql_command == SQLCOM_ALTER_TABLE
+		        && lock_type == TL_READ)) {
 
 			/* If the transaction isolation level is
 			READ UNCOMMITTED or READ COMMITTED and we are executing
diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h
index cef3e5f8e66..914f363720a 100644
--- a/storage/innobase/handler/ha_innodb.h
+++ b/storage/innobase/handler/ha_innodb.h
@@ -213,11 +213,11 @@ class ha_innobase final : public handler
 
 	char* get_foreign_key_create_info() override;
 
-        int get_foreign_key_list(THD *thd,
+        int get_foreign_key_list(const THD *thd,
                                  List<FOREIGN_KEY_INFO> *f_key_list) override;
 
 	int get_parent_foreign_key_list(
-		THD*			thd,
+		const THD*		thd,
 		List<FOREIGN_KEY_INFO>*	f_key_list) override;
 
 	bool can_switch_engines() override;
diff --git a/storage/maria/ha_maria.cc b/storage/maria/ha_maria.cc
index 2f58110cb91..258f8fc8c6d 100644
--- a/storage/maria/ha_maria.cc
+++ b/storage/maria/ha_maria.cc
@@ -2959,8 +2959,11 @@ int ha_maria::external_lock(THD *thd, int lock_type)
           tons of archived logs to roll-forward, we could then not disable
           REDOs/UNDOs in this case.
         */
-        DBUG_PRINT("info", ("Disabling logging for table"));
-        _ma_tmp_disable_logging_for_table(file, TRUE);
+        if (likely(file->s->now_transactional))
+        {
+          DBUG_PRINT("info", ("Disabling logging for table"));
+          _ma_tmp_disable_logging_for_table(file, TRUE);
+        }
         file->autocommit= 0;
       }
       else
diff --git a/storage/maria/ha_maria.h b/storage/maria/ha_maria.h
index 009e8ca5fe7..c974a7e3bf6 100644
--- a/storage/maria/ha_maria.h
+++ b/storage/maria/ha_maria.h
@@ -67,7 +67,7 @@ class __attribute__((visibility("default"))) ha_maria :public handler
   ~ha_maria() = default;
   handler *clone(const char *name, MEM_ROOT *mem_root) override final;
   const char *index_type(uint key_number) override final;
-  ulonglong table_flags() const override final
+  ulonglong table_flags() const override
   { return int_table_flags; }
   ulong index_flags(uint inx, uint part, bool all_parts) const override final;
   uint max_supported_keys() const override final
diff --git a/storage/maria/ha_s3.cc b/storage/maria/ha_s3.cc
index 8c105522c11..19e32ce33fb 100644
--- a/storage/maria/ha_s3.cc
+++ b/storage/maria/ha_s3.cc
@@ -238,6 +238,7 @@ ha_s3::ha_s3(handlerton *hton, TABLE_SHARE *table_arg)
   /* Remove things that S3 doesn't support */
   int_table_flags&= ~(HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE |
                       HA_CAN_EXPORT);
+  int_table_flags|= HA_NO_ONLINE_ALTER;
   can_enable_indexes= 0;
 }
 
diff --git a/storage/mroonga/ha_mroonga.cpp b/storage/mroonga/ha_mroonga.cpp
index 85d6473ded3..f57fc385ae3 100644
--- a/storage/mroonga/ha_mroonga.cpp
+++ b/storage/mroonga/ha_mroonga.cpp
@@ -16676,7 +16676,7 @@ bool ha_mroonga::can_switch_engines()
   DBUG_RETURN(res);
 }
 
-int ha_mroonga::wrapper_get_foreign_key_list(THD *thd,
+int ha_mroonga::wrapper_get_foreign_key_list(const THD *thd,
                                            List<FOREIGN_KEY_INFO> *f_key_list)
 {
   MRN_DBUG_ENTER_METHOD();
@@ -16690,7 +16690,7 @@ int ha_mroonga::wrapper_get_foreign_key_list(THD *thd,
 }
 
 #ifdef MRN_SUPPORT_FOREIGN_KEYS
-int ha_mroonga::storage_get_foreign_key_list(THD *thd,
+int ha_mroonga::storage_get_foreign_key_list(const THD *thd,
                                              List<FOREIGN_KEY_INFO> *f_key_list)
 {
   int error;
@@ -16800,7 +16800,7 @@ int ha_mroonga::storage_get_foreign_key_list(THD *thd,
 }
 #endif
 
-int ha_mroonga::get_foreign_key_list(THD *thd,
+int ha_mroonga::get_foreign_key_list(const THD *thd,
                                      List<FOREIGN_KEY_INFO> *f_key_list)
 {
   MRN_DBUG_ENTER_METHOD();
@@ -16814,7 +16814,7 @@ int ha_mroonga::get_foreign_key_list(THD *thd,
   DBUG_RETURN(res);
 }
 
-int ha_mroonga::wrapper_get_parent_foreign_key_list(THD *thd,
+int ha_mroonga::wrapper_get_parent_foreign_key_list(const THD *thd,
                                             List<FOREIGN_KEY_INFO> *f_key_list)
 {
   MRN_DBUG_ENTER_METHOD();
@@ -16827,7 +16827,7 @@ int ha_mroonga::wrapper_get_parent_foreign_key_list(THD *thd,
   DBUG_RETURN(res);
 }
 
-int ha_mroonga::storage_get_parent_foreign_key_list(THD *thd,
+int ha_mroonga::storage_get_parent_foreign_key_list(const THD *thd,
                                             List<FOREIGN_KEY_INFO> *f_key_list)
 {
   MRN_DBUG_ENTER_METHOD();
@@ -16835,7 +16835,7 @@ int ha_mroonga::storage_get_parent_foreign_key_list(THD *thd,
   DBUG_RETURN(res);
 }
 
-int ha_mroonga::get_parent_foreign_key_list(THD *thd,
+int ha_mroonga::get_parent_foreign_key_list(const THD *thd,
                                             List<FOREIGN_KEY_INFO> *f_key_list)
 {
   MRN_DBUG_ENTER_METHOD();
diff --git a/storage/mroonga/ha_mroonga.hpp b/storage/mroonga/ha_mroonga.hpp
index 27219ffd158..52fd0ef9a0e 100644
--- a/storage/mroonga/ha_mroonga.hpp
+++ b/storage/mroonga/ha_mroonga.hpp
@@ -621,8 +621,8 @@ protected:
   char *get_tablespace_name(THD *thd, char *name, uint name_len);
 #endif
   bool can_switch_engines() mrn_override;
-  int get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list) mrn_override;
-  int get_parent_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list) mrn_override;
+  int get_foreign_key_list(const THD *thd, List<FOREIGN_KEY_INFO> *f_key_list) mrn_override;
+  int get_parent_foreign_key_list(const THD *thd, List<FOREIGN_KEY_INFO> *f_key_list) mrn_override;
   uint referenced_by_foreign_key() mrn_override;
   void init_table_handle_for_HANDLER() mrn_override;
   void free_foreign_key_create_info(char* str) mrn_override;
@@ -1273,10 +1273,10 @@ private:
 #endif
   bool wrapper_can_switch_engines();
   bool storage_can_switch_engines();
-  int wrapper_get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list);
-  int storage_get_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list);
-  int wrapper_get_parent_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list);
-  int storage_get_parent_foreign_key_list(THD *thd, List<FOREIGN_KEY_INFO> *f_key_list);
+  int wrapper_get_foreign_key_list(const THD *thd, List<FOREIGN_KEY_INFO> *f_key_list);
+  int storage_get_foreign_key_list(const THD *thd, List<FOREIGN_KEY_INFO> *f_key_list);
+  int wrapper_get_parent_foreign_key_list(const THD *thd, List<FOREIGN_KEY_INFO> *f_key_list);
+  int storage_get_parent_foreign_key_list(const THD *thd, List<FOREIGN_KEY_INFO> *f_key_list);
   uint wrapper_referenced_by_foreign_key();
   uint storage_referenced_by_foreign_key();
   void wrapper_init_table_handle_for_HANDLER();
diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c
index 6a4635e535e..35b1e05d3a5 100644
--- a/tests/mysql_client_test.c
+++ b/tests/mysql_client_test.c
@@ -19125,7 +19125,7 @@ static void test_progress_reporting()
   }
   
   progress_stage= progress_max_stage= progress_count= 0;
-  rc= mysql_query(conn, "alter table t1 add f1 int primary key auto_increment, order by f2");
+  rc= mysql_query(conn, "alter table t1 add f1 int primary key auto_increment, lock=shared, order by f2");
   myquery(rc);
   if (!opt_silent)
     printf("Got progress_count: %u  stage: %u  max_stage: %u\n",
@@ -19133,7 +19133,7 @@ static void test_progress_reporting()
   DIE_UNLESS(progress_count > 0 && progress_stage >=2 && progress_max_stage == 3);
 
   progress_stage= progress_max_stage= progress_count= 0;
-  rc= mysql_query(conn, "create index f2 on t1 (f2)");
+  rc= mysql_query(conn, "create index f2 on t1 (f2) lock=shared");
   myquery(rc);
   if (!opt_silent)
     printf("Got progress_count: %u  stage: %u  max_stage: %u\n",
@@ -19146,7 +19146,7 @@ static void test_progress_reporting()
   if (!opt_silent)
     printf("Got progress_count: %u  stage: %u  max_stage: %u\n",
            progress_count, progress_stage, progress_max_stage);
-  DIE_UNLESS(progress_count > 0 && progress_stage >=2 && progress_max_stage == 2);
+  DIE_UNLESS(progress_count > 0 && progress_stage >=2 && progress_max_stage == 4);
 
   rc= mysql_query(conn, "set @@global.progress_report_time=@save");
   myquery(rc);
