Repository: snowflakedb/gosnowflake
Pull request #1171 (2024-07-02 11:17:07)

SNOW-1501890 Implement structured maps
### Description

SNOW-1501890 Implemented structured maps

### Checklist
- [ ] Created tests which fail without the change (if possible)
- [ ] Extended the README / documentation, if necessary


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) Report
Attention: Patch coverage is `78.40000%` with `108 lines` in your changes missing coverage. Please review.
> Project coverage is 82.71%. Comparing base [(`52590b9`)](https://app.codecov.io/gh/snowflakedb/gosnowflake/commit/52590b96192ba216dc82470441e5206e8549cb10?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) to head [(`01835fa`)](https://app.codecov.io/gh/snowflakedb/gosnowflake/commit/01835fa99f146af31b93a8e6771370a8dd14c847?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).

| [Files](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) | Patch % | Lines |
|---|---|---|
| [converter.go](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?src=pr&el=tree&filepath=converter.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb#diff-Y29udmVydGVyLmdv) | 83.73% | [60 Missing :warning: ](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) |
| [structured\_type.go](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?src=pr&el=tree&filepath=structured_type.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb#diff-c3RydWN0dXJlZF90eXBlLmdv) | 61.06% | [44 Missing :warning: ](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) |
| [datetime.go](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?src=pr&el=tree&filepath=datetime.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb#diff-ZGF0ZXRpbWUuZ28=) | 60.00% | [4 Missing :warning: ](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1171      +/-   ##
==========================================
- Coverage   83.16%   82.71%   -0.45%     
==========================================
  Files          54       54              
  Lines       12149    12580     +431     
==========================================
+ Hits        10104    10406     +302     
- Misses       2045     2174     +129     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1171?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).


Patch:
diff --git a/bind_uploader.go b/bind_uploader.go
index 682605d..04a266a 100644
--- a/bind_uploader.go
+++ b/bind_uploader.go
@@ -256,9 +256,9 @@ func getBindValues(bindings []driver.NamedValue, params map[string]*string) (map
 			}
 			if t == nullType || t == unSupportedType {
 				t = textType // if null or not supported, pass to GS as text
-			} else if t == nilObjectType {
+			} else if t == nilObjectType || t == mapType || t == nilMapType {
 				t = objectType
-			} else if t == emptyArrayType || t == nilArrayType {
+			} else if t == nilArrayType {
 				t = arrayType
 			}
 			bindValues[bindingName(binding, idx)] = execBindParameter{
@@ -355,3 +355,8 @@ func supportedStructuredArrayBind(nv *driver.NamedValue) bool {
 	typ := reflect.TypeOf(nv.Value)
 	return typ != nil && (typ.Kind() == reflect.Array || typ.Kind() == reflect.Slice)
 }
+
+func supportedStructuredMapBind(nv *driver.NamedValue) bool {
+	typ := reflect.TypeOf(nv.Value)
+	return typ != nil && (typ.Kind() == reflect.Map || typ == reflect.TypeOf(NilMapTypes{}))
+}
diff --git a/connection.go b/connection.go
index 29e7f89..a3e9bd5 100644
--- a/connection.go
+++ b/connection.go
@@ -470,7 +470,7 @@ func (sc *snowflakeConn) Ping(ctx context.Context) error {
 // CheckNamedValue determines which types are handled by this driver aside from
 // the instances captured by driver.Value
 func (sc *snowflakeConn) CheckNamedValue(nv *driver.NamedValue) error {
-	if supportedNullBind(nv) || supportedArrayBind(nv) || supportedStructuredObjectWriterBind(nv) || supportedStructuredArrayBind(nv) {
+	if supportedNullBind(nv) || supportedArrayBind(nv) || supportedStructuredObjectWriterBind(nv) || supportedStructuredArrayBind(nv) || supportedStructuredMapBind(nv) {
 		return nil
 	}
 	return driver.ErrSkip
diff --git a/converter.go b/converter.go
index f16f9ea..231893b 100644
--- a/converter.go
+++ b/converter.go
@@ -27,6 +27,7 @@ import (
 )
 
 const format = "2006-01-02 15:04:05.999999999"
+const numberDefaultPrecision = 38
 
 type timezoneType int
 
@@ -121,10 +122,12 @@ func goTypeToSnowflake(v driver.Value, tsmode snowflakeType) snowflakeType {
 	// structured arrays
 	if reflect.TypeOf(v).Kind() == reflect.Slice || (reflect.TypeOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).Elem().Kind() == reflect.Slice) {
 		return arrayType
-	} else if tsmode == emptyArrayType {
-		return emptyArrayType
 	} else if tsmode == nilArrayType {
 		return nilArrayType
+	} else if tsmode == nilMapType {
+		return nilMapType
+	} else if reflect.TypeOf(v).Kind() == reflect.Map || (reflect.TypeOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).Elem().Kind() == reflect.Map) {
+		return mapType
 	}
 	return unSupportedType
 }
@@ -254,12 +257,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri
 	case reflect.Slice, reflect.Array:
 		return arrayToString(v, tsmode, params)
 	case reflect.Map:
-		if v1.IsNil() {
-			return bindingValue{nil, "", nil}, nil
-		}
-		// TODO: is this good enough?
-		s := v1.String()
-		return bindingValue{&s, "", nil}, nil
+		return mapToString(v, tsmode, params)
 	case reflect.Struct:
 		return structValueToString(v, tsmode, params)
 	}
@@ -324,7 +322,10 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri
 		return bindingValue{&resString, "json", nil}, nil
 	} else if isArrayOfStructs(v) {
 		stringEntries := make([]string, v1.Len())
-		entrySchema := &bindingSchema{}
+		sowcForSingleElement, err := buildSowcFromType(params, reflect.TypeOf(v).Elem())
+		if err != nil {
+			return bindingValue{}, err
+		}
 		for i := 0; i < v1.Len(); i++ {
 			potentialSow := v1.Index(i)
 			if sow, ok := potentialSow.Interface().(StructuredObjectWriter); ok {
@@ -333,7 +334,6 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri
 					return bindingValue{nil, "json", nil}, err
 				}
 				stringEntries[i] = *bv.value
-				entrySchema = bv.schema
 			}
 		}
 		value := "[" + strings.Join(stringEntries, ",") + "]"
@@ -344,7 +344,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri
 				{
 					Type:     "OBJECT",
 					Nullable: true,
-					Fields:   entrySchema.Fields,
+					Fields:   sowcForSingleElement.toFields(),
 				},
 			},
 		}
@@ -388,6 +388,344 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri
 	return bindingValue{&resString, "json", nil}, nil
 }
 
