diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 02e16da1915b00bc8eeb6ba429b547652c43be4e..8c4acdee2868fcd06d7081a9b1f14db6d79e5606 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -293,7 +293,19 @@ mem_apply_numeric_type(struct Mem *record)
  *    Convert mem to a string representation.
  *
  * SCALAR:
- *    Mem is unchanged, but flag is set to BLOB.
+ *    Mem is unchanged, but flag is set to BLOB in case of
+ *    scalar-like type. Otherwise, (MAP, ARRAY) conversion
+ *    is impossible.
+ *
+ * BOOLEAN:
+ *    If memory holds BOOLEAN no actions take place.
+ *
+ * ANY:
+ *    Mem is unchanged, no actions take place.
+ *
+ * MAP/ARRAY:
+ *    These types can't be casted to scalar ones, or to each
+ *    other. So the only valid conversion is to type itself.
  *
  * @param record The value to apply type to.
  * @param type The type to be applied.
@@ -348,6 +360,27 @@ mem_apply_type(struct Mem *record, enum field_type type)
 			return -1;
 		return 0;
 	case FIELD_TYPE_SCALAR:
+		/* Can't cast MAP and ARRAY to scalar types. */
+		if ((record->flags & MEM_Subtype) != 0 &&
+		    record->subtype == SQL_SUBTYPE_MSGPACK) {
+			assert(mp_typeof(*record->z) == MP_MAP ||
+			       mp_typeof(*record->z) == MP_ARRAY);
+			return -1;
+		}
+		return 0;
+	case FIELD_TYPE_MAP:
+		if ((record->flags & MEM_Subtype) != 0 &&
+		    record->subtype == SQL_SUBTYPE_MSGPACK &&
+		    mp_typeof(*record->z) == MP_MAP)
+			return 0;
+		return -1;
+	case FIELD_TYPE_ARRAY:
+		if ((record->flags & MEM_Subtype) != 0 &&
+		    record->subtype == SQL_SUBTYPE_MSGPACK &&
+		    mp_typeof(*record->z) == MP_ARRAY)
+			return 0;
+		return -1;
+	case FIELD_TYPE_ANY:
 		return 0;
 	default:
 		return -1;
diff --git a/test/sql/types.result b/test/sql/types.result
index 773586023ee932aa46ab6df66e4e760f4cd3f4e9..124a5ca976b47cab2174006db57daf182e4e03dd 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -1546,3 +1546,46 @@ box.space.T:drop()
 box.space.T1:drop()
 ---
 ...
+--
+-- gh-4189: make sure that update doesn't throw an error if format
+-- of table features map/array field types.
+--
+format = {}
+---
+...
+format[1] = {type = 'integer', name = 'I'}
+---
+...
+format[2] = {type = 'boolean', name = 'B'}
+---
+...
+format[3] = {type = 'array', name = 'F1'}
+---
+...
+format[4] = {type = 'map', name = 'F2'}
+---
+...
+format[5] = {type = 'any', name = 'F3'}
+---
+...
+s = box.schema.space.create('T', {format = format})
+---
+...
+ii = s:create_index('ii')
+---
+...
+s:insert({1, true, {1, 2}, {a = 3}, 'asd'})
+---
+- [1, true, [1, 2], {'a': 3}, 'asd']
+...
+box.execute('UPDATE t SET b = false WHERE i = 1;')
+---
+- row_count: 1
+...
+s:select()
+---
+- - [1, false, [1, 2], {'a': 3}, 'asd']
+...
+s:drop()
+---
+...
diff --git a/test/sql/types.test.lua b/test/sql/types.test.lua
index fe0308fadc63df13613e604751e10c337c94d97f..ce933596c2eaf1e40019da2ccef1f00ddfffaced 100644
--- a/test/sql/types.test.lua
+++ b/test/sql/types.test.lua
@@ -351,3 +351,20 @@ box.execute("SELECT typeof(CAST(0 AS UNSIGNED));")
 
 box.space.T:drop()
 box.space.T1:drop()
+
+--
+-- gh-4189: make sure that update doesn't throw an error if format
+-- of table features map/array field types.
+--
+format = {}
+format[1] = {type = 'integer', name = 'I'}
+format[2] = {type = 'boolean', name = 'B'}
+format[3] = {type = 'array', name = 'F1'}
+format[4] = {type = 'map', name = 'F2'}
+format[5] = {type = 'any', name = 'F3'}
+s = box.schema.space.create('T', {format = format})
+ii = s:create_index('ii')
+s:insert({1, true, {1, 2}, {a = 3}, 'asd'})
+box.execute('UPDATE t SET b = false WHERE i = 1;')
+s:select()
+s:drop()