+func mapToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) {
+	var err error
+	valOf := reflect.Indirect(reflect.ValueOf(v))
+	if valOf.IsNil() {
+		return bindingValue{nil, "", nil}, nil
+	}
+	typOf := reflect.TypeOf(v)
+	var jsonBytes []byte
+	if tsmode == binaryType {
+		m := make(map[string]*string, valOf.Len())
+		iter := valOf.MapRange()
+		for iter.Next() {
+			val := iter.Value().Interface().([]byte)
+			if val != nil {
+				s := hex.EncodeToString(val)
+				m[stringOrIntToString(iter.Key())] = &s
+			} else {
+				m[stringOrIntToString(iter.Key())] = nil
+			}
+		}
+		jsonBytes, err = json.Marshal(m)
+		if err != nil {
+			return bindingValue{}, err
+		}
+	} else if typOf.Elem().AssignableTo(reflect.TypeOf(time.Time{})) || typOf.Elem().AssignableTo(reflect.TypeOf(sql.NullTime{})) {
+		m := make(map[string]*string, valOf.Len())
+		iter := valOf.MapRange()
+		for iter.Next() {
+			val, valid, err := toNullableTime(iter.Value().Interface())
+			if err != nil {
+				return bindingValue{}, err
+			}
+			if !valid {
+				m[stringOrIntToString(iter.Key())] = nil
+			} else {
+				typ := driverTypeToSnowflake[tsmode]
+				s, err := timeToString(val, typ, params)
+				if err != nil {
+					return bindingValue{}, err
+				}
+				m[stringOrIntToString(iter.Key())] = &s
+			}
+		}
+		jsonBytes, err = json.Marshal(m)
+		if err != nil {
+			return bindingValue{}, err
+		}
+	} else if typOf.Elem().AssignableTo(reflect.TypeOf(sql.NullString{})) {
+		m := make(map[string]*string, valOf.Len())
+		iter := valOf.MapRange()
+		for iter.Next() {
+			val := iter.Value().Interface().(sql.NullString)
+			if val.Valid {
+				m[stringOrIntToString(iter.Key())] = &val.String
+			} else {
+				m[stringOrIntToString(iter.Key())] = nil
+			}
+		}
+		jsonBytes, err = json.Marshal(m)
+		if err != nil {
+			return bindingValue{}, err
+		}
+	} else if typOf.Elem().AssignableTo(reflect.TypeOf(sql.NullByte{})) || typOf.Elem().AssignableTo(reflect.TypeOf(sql.NullInt16{})) || typOf.Elem().AssignableTo(reflect.TypeOf(sql.NullInt32{})) || typOf.Elem().AssignableTo(reflect.TypeOf(sql.NullInt64{})) {
+		m := make(map[string]*int64, valOf.Len())
+		iter := valOf.MapRange()
+		for iter.Next() {
+			val, valid := toNullableInt64(iter.Value().Interface())
+			if valid {
+				m[stringOrIntToString(iter.Key())] = &val
+			} else {
+				m[stringOrIntToString(iter.Key())] = nil
+			}
+		}
+		jsonBytes, err = json.Marshal(m)
+		if err != nil {
+			return bindingValue{}, err
+		}
+	} else if typOf.Elem().AssignableTo(reflect.TypeOf(sql.NullFloat64{})) {
+		m := make(map[string]*float64, valOf.Len())
+		iter := valOf.MapRange()
+		for iter.Next() {
+			val := iter.Value().Interface().(sql.NullFloat64)
+			if val.Valid {
+				m[stringOrIntToString(iter.Key())] = &val.Float64
+			} else {
+				m[stringOrIntToString(iter.Key())] = nil
+			}
+		}
+		jsonBytes, err = json.Marshal(m)
+		if err != nil {
+			return bindingValue{}, err
+		}
+	} else if typOf.Elem().AssignableTo(reflect.TypeOf(sql.NullBool{})) {
+		m := make(map[string]*bool, valOf.Len())
+		iter := valOf.MapRange()
+		for iter.Next() {
+			val := iter.Value().Interface().(sql.NullBool)
+			if val.Valid {
+				m[stringOrIntToString(iter.Key())] = &val.Bool
+			} else {
+				m[stringOrIntToString(iter.Key())] = nil
+			}
+		}
+		jsonBytes, err = json.Marshal(m)
+		if err != nil {
+			return bindingValue{}, err
+		}
+	} else if typOf.Elem().AssignableTo(structuredObjectWriterType) {
+		m := make(map[string]map[string]any, valOf.Len())
+		iter := valOf.MapRange()
+		var valueMetadata *fieldMetadata
+		for iter.Next() {
+			sowc := structuredObjectWriterContext{}
+			sowc.init(params)
+			if iter.Value().IsNil() {
+				m[stringOrIntToString(iter.Key())] = nil
+				continue
+			}
+			err = iter.Value().Interface().(StructuredObjectWriter).Write(&sowc)
+			if err != nil {
+				return bindingValue{}, nil
+			}
+			m[stringOrIntToString(iter.Key())] = sowc.values
+			if valueMetadata == nil {
+				valueMetadata = &fieldMetadata{
+					Type:     "OBJECT",
+					Nullable: true,
+					Fields:   sowc.toFields(),
+				}
+			}
+		}
+		if valueMetadata == nil {
+			sowcFromValueType, err := buildSowcFromType(params, typOf.Elem())
+			if err != nil {
+				return bindingValue{}, err
+			}
+			valueMetadata = &fieldMetadata{
+				Type:     "OBJECT",
+				Nullable: true,
+				Fields:   sowcFromValueType.toFields(),
+			}
+		}
+		jsonBytes, err = json.Marshal(m)
+		if err != nil {
+			return bindingValue{}, err
+		}
+		jsonString := string(jsonBytes)
+		keyMetadata, err := goTypeToFieldMetadata(typOf.Key(), textType, params)
+		if err != nil {
+			return bindingValue{}, err
+		}
+		schema := bindingSchema{
+			Typ:    "MAP",
+			Fields: []fieldMetadata{keyMetadata, *valueMetadata},
+		}
+		return bindingValue{&jsonString, "json", &schema}, nil
+	} else {
+		jsonBytes, err = json.Marshal(v)
+		if err != nil {
+			return bindingValue{}, err
+		}
+	}
+	jsonString := string(jsonBytes)
+	keyMetadata, err := goTypeToFieldMetadata(typOf.Key(), textType, params)
+	if err != nil {
+		return bindingValue{}, err
+	}
+	valueMetadata, err := goTypeToFieldMetadata(typOf.Elem(), tsmode, params)
+	if err != nil {
+		return bindingValue{}, err
+	}
+	schema := bindingSchema{
+		Typ:    "MAP",
+		Fields: []fieldMetadata{keyMetadata, valueMetadata},
+	}
+	return bindingValue{&jsonString, "json", &schema}, nil
+}
+
+func toNullableInt64(val any) (int64, bool) {
+	switch v := val.(type) {
+	case sql.NullByte:
+		return int64(v.Byte), v.Valid
+	case sql.NullInt16:
+		return int64(v.Int16), v.Valid
+	case sql.NullInt32:
+		return int64(v.Int32), v.Valid
+	case sql.NullInt64:
+		return v.Int64, v.Valid
+	}
+	// should never happen, the list above is exhaustive
+	panic("Only byte, int16, int32 or int64 are supported")
+}
+
+func toNullableTime(val any) (time.Time, bool, error) {
+	switch v := val.(type) {
+	case time.Time:
+		return v, true, nil
+	case sql.NullTime:
+		return v.Time, v.Valid, nil
+	}
+	return time.Now(), false, fmt.Errorf("cannot use %T as time", val)
+}
+
+func stringOrIntToString(v reflect.Value) string {
+	if v.CanInt() {
+		return strconv.Itoa(int(v.Int()))
+	}
+	return v.String()
+}
+
+func goTypeToFieldMetadata(typ reflect.Type, tsmode snowflakeType, params map[string]*string) (fieldMetadata, error) {
+	if tsmode == binaryType {
+		return fieldMetadata{
+			Type:     "BINARY",
+			Nullable: true,
+		}, nil
+	}
+	if typ.Kind() == reflect.Pointer {
+		typ = typ.Elem()
+	}
+	switch typ.Kind() {
+	case reflect.String:
+		return fieldMetadata{
+			Type:     "TEXT",
+			Nullable: true,
+		}, nil
+	case reflect.Bool:
+		return fieldMetadata{
+			Type:     "BOOLEAN",
+			Nullable: true,
+		}, nil
+	case reflect.Int, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return fieldMetadata{
+			Type:      "FIXED",
+			Precision: numberDefaultPrecision,
+			Nullable:  true,
+		}, nil
+	case reflect.Float32, reflect.Float64:
+		return fieldMetadata{
+			Type:     "REAL",
+			Nullable: true,
+		}, nil
+	case reflect.Struct:
+		if typ.AssignableTo(reflect.TypeOf(sql.NullString{})) {
+			return fieldMetadata{
+				Type:     "TEXT",
+				Nullable: true,
+			}, nil
+		} else if typ.AssignableTo(reflect.TypeOf(sql.NullBool{})) {
+			return fieldMetadata{
+				Type:     "BOOLEAN",
+				Nullable: true,
+			}, nil
+		} else if typ.AssignableTo(reflect.TypeOf(sql.NullByte{})) || typ.AssignableTo(reflect.TypeOf(sql.NullInt16{})) || typ.AssignableTo(reflect.TypeOf(sql.NullInt32{})) || typ.AssignableTo(reflect.TypeOf(sql.NullInt64{})) {
+			return fieldMetadata{
+				Type:      "FIXED",
+				Precision: numberDefaultPrecision,
+				Nullable:  true,
+			}, nil
+		} else if typ.AssignableTo(reflect.TypeOf(sql.NullFloat64{})) {
+			return fieldMetadata{
+				Type:     "REAL",
+				Nullable: true,
+			}, nil
+		} else if tsmode == dateType {
+			return fieldMetadata{
+				Type:     "DATE",
+				Nullable: true,
+			}, nil
+		} else if tsmode == timeType {
+			return fieldMetadata{
+				Type:     "TIME",
+				Nullable: true,
+			}, nil
+		} else if tsmode == timestampTzType {
+			return fieldMetadata{
+				Type:     "TIMESTAMP_TZ",
+				Nullable: true,
+			}, nil
+		} else if tsmode == timestampNtzType {
+			return fieldMetadata{
+				Type:     "TIMESTAMP_NTZ",
+				Nullable: true,
+			}, nil
+		} else if tsmode == timestampLtzType {
+			return fieldMetadata{
+				Type:     "TIMESTAMP_LTZ",
+				Nullable: true,
+			}, nil
+		} else if typ.AssignableTo(structuredObjectWriterType) || tsmode == nilObjectType {
+			sowc, err := buildSowcFromType(params, typ)
+			if err != nil {
+				return fieldMetadata{}, err
+			}
+			return fieldMetadata{
+				Type:     "OBJECT",
+				Nullable: true,
+				Fields:   sowc.toFields(),
+			}, nil
+		} else if tsmode == nilArrayType || tsmode == nilMapType {
+			sowc, err := buildSowcFromType(params, typ)
+			if err != nil {
+				return fieldMetadata{}, err
+			}
+			return fieldMetadata{
+				Type:     "OBJECT",
+				Nullable: true,
+				Fields:   sowc.toFields(),
+			}, nil
+		}
+	case reflect.Slice:
+		metadata, err := goTypeToFieldMetadata(typ.Elem(), tsmode, params)
+		if err != nil {
+			return fieldMetadata{}, err
+		}
+		return fieldMetadata{
+			Type:     "ARRAY",
+			Nullable: true,
+			Fields:   []fieldMetadata{metadata},
+		}, nil
+	case reflect.Map:
+		keyMetadata, err := goTypeToFieldMetadata(typ.Key(), tsmode, params)
+		if err != nil {
+			return fieldMetadata{}, err
+		}
+		valueMetadata, err := goTypeToFieldMetadata(typ.Elem(), tsmode, params)
+		if err != nil {
+			return fieldMetadata{}, err
+		}
+		return fieldMetadata{
+			Type:     "MAP",
+			Nullable: true,
+			Fields:   []fieldMetadata{keyMetadata, valueMetadata},
+		}, nil
+	}
+	return fieldMetadata{}, fmt.Errorf("cannot build field metadata for %v (mode %v)", typ.Kind().String(), tsmode.String())
+}
+
 func isSliceOfSlices(v any) bool {
 	typ := reflect.TypeOf(v)
 	return typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Slice
@@ -452,46 +790,45 @@ func structValueToString(v driver.Value, tsmode snowflakeType, params map[string
 			Fields:   sowc.toFields(),
 		}
 		return bindingValue{&jsonString, "json", &schema}, nil
-	} else if typ, ok := v.(reflect.Type); ok {
-		sowc, err := buildSowcFromType(params, typ)
+	} else if typ, ok := v.(reflect.Type); ok && tsmode == nilArrayType {
+		metadata, err := goTypeToFieldMetadata(typ, tsmode, params)
 		if err != nil {
 			return bindingValue{}, err
 		}
 		schema := bindingSchema{
-			Typ:      "object",
+			Typ:      "ARRAY",
 			Nullable: true,
-			Fields:   sowc.toFields(),
+			Fields: []fieldMetadata{
+				metadata,
+			},
 		}
-		var val *string = nil
-		if tsmode == emptyArrayType {
-			emptyArray := "[]"
-			val = &emptyArray
-			schema = bindingSchema{
-				Typ:      "array",
-				Nullable: true,
-				Fields: []fieldMetadata{
-					{
-						Type:     schema.Typ,
-						Nullable: schema.Nullable,
-						Fields:   schema.Fields,
-					},
-				},
-			}
-		} else if tsmode == nilArrayType {
-			val = nil
-			schema = bindingSchema{
-				Typ:      "array",
-				Nullable: true,
-				Fields: []fieldMetadata{
-					{
-						Type:     schema.Typ,
-						Nullable: schema.Nullable,
-						Fields:   schema.Fields,
-					},
-				},
-			}
+		return bindingValue{nil, "json", &schema}, nil
+	} else if types, ok := v.(NilMapTypes); ok && tsmode == nilMapType {
+		keyMetadata, err := goTypeToFieldMetadata(types.Key, tsmode, params)
+		if err != nil {
+			return bindingValue{}, err
 		}
-		return bindingValue{val, "json", &schema}, nil
+		valueMetadata, err := goTypeToFieldMetadata(types.Value, tsmode, params)
+		if err != nil {
+			return bindingValue{}, err
+		}
+		schema := bindingSchema{
+			Typ:      "map",
+			Nullable: true,
+			Fields:   []fieldMetadata{keyMetadata, valueMetadata},
+		}
+		return bindingValue{nil, "json", &schema}, nil
+	} else if typ, ok := v.(reflect.Type); ok && tsmode == nilObjectType {
+		metadata, err := goTypeToFieldMetadata(typ, tsmode, params)
+		if err != nil {
+			return bindingValue{}, err
+		}
+		schema := bindingSchema{
+			Typ:      "object",
+			Nullable: true,
+			Fields:   metadata.Fields,
+		}
+		return bindingValue{nil, "json", &schema}, nil
 	}
 	return bindingValue{}, fmt.Errorf("unknown binding for type %T and mode %v", v, tsmode)
 }
@@ -694,7 +1031,11 @@ func jsonToMap(ctx context.Context, keyMetadata, valueMetadata fieldMetadata, sr
 		if valueMetadata.Type == "object" {
 			res := make(map[string]*structuredType)
 			for k, v := range m {
-				res[k] = buildStructuredTypeFromMap(v.(map[string]any), valueMetadata.Fields, params)
+				if v == nil || reflect.ValueOf(v).IsNil() {
+					res[k] = nil
+				} else {
+					res[k] = buildStructuredTypeFromMap(v.(map[string]any), valueMetadata.Fields, params)
+				}
 			}
 			return res, nil
 		}
@@ -838,11 +1179,15 @@ func jsonToMapWithKeyType[K comparable](ctx context.Context, valueMetadata field
 func buildArrayFromMap[K comparable, V any](ctx context.Context, valueMetadata fieldMetadata, m map[K]any, params map[string]*string) (snowflakeValue, error) {
 	res := make(map[K][]V)
 	for k, v := range m {
-		structuredArray, err := buildStructuredArray(ctx, valueMetadata, v.([]any), params)
-		if err != nil {
-			return nil, err
+		if v == nil {
+			res[k] = nil
+		} else {
+			structuredArray, err := buildStructuredArray(ctx, valueMetadata, v.([]any), params)
+			if err != nil {
+				return nil, err
+			}
+			res[k] = structuredArray.([]V)
 		}
-		res[k] = structuredArray.([]V)
 	}
 	return res, nil
 }
@@ -1564,7 +1909,7 @@ func buildStructuredMapFromArrow[K comparable](ctx context.Context, rowIdx int,
 func buildListFromNativeArrowMap[K comparable, V any](ctx context.Context, rowIdx int, valueMetadata fieldMetadata, offsets []int32, keyFunc func(j int) (K, error), items arrow.Array, higherPrecision bool, loc *time.Location, params map[string]*string) (snowflakeValue, error) {
 	return mapStructuredMapNativeArrowRows(make(map[K][]V), offsets, rowIdx, keyFunc, func(j int) ([]V, error) {
 		if items.IsNull(j) {
-			return []V{}, nil
+			return nil, nil
 		}
 		list, err := buildListFromNativeArrow(ctx, j, valueMetadata.Fields[0], items, loc, higherPrecision, params)
 		return list.([]V), err
diff --git a/datatype.go b/datatype.go
index 83176ee..6b8e238 100644
--- a/datatype.go
+++ b/datatype.go
@@ -34,7 +34,7 @@ const (
 	unSupportedType
 	nilObjectType
 	nilArrayType
-	emptyArrayType
+	nilMapType
 )
 
 var snowflakeToDriverType = map[string]snowflakeType{
@@ -113,8 +113,8 @@ var (
 	DataTypeNilObject = []byte{nilObjectType.Byte()}
 	// DataTypeNilArray represents a nil structured array.
 	DataTypeNilArray = []byte{nilArrayType.Byte()}
-	// DataTypeEmptyArray represents an empty structured array.
-	DataTypeEmptyArray = []byte{emptyArrayType.Byte()}
+	// DataTypeNilMap represents a nil structured map.
+	DataTypeNilMap = []byte{nilMapType.Byte()}
 )
 
 // dataTypeMode returns the subsequent data type in a string representation.
@@ -143,8 +143,8 @@ func dataTypeMode(v driver.Value) (tsmode snowflakeType, err error) {
 			tsmode = nilObjectType
 		case bytes.Equal(bd, DataTypeNilArray):
 			tsmode = nilArrayType
-		case bytes.Equal(bd, DataTypeEmptyArray):
-			tsmode = emptyArrayType
+		case bytes.Equal(bd, DataTypeNilMap):
+			tsmode = nilMapType
 		default:
 			return nullType, fmt.Errorf(errMsgInvalidByteArray, v)
 		}
diff --git a/datetime.go b/datetime.go
index ab3cfe3..0e634c2 100644
--- a/datetime.go
+++ b/datetime.go
@@ -7,6 +7,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"time"
 )
 
 var incorrectSecondsFractionRegex = regexp.MustCompile(`[^.,]FF`)
@@ -34,6 +35,18 @@ var formatReplacements = []formatReplacement{
 	{input: "TZM", output: "00"},
 }
 
+func timeToString(t time.Time, dateTimeType string, params map[string]*string) (string, error) {
+	sfFormat, err := dateTimeInputFormatByType(dateTimeType, params)
+	if err != nil {
+		return "", err
+	}
+	goFormat, err := snowflakeFormatToGoFormat(sfFormat)
+	if err != nil {
+		return "", err
+	}
+	return t.Format(goFormat), nil
+}
+
 func snowflakeFormatToGoFormat(sfFormat string) (string, error) {
 	res := sfFormat
 	for _, replacement := range formatReplacements {
diff --git a/structured_type.go b/structured_type.go
index 07e568c..bfb48c2 100644
--- a/structured_type.go
+++ b/structured_type.go
@@ -75,9 +75,19 @@ type StructuredObjectWriterContext interface {
 	WriteNullTime(fieldName string, value sql.NullTime, tsmode []byte) error
 	WriteStruct(fieldName string, value StructuredObjectWriter) error
 	WriteNullableStruct(fieldName string, value StructuredObjectWriter, typ reflect.Type) error
+	// WriteRaw is used for inserting slices and maps only.
+	WriteRaw(fieldName string, value any, tsmode ...[]byte) error
+	// WriteNullRaw is used for inserting nil slices and maps only.
+	WriteNullRaw(fieldName string, typ reflect.Type, tsmode ...[]byte) error
 	WriteAll(sow StructuredObjectWriter) error
 }
 
+// NilMapTypes is used to define types when binding nil maps.
+type NilMapTypes struct {
+	Key   reflect.Type
+	Value reflect.Type
+}
+
 type structuredObjectWriterEntry struct {
 	name      string
 	typ       string
@@ -294,6 +304,75 @@ func (sowc *structuredObjectWriterContext) WriteNullableStruct(structFieldName s
 	return sowc.WriteStruct(structFieldName, value)
 }
 
+func (sowc *structuredObjectWriterContext) WriteRaw(fieldName string, value any, dataTypeModes ...[]byte) error {
+	dataTypeModeSingle := DataTypeArray
+	if len(dataTypeModes) == 1 && dataTypeModes[0] != nil {
+		dataTypeModeSingle = dataTypeModes[0]
+	}
+	tsmode, err := dataTypeMode(dataTypeModeSingle)
+	if err != nil {
+		return err
+	}
+
+	switch reflect.ValueOf(value).Kind() {
+	case reflect.Slice:
+		metadata, err := goTypeToFieldMetadata(reflect.TypeOf(value).Elem(), tsmode, sowc.params)
+		if err != nil {
+			return err
+		}
+		return sowc.write(value, structuredObjectWriterEntry{
+			name:     fieldName,
+			typ:      "ARRAY",
+			nullable: true,
+			fields:   []fieldMetadata{metadata},
+		})
+	case reflect.Map:
+		keyMetadata, err := goTypeToFieldMetadata(reflect.TypeOf(value).Key(), tsmode, sowc.params)
+		if err != nil {
+			return err
+		}
+		valueMetadata, err := goTypeToFieldMetadata(reflect.TypeOf(value).Elem(), tsmode, sowc.params)
+		if err != nil {
+			return err
+		}
+		return sowc.write(value, structuredObjectWriterEntry{
+			name:     fieldName,
+			typ:      "MAP",
+			nullable: true,
+			fields:   []fieldMetadata{keyMetadata, valueMetadata},
+		})
+	}
+	return fmt.Errorf("unsupported raw type: %T", value)
+}
+
+func (sowc *structuredObjectWriterContext) WriteNullRaw(fieldName string, typ reflect.Type, dataTypeModes ...[]byte) error {
+	dataTypeModeSingle := DataTypeArray
+	if len(dataTypeModes) == 1 && dataTypeModes[0] != nil {
+		dataTypeModeSingle = dataTypeModes[0]
+	}
+	tsmode, err := dataTypeMode(dataTypeModeSingle)
+	if err != nil {
+		return err
+	}
+
+	if typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map {
+		metadata, err := goTypeToFieldMetadata(typ, tsmode, sowc.params)
+		if err != nil {
+			return err
+		}
+		if err := sowc.write(nil, structuredObjectWriterEntry{
+			name:     fieldName,
+			typ:      metadata.Type,
+			nullable: true,
+			fields:   metadata.Fields,
+		}); err != nil {
+			return err
+		}
+		return nil
+	}
+	return fmt.Errorf("cannot use %v as nillable field", typ.Kind().String())
+}
+
 func buildSowcFromType(params map[string]*string, typ reflect.Type) (*structuredObjectWriterContext, error) {
 	childSowc := &structuredObjectWriterContext{}
 	childSowc.init(params)
@@ -319,12 +398,9 @@ func buildSowcFromType(params map[string]*string, typ reflect.Type) (*structured
 			if err := childSowc.writeBool(fieldName, nil); err != nil {
 				return nil, err
 			}
-		} else if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {
-			if field.Type.Elem().Kind() == reflect.Uint8 {
-				if err := childSowc.WriteBytes(fieldName, nil); err != nil {
-					return nil, err
-				}
-				// TODO structured types - handle arrays
+		} else if (field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array) && field.Type.Elem().Kind() == reflect.Uint8 {
+			if err := childSowc.WriteBytes(fieldName, nil); err != nil {
+				return nil, err
 			}
 		} else if field.Type.Kind() == reflect.Struct || field.Type.Kind() == reflect.Pointer {
 			t := field.Type
@@ -364,6 +440,9 @@ func buildSowcFromType(params map[string]*string, typ reflect.Type) (*structured
 				if err != nil {
 					return nil, err
 				}
+				if timeSnowflakeType == nil {
+					return nil, fmt.Errorf("field %v does not have proper sf tag", fieldName)
+				}
 				if err := childSowc.WriteNullTime(fieldName, sql.NullTime{}, timeSnowflakeType); err != nil {
 					return nil, err
 				}
@@ -374,6 +453,14 @@ func buildSowcFromType(params map[string]*string, typ reflect.Type) (*structured
 			} else {
 				return nil, fmt.Errorf("field %s has unsupported type", field.Name)
 			}
+		} else if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Map {
+			timeSnowflakeType, err := getTimeSnowflakeType(field)
+			if err != nil {
+				return nil, err
+			}
+			if err := childSowc.WriteNullRaw(fieldName, field.Type, timeSnowflakeType); err != nil {
+				return nil, err
+			}
 		}
 	}
 	return childSowc, nil
@@ -449,11 +536,9 @@ func (sowc *structuredObjectWriterContext) WriteAll(sow StructuredObjectWriter)
 			if err := sowc.WriteBool(fieldName, val.Field(i).Bool()); err != nil {
 				return err
 			}
-		} else if field.Type.Kind() == reflect.Array || field.Type.Kind() == reflect.Slice {
-			if field.Type.Elem().Kind() == reflect.Uint8 {
-				if err := sowc.WriteBytes(fieldName, val.Field(i).Bytes()); err != nil {
-					return err
-				}
+		} else if (field.Type.Kind() == reflect.Array || field.Type.Kind() == reflect.Slice) && field.Type.Elem().Kind() == reflect.Uint8 {
+			if err := sowc.WriteBytes(fieldName, val.Field(i).Bytes()); err != nil {
+				return err
 			}
 		} else if field.Type.Kind() == reflect.Struct || field.Type.Kind() == reflect.Pointer {
 			if v, ok := val.Field(i).Interface().(time.Time); ok {
@@ -461,6 +546,9 @@ func (sowc *structuredObjectWriterContext) WriteAll(sow StructuredObjectWriter)
 				if err != nil {
 					return err
 				}
+				if timeSnowflakeType == nil {
+					return fmt.Errorf("field %v does not have a proper sf tag", fieldName)
+				}
 				if err := sowc.WriteTime(fieldName, v, timeSnowflakeType); err != nil {
 					return err
 				}
@@ -497,6 +585,9 @@ func (sowc *structuredObjectWriterContext) WriteAll(sow StructuredObjectWriter)
 				if err != nil {
 					return err
 				}
+				if timeSnowflakeType == nil {
+					return fmt.Errorf("field %v does not have a proper sf tag", fieldName)
+				}
 				if err := sowc.WriteNullTime(fieldName, v, timeSnowflakeType); err != nil {
 					return err
 				}
@@ -511,16 +602,30 @@ func (sowc *structuredObjectWriterContext) WriteAll(sow StructuredObjectWriter)
 					if err := v.Write(childSowc); err != nil {
 						return err
 					}
-					return sowc.write(childSowc.values, structuredObjectWriterEntry{
+					if err := sowc.write(childSowc.values, structuredObjectWriterEntry{
 						name:     fieldName,
 						typ:      "OBJECT",
 						nullable: true,
 						fields:   childSowc.toFields(),
-					})
+					}); err != nil {
+						return err
+					}
+				}
+			}
+		} else if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Map {
+			var timeSfType []byte
+			var err error
+			if field.Type.Elem().AssignableTo(reflect.TypeOf(time.Time{})) || field.Type.Elem().AssignableTo(reflect.TypeOf(sql.NullTime{})) {
+				timeSfType, err = getTimeSnowflakeType(typ.Field(i))
+				if err != nil {
+					return err
 				}
 			}
-			//} else {
-			//	return fmt.Errorf("field %s has unsupported type", field.Name) // TODO structured type uncomment when all types are ready
+			if err := sowc.WriteRaw(fieldName, val.Field(i).Interface(), timeSfType); err != nil {
+				return err
+			}
+		} else {
+			return fmt.Errorf("field %s has unsupported type", field.Name)
 		}
 	}
 	return nil
@@ -566,13 +671,20 @@ func ScanArrayOfScanners[T sql.Scanner](value *[]T) *ArrayOfScanners[T] {
 type MapOfScanners[K comparable, V sql.Scanner] map[K]V
 
 func (st *MapOfScanners[K, V]) Scan(val any) error {
+	if val == nil {
+		return nil
+	}
 	sts := val.(map[K]*structuredType)
 	*st = make(map[K]V)
 	var someV V
-	for k := range sts {
-		(*st)[k] = reflect.New(reflect.TypeOf(someV).Elem()).Interface().(V)
-		if err := (*st)[k].Scan(sts[k]); err != nil {
-			return err
+	for k, v := range sts {
+		if v != nil && !reflect.ValueOf(v).IsNil() {
+			(*st)[k] = reflect.New(reflect.TypeOf(someV).Elem()).Interface().(V)
+			if err := (*st)[k].Scan(sts[k]); err != nil {
+				return err
+			}
+		} else {
+			(*st)[k] = reflect.Zero(reflect.TypeOf(someV)).Interface().(V)
 		}
 	}
 	return nil
@@ -1068,7 +1180,7 @@ func shouldIgnoreField(field reflect.StructField) bool {
 func getTimeSnowflakeType(field reflect.StructField) ([]byte, error) {
 	sfTag := strings.ToLower(field.Tag.Get("sf"))
 	if sfTag == "" {
-		return nil, fmt.Errorf("%s is of time type, but it does not have sf tag", field.Name)
+		return nil, nil
 	}
 	values := strings.Split(sfTag, ",")[1:]
 	if contains(values, "time") {
@@ -1082,5 +1194,5 @@ func getTimeSnowflakeType(field reflect.StructField) ([]byte, error) {
 	} else if contains(values, "tz") {
 		return DataTypeTimestampTz, nil
 	}
-	return nil, fmt.Errorf("%s is of time type, but does not contain snowflake time type, use date, time, ltz, ntz or tz", field.Name)
+	return nil, nil
 }


Test patch:
diff --git a/structured_type_read_test.go b/structured_type_read_test.go
index efedecb..773fb90 100644
--- a/structured_type_read_test.go
+++ b/structured_type_read_test.go
@@ -163,6 +163,15 @@ func (o objectWithAllTypes) Write(sowc StructuredObjectWriterContext) error {
 	if err := sowc.WriteStruct("so", o.so); err != nil {
 		return err
 	}
+	if err := sowc.WriteRaw("sArr", o.sArr); err != nil {
+		return err
+	}
+	if err := sowc.WriteRaw("f64Arr", o.f64Arr); err != nil {
+		return err
+	}
+	if err := sowc.WriteRaw("someMap", o.someMap); err != nil {
+		return err
+	}
 	return nil
 }
 
@@ -394,6 +403,15 @@ func (o *objectWithAllTypesNullable) Write(sowc StructuredObjectWriterContext) e
 	if err := sowc.WriteNullableStruct("so", o.so, reflect.TypeOf(simpleObject{})); err != nil {
 		return err
 	}
+	if err := sowc.WriteRaw("sArr", o.sArr); err != nil {
+		return err
+	}
+	if err := sowc.WriteRaw("f64Arr", o.f64Arr); err != nil {
+		return err
+	}
+	if err := sowc.WriteRaw("someMap", o.someMap); err != nil {
+		return err
+	}
 	return nil
 }
 
diff --git a/structured_type_write_test.go b/structured_type_write_test.go
index 8e6ebb6..c36ef33 100644
--- a/structured_type_write_test.go
+++ b/structured_type_write_test.go
@@ -1,6 +1,7 @@
 package gosnowflake
 
 import (
+	"context"
 	"database/sql"
 	"fmt"
 	"reflect"
@@ -130,7 +131,7 @@ func TestBindingObjectWithSchema(t *testing.T) {
 	warsawTz, err := time.LoadLocation("Europe/Warsaw")
 	assertNilF(t, err)
 	runDBTest(t, func(dbt *DBTest) {
-		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER)))")
+		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))")
 		defer func() {
 			dbt.mustExec("DROP TABLE IF EXISTS test_object_binding")
 		}()
@@ -154,6 +155,9 @@ func TestBindingObjectWithSchema(t *testing.T) {
 			ntz:       time.Date(2026, time.May, 24, 11, 22, 33, 0, time.UTC),
 			tz:        time.Date(2027, time.May, 24, 11, 22, 33, 44, warsawTz),
 			so:        &simpleObject{s: "another string", i: 123},
+			sArr:      []string{"a", "b"},
+			f64Arr:    []float64{1.1, 2.2},
+			someMap:   map[string]bool{"a": true, "b": false},
 		}
 		dbt.mustExec("INSERT INTO test_object_binding SELECT (?)", o)
 		rows := dbt.mustQuery("SELECT * FROM test_object_binding WHERE obj = ?", o)
@@ -181,6 +185,9 @@ func TestBindingObjectWithSchema(t *testing.T) {
 		assertTrueE(t, res.tz.Equal(o.tz))
 		assertTrueE(t, res.ntz.Equal(o.ntz))
 		assertDeepEqualE(t, res.so, o.so)
+		assertDeepEqualE(t, res.sArr, o.sArr)
+		assertDeepEqualE(t, res.f64Arr, o.f64Arr)
+		assertDeepEqualE(t, res.someMap, o.someMap)
 	})
 }
 
@@ -188,7 +195,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) {
 	warsawTz, err := time.LoadLocation("Europe/Warsaw")
 	assertNilF(t, err)
 	runDBTest(t, func(dbt *DBTest) {
-		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER)))")
+		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))")
 		defer func() {
 			dbt.mustExec("DROP TABLE IF EXISTS test_object_binding")
 		}()
@@ -197,20 +204,23 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) {
 		dbt.mustExec("ALTER SESSION SET TIMESTAMP_OUTPUT_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF9 TZHTZM'")
 		t.Run("not null", func(t *testing.T) {
 			o := &objectWithAllTypesNullable{
-				s:    sql.NullString{String: "some string", Valid: true},
-				b:    sql.NullByte{Byte: 1, Valid: true},
-				i16:  sql.NullInt16{Int16: 2, Valid: true},
-				i32:  sql.NullInt32{Int32: 3, Valid: true},
-				i64:  sql.NullInt64{Int64: 4, Valid: true},
-				f64:  sql.NullFloat64{Float64: 2.2, Valid: true},
-				bo:   sql.NullBool{Bool: true, Valid: true},
-				bi:   []byte{'a', 'b', 'c'},
-				date: sql.NullTime{Time: time.Date(2024, time.May, 24, 0, 0, 0, 0, time.UTC), Valid: true},
-				time: sql.NullTime{Time: time.Date(1, 1, 1, 11, 22, 33, 0, time.UTC), Valid: true},
-				ltz:  sql.NullTime{Time: time.Date(2025, time.May, 24, 11, 22, 33, 44, warsawTz), Valid: true},
-				ntz:  sql.NullTime{Time: time.Date(2026, time.May, 24, 11, 22, 33, 0, time.UTC), Valid: true},
-				tz:   sql.NullTime{Time: time.Date(2027, time.May, 24, 11, 22, 33, 44, warsawTz), Valid: true},
-				so:   &simpleObject{s: "another string", i: 123},
+				s:       sql.NullString{String: "some string", Valid: true},
+				b:       sql.NullByte{Byte: 1, Valid: true},
+				i16:     sql.NullInt16{Int16: 2, Valid: true},
+				i32:     sql.NullInt32{Int32: 3, Valid: true},
+				i64:     sql.NullInt64{Int64: 4, Valid: true},
+				f64:     sql.NullFloat64{Float64: 2.2, Valid: true},
+				bo:      sql.NullBool{Bool: true, Valid: true},
+				bi:      []byte{'a', 'b', 'c'},
+				date:    sql.NullTime{Time: time.Date(2024, time.May, 24, 0, 0, 0, 0, time.UTC), Valid: true},
+				time:    sql.NullTime{Time: time.Date(1, 1, 1, 11, 22, 33, 0, time.UTC), Valid: true},
+				ltz:     sql.NullTime{Time: time.Date(2025, time.May, 24, 11, 22, 33, 44, warsawTz), Valid: true},
+				ntz:     sql.NullTime{Time: time.Date(2026, time.May, 24, 11, 22, 33, 0, time.UTC), Valid: true},
+				tz:      sql.NullTime{Time: time.Date(2027, time.May, 24, 11, 22, 33, 44, warsawTz), Valid: true},
+				so:      &simpleObject{s: "another string", i: 123},
+				sArr:    []string{"a", "b"},
+				f64Arr:  []float64{1.1, 2.2},
+				someMap: map[string]bool{"a": true, "b": false},
 			}
 			dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o)
 			rows := dbt.mustQuery("SELECT * FROM test_object_binding WHERE obj = ?", o)
@@ -236,23 +246,29 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) {
 			assertTrueE(t, res.tz.Time.Equal(o.tz.Time))
 			assertTrueE(t, res.ntz.Time.Equal(o.ntz.Time))
 			assertDeepEqualE(t, res.so, o.so)
+			assertDeepEqualE(t, res.sArr, o.sArr)
+			assertDeepEqualE(t, res.f64Arr, o.f64Arr)
+			assertDeepEqualE(t, res.someMap, o.someMap)
 		})
 		t.Run("null", func(t *testing.T) {
 			o := &objectWithAllTypesNullable{
-				s:    sql.NullString{},
-				b:    sql.NullByte{},
-				i16:  sql.NullInt16{},
-				i32:  sql.NullInt32{},
-				i64:  sql.NullInt64{},
-				f64:  sql.NullFloat64{},
-				bo:   sql.NullBool{},
-				bi:   nil,
-				date: sql.NullTime{},
-				time: sql.NullTime{},
-				ltz:  sql.NullTime{},
-				ntz:  sql.NullTime{},
-				tz:   sql.NullTime{},
-				so:   nil,
+				s:       sql.NullString{},
+				b:       sql.NullByte{},
+				i16:     sql.NullInt16{},
+				i32:     sql.NullInt32{},
+				i64:     sql.NullInt64{},
+				f64:     sql.NullFloat64{},
+				bo:      sql.NullBool{},
+				bi:      nil,
+				date:    sql.NullTime{},
+				time:    sql.NullTime{},
+				ltz:     sql.NullTime{},
+				ntz:     sql.NullTime{},
+				tz:      sql.NullTime{},
+				so:      nil,
+				sArr:    nil,
+				f64Arr:  nil,
+				someMap: nil,
 			}
 			dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o)
 			rows := dbt.mustQuery("SELECT * FROM test_object_binding WHERE obj = ?", o)
@@ -278,6 +294,9 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) {
 			assertTrueE(t, res.tz.Time.Equal(o.tz.Time))
 			assertTrueE(t, res.ntz.Time.Equal(o.ntz.Time))
 			assertDeepEqualE(t, res.so, o.so)
+			assertDeepEqualE(t, res.sArr, o.sArr)
+			assertDeepEqualE(t, res.f64Arr, o.f64Arr)
+			assertDeepEqualE(t, res.someMap, o.someMap)
 		})
 	})
 }
@@ -286,7 +305,7 @@ func TestBindingObjectWithSchemaSimpleWrite(t *testing.T) {
 	warsawTz, err := time.LoadLocation("Europe/Warsaw")
 	assertNilF(t, err)
 	runDBTest(t, func(dbt *DBTest) {
-		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER)))")
+		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))")
 		defer func() {
 			dbt.mustExec("DROP TABLE IF EXISTS test_object_binding")
 		}()
@@ -310,6 +329,9 @@ func TestBindingObjectWithSchemaSimpleWrite(t *testing.T) {
 			Ntz:       time.Date(2026, time.May, 24, 11, 22, 33, 0, time.UTC),
 			Tz:        time.Date(2027, time.May, 24, 11, 22, 33, 44, warsawTz),
 			So:        &simpleObject{s: "another string", i: 123},
+			SArr:      []string{"a", "b"},
+			F64Arr:    []float64{1.1, 2.2},
+			SomeMap:   map[string]bool{"a": true, "b": false},
 		}
 		dbt.mustExec("INSERT INTO test_object_binding SELECT (?)", o)
 		rows := dbt.mustQuery("SELECT * FROM test_object_binding WHERE obj = ?", o)
@@ -337,6 +359,9 @@ func TestBindingObjectWithSchemaSimpleWrite(t *testing.T) {
 		assertTrueE(t, res.Tz.Equal(o.Tz))
 		assertTrueE(t, res.Ntz.Equal(o.Ntz))
 		assertDeepEqualE(t, res.So, o.So)
+		assertDeepEqualE(t, res.SArr, o.SArr)
+		assertDeepEqualE(t, res.F64Arr, o.F64Arr)
+		assertDeepEqualE(t, res.SomeMap, o.SomeMap)
 	})
 }
 
@@ -345,7 +370,7 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) {
 	assertNilF(t, err)
 	runDBTest(t, func(dbt *DBTest) {
 		dbt.forceJSON()
-		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER)))")
+		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))")
 		defer func() {
 			dbt.mustExec("DROP TABLE IF EXISTS test_object_binding")
 		}()
@@ -354,20 +379,23 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) {
 		dbt.mustExec("ALTER SESSION SET TIMESTAMP_OUTPUT_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF9 TZHTZM'")
 		t.Run("not null", func(t *testing.T) {
 			o := &objectWithAllTypesNullableSimpleScan{
-				S:    sql.NullString{String: "some string", Valid: true},
-				B:    sql.NullByte{Byte: 1, Valid: true},
-				I16:  sql.NullInt16{Int16: 2, Valid: true},
-				I32:  sql.NullInt32{Int32: 3, Valid: true},
-				I64:  sql.NullInt64{Int64: 4, Valid: true},
-				F64:  sql.NullFloat64{Float64: 2.2, Valid: true},
-				Bo:   sql.NullBool{Bool: true, Valid: true},
-				Bi:   []byte{'a', 'b', 'c'},
-				Date: sql.NullTime{Time: time.Date(2024, time.May, 24, 0, 0, 0, 0, time.UTC), Valid: true},
-				Time: sql.NullTime{Time: time.Date(1, 1, 1, 11, 22, 33, 0, time.UTC), Valid: true},
-				Ltz:  sql.NullTime{Time: time.Date(2025, time.May, 24, 11, 22, 33, 44, warsawTz), Valid: true},
-				Ntz:  sql.NullTime{Time: time.Date(2026, time.May, 24, 11, 22, 33, 0, time.UTC), Valid: true},
-				Tz:   sql.NullTime{Time: time.Date(2027, time.May, 24, 11, 22, 33, 44, warsawTz), Valid: true},
-				So:   &simpleObject{s: "another string", i: 123},
+				S:       sql.NullString{String: "some string", Valid: true},
+				B:       sql.NullByte{Byte: 1, Valid: true},
+				I16:     sql.NullInt16{Int16: 2, Valid: true},
+				I32:     sql.NullInt32{Int32: 3, Valid: true},
+				I64:     sql.NullInt64{Int64: 4, Valid: true},
+				F64:     sql.NullFloat64{Float64: 2.2, Valid: true},
+				Bo:      sql.NullBool{Bool: true, Valid: true},
+				Bi:      []byte{'a', 'b', 'c'},
+				Date:    sql.NullTime{Time: time.Date(2024, time.May, 24, 0, 0, 0, 0, time.UTC), Valid: true},
+				Time:    sql.NullTime{Time: time.Date(1, 1, 1, 11, 22, 33, 0, time.UTC), Valid: true},
+				Ltz:     sql.NullTime{Time: time.Date(2025, time.May, 24, 11, 22, 33, 44, warsawTz), Valid: true},
+				Ntz:     sql.NullTime{Time: time.Date(2026, time.May, 24, 11, 22, 33, 0, time.UTC), Valid: true},
+				Tz:      sql.NullTime{Time: time.Date(2027, time.May, 24, 11, 22, 33, 44, warsawTz), Valid: true},
+				So:      &simpleObject{s: "another string", i: 123},
+				SArr:    []string{"a", "b"},
+				F64Arr:  []float64{1.1, 2.2},
+				SomeMap: map[string]bool{"a": true, "b": false},
 			}
 			dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o)
 			rows := dbt.mustQuery("SELECT * FROM test_object_binding WHERE obj = ?", o)
@@ -393,23 +421,29 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) {
 			assertTrueE(t, res.Tz.Time.Equal(o.Tz.Time))
 			assertTrueE(t, res.Ntz.Time.Equal(o.Ntz.Time))
 			assertDeepEqualE(t, res.So, o.So)
+			assertDeepEqualE(t, res.SArr, o.SArr)
+			assertDeepEqualE(t, res.F64Arr, o.F64Arr)
+			assertDeepEqualE(t, res.SomeMap, o.SomeMap)
 		})
 		t.Run("null", func(t *testing.T) {
 			o := &objectWithAllTypesNullableSimpleScan{
-				S:    sql.NullString{},
-				B:    sql.NullByte{},
-				I16:  sql.NullInt16{},
-				I32:  sql.NullInt32{},
-				I64:  sql.NullInt64{},
-				F64:  sql.NullFloat64{},
-				Bo:   sql.NullBool{},
-				Bi:   nil,
-				Date: sql.NullTime{},
-				Time: sql.NullTime{},
-				Ltz:  sql.NullTime{},
-				Ntz:  sql.NullTime{},
-				Tz:   sql.NullTime{},
-				So:   nil,
+				S:       sql.NullString{},
+				B:       sql.NullByte{},
+				I16:     sql.NullInt16{},
+				I32:     sql.NullInt32{},
+				I64:     sql.NullInt64{},
+				F64:     sql.NullFloat64{},
+				Bo:      sql.NullBool{},
+				Bi:      nil,
+				Date:    sql.NullTime{},
+				Time:    sql.NullTime{},
+				Ltz:     sql.NullTime{},
+				Ntz:     sql.NullTime{},
+				Tz:      sql.NullTime{},
+				So:      nil,
+				SArr:    nil,
+				F64Arr:  nil,
+				SomeMap: nil,
 			}
 			dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o)
 			rows := dbt.mustQuery("SELECT * FROM test_object_binding WHERE obj = ?", o)
@@ -435,6 +469,9 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) {
 			assertTrueE(t, res.Tz.Time.Equal(o.Tz.Time))
 			assertTrueE(t, res.Ntz.Time.Equal(o.Ntz.Time))
 			assertDeepEqualE(t, res.So, o.So)
+			assertDeepEqualE(t, res.SArr, o.SArr)
+			assertDeepEqualE(t, res.F64Arr, o.F64Arr)
+			assertDeepEqualE(t, res.SomeMap, o.SomeMap)
 		})
 	})
 }
@@ -461,7 +498,7 @@ func (o *objectWithAllTypesWrapper) Write(sowc StructuredObjectWriterContext) er
 func TestBindingObjectWithAllTypesNullable(t *testing.T) {
 	runDBTest(t, func(dbt *DBTest) {
 		dbt.forceJSON()
-		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (o OBJECT(o OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER))))")
+		dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (o OBJECT(o OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))))")
 		defer func() {
 			dbt.mustExec("DROP TABLE IF EXISTS test_object_binding")
 		}()
@@ -660,9 +697,9 @@ func TestBindingEmptyArrayOfObjects(t *testing.T) {
 		}()
 
 		arr := []*simpleObject{}
-		dbt.mustExec("INSERT INTO test_array_binding SELECT (?)", DataTypeEmptyArray, reflect.TypeOf(simpleObject{}))
+		dbt.mustExec("INSERT INTO test_array_binding SELECT (?)", arr)
 
-		rows := dbt.mustQuery("SELECT * FROM test_array_binding WHERE arr = ?", DataTypeEmptyArray, reflect.TypeOf(simpleObject{}))
+		rows := dbt.mustQuery("SELECT * FROM test_array_binding WHERE arr = ?", arr)
 		defer rows.Close()
 
 		assertTrueF(t, rows.Next())
@@ -694,3 +731,547 @@ func TestBindingNilArrayOfObjects(t *testing.T) {
 		assertDeepEqualE(t, res, arr)
 	})
 }
+
+func TestBindingNilArrayOfInts(t *testing.T) {
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.enableStructuredTypesBinding()
+		dbt.mustExec("CREATE OR REPLACE TABLE test_array_binding (arr ARRAY(INTEGER))")
+		defer func() {
+			dbt.mustExec("DROP TABLE IF EXISTS test_array_binding")
+		}()
+
+		var arr *[]int64
+		dbt.mustExec("INSERT INTO test_array_binding SELECT (?)", DataTypeNilArray, reflect.TypeOf(1))
+
+		rows := dbt.mustQuery("SELECT * FROM test_array_binding")
+		defer rows.Close()
+
+		assertTrueF(t, rows.Next())
+		var res *[]int64
+		err := rows.Scan(&res)
+		assertNilF(t, err)
+		assertDeepEqualE(t, res, arr)
+	})
+}
+
+func TestBindingMap(t *testing.T) {
+	warsawTz, err := time.LoadLocation("Europe/Warsaw")
+	assertNilF(t, err)
+	testcases := []struct {
+		tableDefinition string
+		values          []any
+		expected        any
+		isTimeOnly      bool
+	}{
+		{
+			tableDefinition: "VARCHAR, VARCHAR",
+			values: []any{map[string]string{
+				"a": "b",
+				"c": "d",
+			}},
+			expected: map[string]string{
+				"a": "b",
+				"c": "d",
+			},
+		},
+		{
+			tableDefinition: "INTEGER, VARCHAR",
+			values: []any{map[int64]string{
+				1: "b",
+				2: "d",
+			}},
+			expected: map[int64]string{
+				1: "b",
+				2: "d",
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, BOOLEAN",
+			values: []any{map[string]bool{
+				"a": true,
+				"c": false,
+			}},
+			expected: map[string]bool{
+				"a": true,
+				"c": false,
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, INTEGER",
+			values: []any{map[string]int64{
+				"a": 1,
+				"b": 2,
+			}},
+			expected: map[string]int64{
+				"a": 1,
+				"b": 2,
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, DOUBLE",
+			values: []any{map[string]float64{
+				"a": 1.1,
+				"b": 2.2,
+			}},
+			expected: map[string]float64{
+				"a": 1.1,
+				"b": 2.2,
+			},
+		},
+		{
+			tableDefinition: "INTEGER, BINARY",
+			values: []any{DataTypeBinary, map[int64][]byte{
+				1: {'a', 'b'},
+				2: {'c', 'd'},
+			}},
+			expected: map[int64][]byte{
+				1: {'a', 'b'},
+				2: {'c', 'd'},
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, BINARY",
+			values: []any{DataTypeBinary, map[string][]byte{
+				"a": {'a', 'b'},
+				"b": {'c', 'd'},
+			}},
+			expected: map[string][]byte{
+				"a": {'a', 'b'},
+				"b": {'c', 'd'},
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, DATE",
+			values: []any{DataTypeDate, map[string]time.Time{
+				"a": time.Date(2024, time.June, 25, 0, 0, 0, 0, time.UTC),
+				"b": time.Date(2024, time.June, 26, 0, 0, 0, 0, time.UTC),
+			}},
+			expected: map[string]time.Time{
+				"a": time.Date(2024, time.June, 25, 0, 0, 0, 0, time.UTC),
+				"b": time.Date(2024, time.June, 26, 0, 0, 0, 0, time.UTC),
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, TIME",
+			values: []any{DataTypeTime, map[string]time.Time{
+				"a": time.Date(1, time.January, 1, 11, 22, 33, 0, time.UTC),
+				"b": time.Date(2, time.January, 1, 22, 11, 44, 0, time.UTC),
+			}},
+			expected: map[string]time.Time{
+				"a": time.Date(1, time.January, 1, 11, 22, 33, 0, time.UTC),
+				"b": time.Date(2, time.January, 1, 22, 11, 44, 0, time.UTC),
+			},
+			isTimeOnly: true,
+		},
+		{
+			tableDefinition: "VARCHAR, TIMESTAMPNTZ",
+			values: []any{DataTypeTimestampNtz, map[string]time.Time{
+				"a": time.Date(2024, time.June, 25, 11, 22, 33, 0, time.UTC),
+				"b": time.Date(2024, time.June, 26, 11, 22, 33, 0, time.UTC),
+			}},
+			expected: map[string]time.Time{
+				"a": time.Date(2024, time.June, 25, 11, 22, 33, 0, time.UTC),
+				"b": time.Date(2024, time.June, 26, 11, 22, 33, 0, time.UTC),
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, TIMESTAMPTZ",
+			values: []any{DataTypeTimestampTz, map[string]time.Time{
+				"a": time.Date(2024, time.June, 25, 11, 22, 33, 0, warsawTz),
+				"b": time.Date(2024, time.June, 26, 11, 22, 33, 0, warsawTz),
+			}},
+			expected: map[string]time.Time{
+				"a": time.Date(2024, time.June, 25, 11, 22, 33, 0, warsawTz),
+				"b": time.Date(2024, time.June, 26, 11, 22, 33, 0, warsawTz),
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, TIMESTAMPLTZ",
+			values: []any{DataTypeTimestampLtz, map[string]time.Time{
+				"a": time.Date(2024, time.June, 25, 11, 22, 33, 0, warsawTz),
+				"b": time.Date(2024, time.June, 26, 11, 22, 33, 0, warsawTz),
+			}},
+			expected: map[string]time.Time{
+				"a": time.Date(2024, time.June, 25, 11, 22, 33, 0, warsawTz),
+				"b": time.Date(2024, time.June, 26, 11, 22, 33, 0, warsawTz),
+			},
+		},
+	}
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.mustExecT(t, "ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'")
+		dbt.enableStructuredTypesBinding()
+		for _, tc := range testcases {
+			t.Run(tc.tableDefinition, func(t *testing.T) {
+				dbt.mustExecT(t, fmt.Sprintf("CREATE OR REPLACE TABLE test_map_binding (m MAP(%v))", tc.tableDefinition))
+				defer func() {
+					dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+				}()
+
+				dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT (?)", tc.values...)
+
+				rows := dbt.mustQueryT(t, "SELECT * FROM test_map_binding WHERE m = ?", tc.values...)
+				defer rows.Close()
+
+				assertTrueE(t, rows.Next())
+				var res any
+				err := rows.Scan(&res)
+				assertNilF(t, err)
+				if m, ok := tc.expected.(map[string]time.Time); ok {
+					resTimes := res.(map[string]time.Time)
+					for k, v := range m {
+						if tc.isTimeOnly {
+							assertEqualE(t, resTimes[k].Hour(), v.Hour())
+							assertEqualE(t, resTimes[k].Minute(), v.Minute())
+							assertEqualE(t, resTimes[k].Second(), v.Second())
+						} else {
+							assertTrueE(t, resTimes[k].Equal(v))
+						}
+					}
+				} else {
+					assertDeepEqualE(t, res, tc.expected)
+				}
+			})
+		}
+	})
+}
+
+func TestBindingMapOfStructs(t *testing.T) {
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.enableStructuredTypesBinding()
+		dbt.mustExec("CREATE OR REPLACE TABLE test_map_binding (m MAP(VARCHAR, OBJECT(s VARCHAR, i INTEGER)))")
+		defer func() {
+			dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+		}()
+		m := map[string]*simpleObject{
+			"a": {"abc", 1},
+			"b": nil,
+			"c": {"def", 2},
+		}
+
+		dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT ?", m)
+		rows := dbt.mustQueryT(t, "SELECT * FROM test_map_binding WHERE m = ?", m)
+		defer rows.Close()
+
+		rows.Next()
+		var res map[string]*simpleObject
+		err := rows.Scan(ScanMapOfScanners(&res))
+		assertNilF(t, err)
+		assertDeepEqualE(t, res, m)
+	})
+}
+
+func TestBindingMapOfWithAllValuesNil(t *testing.T) {
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.enableStructuredTypesBinding()
+		dbt.mustExec("CREATE OR REPLACE TABLE test_map_binding (m MAP(VARCHAR, OBJECT(s VARCHAR, i INTEGER)))")
+		defer func() {
+			dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+		}()
+		m := map[string]*simpleObject{
+			"a": nil,
+		}
+
+		dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT ?", m)
+		rows := dbt.mustQueryT(t, "SELECT * FROM test_map_binding WHERE m = ?", m)
+		defer rows.Close()
+
+		rows.Next()
+		var res map[string]*simpleObject
+		err := rows.Scan(ScanMapOfScanners(&res))
+		assertNilF(t, err)
+		assertDeepEqualE(t, res, m)
+	})
+}
+
+func TestBindingEmptyMapOfStructs(t *testing.T) {
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.enableStructuredTypesBinding()
+		dbt.mustExec("CREATE OR REPLACE TABLE test_map_binding (m MAP(VARCHAR, OBJECT(s VARCHAR, i INTEGER)))")
+		defer func() {
+			dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+		}()
+
+		m := map[string]*simpleObject{}
+		dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT ?", m)
+		rows := dbt.mustQueryT(t, "SELECT * FROM test_map_binding WHERE m = ?", m)
+		defer rows.Close()
+
+		assertTrueF(t, rows.Next())
+		var res map[string]*simpleObject
+		err := rows.Scan(ScanMapOfScanners(&res))
+		assertNilF(t, err)
+		assertDeepEqualE(t, res, m)
+	})
+}
+
+func TestBindingEmptyMapOfInts(t *testing.T) {
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.enableStructuredTypesBinding()
+		dbt.mustExec("CREATE OR REPLACE TABLE test_map_binding (m MAP(VARCHAR, INTEGER))")
+		defer func() {
+			dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+		}()
+
+		m := map[string]int64{}
+		dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT ?", m)
+		rows := dbt.mustQueryT(t, "SELECT * FROM test_map_binding WHERE m = ?", m)
+		defer rows.Close()
+
+		assertTrueF(t, rows.Next())
+		var res map[string]int64
+		err := rows.Scan(&res)
+		assertNilF(t, err)
+		assertDeepEqualE(t, res, m)
+	})
+}
+
+func TestBindingNilMapOfStructs(t *testing.T) {
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.enableStructuredTypesBinding()
+		dbt.mustExec("CREATE OR REPLACE TABLE test_map_binding (m MAP(VARCHAR, OBJECT(s VARCHAR, i INTEGER)))")
+		defer func() {
+			dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+		}()
+
+		var m map[string]*simpleObject
+		dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT ?", DataTypeNilMap, NilMapTypes{Key: reflect.TypeOf(""), Value: reflect.TypeOf(&simpleObject{})})
+		rows := dbt.mustQueryT(t, "SELECT * FROM test_map_binding", DataTypeNilMap, NilMapTypes{Key: reflect.TypeOf(""), Value: reflect.TypeOf(&simpleObject{})})
+		defer rows.Close()
+
+		assertTrueF(t, rows.Next())
+		var res map[string]*simpleObject
+		err := rows.Scan(ScanMapOfScanners(&res))
+		assertNilF(t, err)
+		assertDeepEqualE(t, res, m)
+	})
+}
+
+func TestBindingNilMapOfInts(t *testing.T) {
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.enableStructuredTypesBinding()
+		dbt.mustExec("CREATE OR REPLACE TABLE test_map_binding (m MAP(VARCHAR, INTEGER))")
+		defer func() {
+			dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+		}()
+
+		var m *map[string]int64
+		dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT ?", DataTypeNilMap, NilMapTypes{Key: reflect.TypeOf(""), Value: reflect.TypeOf(1)})
+		rows := dbt.mustQueryT(t, "SELECT * FROM test_map_binding", DataTypeNilMap, NilMapTypes{Key: reflect.TypeOf(""), Value: reflect.TypeOf(1)})
+		defer rows.Close()
+
+		assertTrueF(t, rows.Next())
+		var res *map[string]int64
+		err := rows.Scan(&res)
+		assertNilF(t, err)
+		assertDeepEqualE(t, res, m)
+	})
+}
+
+func TestBindingMapOfArrays(t *testing.T) {
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.enableStructuredTypesBinding()
+		dbt.mustExec("CREATE OR REPLACE TABLE test_map_binding (m MAP(VARCHAR, ARRAY(INTEGER)))")
+		defer func() {
+			dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+		}()
+
+		m := map[string][]int64{
+			"a": {1, 2},
+			"b": nil,
+		}
+		dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT ?", m)
+		rows := dbt.mustQueryT(t, "SELECT * FROM test_map_binding", m)
+		defer rows.Close()
+
+		assertTrueF(t, rows.Next())
+		var res map[string][]int64
+		err := rows.Scan(&res)
+		assertNilF(t, err)
+		assertDeepEqualE(t, res, m)
+	})
+}
+
+func TestBindingMapWithNillableValues(t *testing.T) {
+	warsawTz, err := time.LoadLocation("Europe/Warsaw")
+	assertNilF(t, err)
+	var testcases = []struct {
+		tableDefinition string
+		values          []any
+		expected        any
+		isTimeOnly      bool
+	}{
+		{
+			tableDefinition: "VARCHAR, VARCHAR",
+			values: []any{map[string]sql.NullString{
+				"a": {String: "b", Valid: true},
+				"c": {},
+			}},
+			expected: map[string]sql.NullString{
+				"a": {String: "b", Valid: true},
+				"c": {},
+			},
+		},
+		{
+			tableDefinition: "INTEGER, VARCHAR",
+			values: []any{map[int64]sql.NullString{
+				1: {String: "b", Valid: true},
+				2: {},
+			}},
+			expected: map[int64]sql.NullString{
+				1: {String: "b", Valid: true},
+				2: {},
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, BOOLEAN",
+			values: []any{map[string]sql.NullBool{
+				"a": {Bool: true, Valid: true},
+				"c": {},
+			}},
+			expected: map[string]sql.NullBool{
+				"a": {Bool: true, Valid: true},
+				"c": {},
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, INTEGER",
+			values: []any{map[string]sql.NullInt64{
+				"a": {Int64: 1, Valid: true},
+				"b": {},
+			}},
+			expected: map[string]sql.NullInt64{
+				"a": {Int64: 1, Valid: true},
+				"b": {},
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, DOUBLE",
+			values: []any{map[string]sql.NullFloat64{
+				"a": {Float64: 1.1, Valid: true},
+				"b": {},
+			}},
+			expected: map[string]sql.NullFloat64{
+				"a": {Float64: 1.1, Valid: true},
+				"b": {},
+			},
+		},
+		{
+			tableDefinition: "INTEGER, BINARY",
+			values: []any{DataTypeBinary, map[int64][]byte{
+				1: {'a', 'b'},
+				2: nil,
+			}},
+			expected: map[int64][]byte{
+				1: {'a', 'b'},
+				2: nil,
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, BINARY",
+			values: []any{DataTypeBinary, map[string][]byte{
+				"a": {'a', 'b'},
+				"b": nil,
+			}},
+			expected: map[string][]byte{
+				"a": {'a', 'b'},
+				"b": nil,
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, DATE",
+			values: []any{DataTypeDate, map[string]sql.NullTime{
+				"a": {Time: time.Date(2024, time.June, 25, 0, 0, 0, 0, time.UTC), Valid: true},
+				"b": {},
+			}},
+			expected: map[string]sql.NullTime{
+				"a": {Time: time.Date(2024, time.June, 25, 0, 0, 0, 0, time.UTC), Valid: true},
+				"b": {},
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, TIME",
+			values: []any{DataTypeTime, map[string]sql.NullTime{
+				"a": {Time: time.Date(1, time.January, 1, 11, 22, 33, 0, time.UTC), Valid: true},
+				"b": {},
+			}},
+			expected: map[string]sql.NullTime{
+				"a": {Time: time.Date(1, time.January, 1, 11, 22, 33, 0, time.UTC), Valid: true},
+				"b": {},
+			},
+			isTimeOnly: true,
+		},
+		{
+			tableDefinition: "VARCHAR, TIMESTAMPNTZ",
+			values: []any{DataTypeTimestampNtz, map[string]sql.NullTime{
+				"a": {Time: time.Date(2024, time.June, 25, 11, 22, 33, 0, time.UTC), Valid: true},
+				"b": {},
+			}},
+			expected: map[string]sql.NullTime{
+				"a": {Time: time.Date(2024, time.June, 25, 11, 22, 33, 0, time.UTC), Valid: true},
+				"b": {},
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, TIMESTAMPTZ",
+			values: []any{DataTypeTimestampTz, map[string]sql.NullTime{
+				"a": {Time: time.Date(2024, time.June, 25, 11, 22, 33, 0, warsawTz), Valid: true},
+				"b": {},
+			}},
+			expected: map[string]sql.NullTime{
+				"a": {Time: time.Date(2024, time.June, 25, 11, 22, 33, 0, warsawTz), Valid: true},
+				"b": {},
+			},
+		},
+		{
+			tableDefinition: "VARCHAR, TIMESTAMPLTZ",
+			values: []any{DataTypeTimestampLtz, map[string]sql.NullTime{
+				"a": {Time: time.Date(2024, time.June, 25, 11, 22, 33, 0, warsawTz), Valid: true},
+				"b": {},
+			}},
+			expected: map[string]sql.NullTime{
+				"a": {Time: time.Date(2024, time.June, 25, 11, 22, 33, 0, warsawTz), Valid: true},
+				"b": {},
+			},
+		},
+	}
+	runDBTest(t, func(dbt *DBTest) {
+		dbt.mustExecT(t, "ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'")
+		dbt.enableStructuredTypesBinding()
+		for _, tc := range testcases {
+			t.Run(tc.tableDefinition, func(t *testing.T) {
+				dbt.mustExecT(t, fmt.Sprintf("CREATE OR REPLACE TABLE test_map_binding (m MAP(%v))", tc.tableDefinition))
+				defer func() {
+					dbt.mustExecT(t, "DROP TABLE IF EXISTS test_map_binding")
+				}()
+
+				dbt.mustExecT(t, "INSERT INTO test_map_binding SELECT (?)", tc.values...)
+
+				rows := dbt.mustQueryContextT(WithMapValuesNullable(context.Background()), t, "SELECT * FROM test_map_binding WHERE m = ?", tc.values...)
+				defer rows.Close()
+
+				assertTrueE(t, rows.Next())
+				var res any
+				err := rows.Scan(&res)
+				assertNilF(t, err)
+				if m, ok := tc.expected.(map[string]sql.NullTime); ok {
+					resTimes := res.(map[string]sql.NullTime)
+					for k, v := range m {
+						if tc.isTimeOnly {
+							assertEqualE(t, resTimes[k].Valid, v.Valid)
+							assertEqualE(t, resTimes[k].Time.Hour(), v.Time.Hour())
+							assertEqualE(t, resTimes[k].Time.Minute(), v.Time.Minute())
+							assertEqualE(t, resTimes[k].Time.Second(), v.Time.Second())
+						} else {
+							assertEqualE(t, resTimes[k].Valid, v.Valid)
+							if v.Valid {
+								assertTrueE(t, resTimes[k].Time.Equal(v.Time))
+							}
+						}
+					}
+				} else {
+					assertDeepEqualE(t, res, tc.expected)
+				}
+			})
+		}
+	})
+}