Repository: snowflakedb/gosnowflake
Pull request #384 (2021-02-25 19:58:59)

SNOW-158369 Refactor Type Converter
### Description
Refactor type converters to allow for array binding.

### Checklist
- [x] Code compiles correctly
- [x] Run ``make fmt`` to fix inconsistent formats
- [x] Run ``make lint`` to get lint errors and fix all of them
- [x] Created tests which fail without the change (if possible)
- [x] All tests passing
- [ ] Extended the README / documentation, if necessary



Patch:
diff --git a/bind_uploader.go b/bind_uploader.go
new file mode 100644
index 0000000..0a47324
--- /dev/null
+++ b/bind_uploader.go
@@ -0,0 +1,22 @@
+// Copyright (c) 2021 Snowflake Computing Inc. All right reserved.
+
+package gosnowflake
+
+import (
+	"database/sql/driver"
+	"reflect"
+)
+
+func supportedArrayBind(nv *driver.NamedValue) bool {
+	switch reflect.TypeOf(nv.Value) {
+	case reflect.TypeOf(&intArray{}), reflect.TypeOf(&int32Array{}),
+		reflect.TypeOf(&int64Array{}), reflect.TypeOf(&float64Array{}),
+		reflect.TypeOf(&float32Array{}), reflect.TypeOf(&boolArray{}),
+		reflect.TypeOf(&stringArray{}), reflect.TypeOf(&byteArray{}):
+		return true
+	default:
+		// TODO SNOW-292862 date, timestamp, time
+		// TODO SNOW-176486 variant, object, array
+		return false
+	}
+}
diff --git a/connection.go b/connection.go
index 51923f7..48a056e 100644
--- a/connection.go
+++ b/connection.go
@@ -9,7 +9,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/url"
-	"reflect"
 	"strconv"
 	"strings"
 	"sync/atomic"
@@ -87,31 +86,35 @@ func (sc *snowflakeConn) exec(
 	}
 	logger.WithContext(ctx).Infof("parameters: %v", req.Parameters)
 
-	tsmode := "TIMESTAMP_NTZ"
+	tsmode := timestampNtzType
 	idx := 1
 	if len(bindings) > 0 {
 		req.Bindings = make(map[string]execBindParameter, len(bindings))
-		for i, n := 0, len(bindings); i < n; i++ {
-			t := goTypeToSnowflake(bindings[i].Value, tsmode)
+		for _, binding := range bindings {
+			t := goTypeToSnowflake(binding.Value, tsmode)
 			logger.WithContext(ctx).Debugf("tmode: %v\n", t)
-			if t == "CHANGE_TYPE" {
-				tsmode, err = dataTypeMode(bindings[i].Value)
+			if t == changeType {
+				tsmode, err = dataTypeMode(binding.Value)
 				if err != nil {
 					return nil, err
 				}
 			} else {
-				var v1 interface{}
-				if t == "ARRAY" {
-					t, v1 = arrayToString(bindings[i].Value)
+				var val interface{}
+				if t == sliceType {
+					// retrieve array binding data
+					t, val = snowflakeArrayToString(&binding)
 				} else {
-					v1, err = valueToString(bindings[i].Value, tsmode)
+					val, err = valueToString(binding.Value, tsmode)
+					if err != nil {
+						return nil, err
+					}
 				}
-				if err != nil {
-					return nil, err
+				if t == nullType || t == unSupportedType {
+					t = textType // if null or not supported, pass to GS as text
 				}
 				req.Bindings[strconv.Itoa(idx)] = execBindParameter{
-					Type:  t,
-					Value: v1,
+					Type:  t.String(),
+					Value: val,
 				}
 				idx++
 			}
@@ -383,14 +386,13 @@ func (sc *snowflakeConn) Ping(ctx context.Context) error {
 	return err
 }
 
+// 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 {
-	switch reflect.TypeOf(nv.Value) {
-	case reflect.TypeOf([]int{0}), reflect.TypeOf([]int64{0}), reflect.TypeOf([]float64{0}),
-		reflect.TypeOf([]bool{false}), reflect.TypeOf([]string{""}):
-		return nil
-	default:
+	if supported := supportedArrayBind(nv); !supported {
 		return driver.ErrSkip
 	}
+	return nil
 }
 
 func (sc *snowflakeConn) populateSessionParameters(parameters []nameValueParameter) {
diff --git a/converter.go b/converter.go
index 6a2ac95..468d49f 100644
--- a/converter.go
+++ b/converter.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2020 Snowflake Computing Inc. All right reserved.
+// Copyright (c) 2017-2021 Snowflake Computing Inc. All right reserved.
 
 package gosnowflake
 
@@ -6,65 +6,69 @@ import (
 	"database/sql/driver"
 	"encoding/hex"
 	"fmt"
-	"github.com/apache/arrow/go/arrow"
-	"github.com/apache/arrow/go/arrow/array"
-	"github.com/apache/arrow/go/arrow/decimal128"
 	"math"
 	"math/big"
 	"reflect"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/apache/arrow/go/arrow"
+	"github.com/apache/arrow/go/arrow/array"
+	"github.com/apache/arrow/go/arrow/decimal128"
 )
 
 // goTypeToSnowflake translates Go data type to Snowflake data type.
-func goTypeToSnowflake(v driver.Value, tsmode string) string {
-	switch v := v.(type) {
+func goTypeToSnowflake(v driver.Value, tsmode snowflakeType) snowflakeType {
+	switch t := v.(type) {
 	case int64:
-		return "FIXED"
+		return fixedType
 	case float64:
-		return "REAL"
+		return realType
 	case bool:
-		return "BOOLEAN"
+		return booleanType
 	case string:
-		return "TEXT"
+		return textType
 	case []byte:
-		if tsmode == "BINARY" {
-			return "BINARY" // may be redundant but ensures BINARY type
+		if tsmode == binaryType {
+			return binaryType // may be redundant but ensures BINARY type
 		}
-		if v == nil || len(v) != 1 {
-			return "TEXT" // invalid byte array. won't take as BINARY
+		if t == nil {
+			return nullType // invalid byte array. won't take as BINARY
 		}
-		_, err := dataTypeMode(v)
-		if err != nil {
-			return "TEXT" // not supported dataType
+		if len(t) != 1 {
+			return unSupportedType
+		}
+		if _, err := dataTypeMode(t); err != nil {
+			return unSupportedType
 		}
-		return "CHANGE_TYPE"
-	case []int, []int64, []float64, []bool, []string:
-		return "ARRAY"
+		return changeType
 	case time.Time:
 		return tsmode
 	}
-	return "TEXT"
+	if supportedArrayBind(&driver.NamedValue{Value: v}) {
+		return sliceType
+	}
+	return unSupportedType
 }
 
 // snowflakeTypeToGo translates Snowflake data type to Go data type.
-func snowflakeTypeToGo(dbtype string, scale int64) reflect.Type {
+func snowflakeTypeToGo(dbtype snowflakeType, scale int64) reflect.Type {
 	switch dbtype {
-	case "fixed":
+	case fixedType:
 		if scale == 0 {
 			return reflect.TypeOf(int64(0))
 		}
 		return reflect.TypeOf(float64(0))
-	case "real":
+	case realType:
 		return reflect.TypeOf(float64(0))
-	case "text", "variant", "object", "array":
+	case textType, variantType, objectType, arrayType:
 		return reflect.TypeOf("")
-	case "date", "time", "timestamp_ltz", "timestamp_ntz", "timestamp_tz":
+	case dateType, timeType, timestampLtzType, timestampNtzType, timestampTzType:
 		return reflect.TypeOf(time.Now())
-	case "binary":
+	case binaryType:
 		return reflect.TypeOf([]byte{})
-	case "boolean":
+	case booleanType:
 		return reflect.TypeOf(true)
 	}
 	logger.Errorf("unsupported dbtype is specified. %v", dbtype)
@@ -73,7 +77,7 @@ func snowflakeTypeToGo(dbtype string, scale int64) reflect.Type {
 
 // valueToString converts arbitrary golang type to a string. This is mainly used in binding data with placeholders
 // in queries.
-func valueToString(v driver.Value, tsmode string) (*string, error) {
+func valueToString(v driver.Value, tsmode snowflakeType) (*string, error) {
 	logger.Debugf("TYPE: %v, %v", reflect.TypeOf(v), reflect.ValueOf(v))
 	if v == nil {
 		return nil, nil
@@ -97,7 +101,7 @@ func valueToString(v driver.Value, tsmode string) (*string, error) {
 			return nil, nil
 		}
 		if bd, ok := v.([]byte); ok {
-			if tsmode == "BINARY" {
+			if tsmode == binaryType {
 				s := hex.EncodeToString(bd)
 				return &s, nil
 			}
@@ -108,19 +112,19 @@ func valueToString(v driver.Value, tsmode string) (*string, error) {
 	case reflect.Struct:
 		if tm, ok := v.(time.Time); ok {
 			switch tsmode {
-			case "DATE":
+			case dateType:
 				_, offset := tm.Zone()
 				tm = tm.Add(time.Second * time.Duration(offset))
 				s := fmt.Sprintf("%d", tm.Unix()*1000)
 				return &s, nil
-			case "TIME":
+			case timeType:
 				s := fmt.Sprintf("%d",
 					(tm.Hour()*3600+tm.Minute()*60+tm.Second())*1e9+tm.Nanosecond())
 				return &s, nil
-			case "TIMESTAMP_NTZ", "TIMESTAMP_LTZ":
+			case timestampNtzType, timestampLtzType:
 				s := fmt.Sprintf("%d", tm.UnixNano())
 				return &s, nil
-			case "TIMESTAMP_TZ":
+			case timestampTzType:
 				_, offset := tm.Zone()
 				s := fmt.Sprintf("%v %v", tm.UnixNano(), offset/60+1440)
 				return &s, nil
@@ -246,37 +250,6 @@ func stringToValue(dest *driver.Value, srcColumnMeta execResponseRowType, srcVal
 	return nil
 }
 
-func arrayToString(v driver.Value) (string, []string) {
-	var t string
-	var arr []string
-	switch a := v.(type) {
-	case []int:
-		t = "FIXED"
-		for _, x := range a {
-			arr = append(arr, strconv.Itoa(x))
-		}
-	case []int64:
-		t = "FIXED"
-		for _, x := range a {
-			arr = append(arr, strconv.Itoa(int(x)))
-		}
-	case []float64:
-		t = "REAL"
-		for _, x := range a {
-			arr = append(arr, fmt.Sprintf("%g", x))
-		}
-	case []bool:
-		t = "BOOLEAN"
-		for _, x := range a {
-			arr = append(arr, strconv.FormatBool(x))
-		}
-	case []string:
-		t = "TEXT"
-		arr = a
-	}
-	return t, arr
-}
-
 var decimalShift = new(big.Int).Exp(big.NewInt(2), big.NewInt(64), nil)
 
 func intToBigFloat(val int64, scale int64) *big.Float {
@@ -333,8 +306,8 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 	}
 	logger.Debugf("snowflake data type: %v, arrow data type: %v", srcColumnMeta.Type, srcValue.DataType())
 
-	switch strings.ToUpper(srcColumnMeta.Type) {
-	case "FIXED":
+	switch getSnowflakeType(strings.ToUpper(srcColumnMeta.Type)) {
+	case fixedType:
 		switch srcValue.DataType().ID() {
 		case arrow.DECIMAL:
 			for i, num := range array.NewDecimal128Data(data).Values() {
@@ -392,7 +365,7 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			}
 		}
 		return err
-	case "BOOLEAN":
+	case booleanType:
 		boolData := array.NewBooleanData(data)
 		for i := range *destcol {
 			if !srcValue.IsNull(i) {
@@ -400,14 +373,14 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			}
 		}
 		return err
-	case "REAL":
+	case realType:
 		for i, float64 := range array.NewFloat64Data(data).Float64Values() {
 			if !srcValue.IsNull(i) {
 				(*destcol)[i] = float64
 			}
 		}
 		return err
-	case "TEXT", "ARRAY", "VARIANT", "OBJECT":
+	case textType, arrayType, variantType, objectType:
 		strings := array.NewStringData(data)
 		for i := range *destcol {
 			if !srcValue.IsNull(i) {
@@ -415,7 +388,7 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			}
 		}
 		return err
-	case "BINARY":
+	case binaryType:
 		binaryData := array.NewBinaryData(data)
 		for i := range *destcol {
 			if !srcValue.IsNull(i) {
@@ -423,7 +396,7 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			}
 		}
 		return err
-	case "DATE":
+	case dateType:
 		for i, date32 := range array.NewDate32Data(data).Date32Values() {
 			if !srcValue.IsNull(i) {
 				t0 := time.Unix(int64(date32)*86400, 0).UTC()
@@ -431,7 +404,7 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			}
 		}
 		return err
-	case "TIME":
+	case timeType:
 		if srcValue.DataType().ID() == arrow.INT64 {
 			for i, int64 := range array.NewInt64Data(data).Int64Values() {
 				if !srcValue.IsNull(i) {
@@ -448,7 +421,7 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			}
 		}
 		return err
-	case "TIMESTAMP_NTZ":
+	case timestampNtzType:
 		if srcValue.DataType().ID() == arrow.STRUCT {
 			structData := array.NewStructData(data)
 			epoch := array.NewInt64Data(structData.Field(0).Data()).Int64Values()
@@ -466,7 +439,7 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			}
 		}
 		return err
-	case "TIMESTAMP_LTZ":
+	case timestampLtzType:
 		if srcValue.DataType().ID() == arrow.STRUCT {
 			structData := array.NewStructData(data)
 			epoch := array.NewInt64Data(structData.Field(0).Data()).Int64Values()
@@ -486,7 +459,7 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			}
 		}
 		return err
-	case "TIMESTAMP_TZ":
+	case timestampTzType:
 		structData := array.NewStructData(data)
 		if structData.NumField() == 2 {
 			epoch := array.NewInt64Data(structData.Field(0).Data()).Int64Values()
@@ -516,3 +489,104 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 	err = fmt.Errorf("unsupported data type")
 	return err
 }
+
+type intArray []int
+type int32Array []int32
+type int64Array []int64
+type float64Array []float64
+type float32Array []float32
+type boolArray []bool
+type stringArray []string
+type byteArray [][]byte
+
+// Array takes in a column of a row to be inserted via array binding, bulk or
+// otherwise, and converts it into a native snowflake type for binding
+func Array(a interface{}) interface{} {
+	switch t := a.(type) {
+	case []int:
+		return (*intArray)(&t)
+	case []int32:
+		return (*int32Array)(&t)
+	case []int64:
+		return (*int64Array)(&t)
+	case []float64:
+		return (*float64Array)(&t)
+	case []float32:
+		return (*float32Array)(&t)
+	case []bool:
+		return (*boolArray)(&t)
+	case []string:
+		return (*stringArray)(&t)
+	case [][]byte:
+		return (*byteArray)(&t)
+
+	case *[]int:
+		return (*intArray)(t)
+	case *[]int32:
+		return (*int32Array)(t)
+	case *[]int64:
+		return (*int64Array)(t)
+	case *[]float64:
+		return (*float64Array)(t)
+	case *[]float32:
+		return (*float32Array)(t)
+	case *[]bool:
+		return (*boolArray)(t)
+	case *[]string:
+		return (*stringArray)(t)
+	case *[][]byte:
+		return (*byteArray)(t)
+	default:
+		return a
+	}
+}
+
+func snowflakeArrayToString(nv *driver.NamedValue) (snowflakeType, []string) {
+	var t snowflakeType
+	var arr []string
+	switch reflect.TypeOf(nv.Value) {
+	case reflect.TypeOf(&intArray{}):
+		t = fixedType
+		a := nv.Value.(*intArray)
+		for _, x := range *a {
+			arr = append(arr, strconv.Itoa(x))
+		}
+	case reflect.TypeOf(&int64Array{}):
+		t = fixedType
+		a := nv.Value.(*int64Array)
+		for _, x := range *a {
+			arr = append(arr, strconv.Itoa(int(x)))
+		}
+	case reflect.TypeOf(&int32Array{}):
+		t = fixedType
+		a := nv.Value.(*int32Array)
+		for _, x := range *a {
+			arr = append(arr, strconv.Itoa(int(x)))
+		}
+	case reflect.TypeOf(&float64Array{}):
+		t = realType
+		a := nv.Value.(*float64Array)
+		for _, x := range *a {
+			arr = append(arr, fmt.Sprintf("%g", x))
+		}
+	case reflect.TypeOf(&float32Array{}):
+		t = realType
+		a := nv.Value.(*float32Array)
+		for _, x := range *a {
+			arr = append(arr, fmt.Sprintf("%g", x))
+		}
+	case reflect.TypeOf(&boolArray{}):
+		t = booleanType
+		a := nv.Value.(*boolArray)
+		for _, x := range *a {
+			arr = append(arr, strconv.FormatBool(x))
+		}
+	case reflect.TypeOf(&stringArray{}):
+		t = textType
+		a := nv.Value.(*stringArray)
+		arr = *a
+	default:
+		return unSupportedType, nil
+	}
+	return t, arr
+}
diff --git a/datatype.go b/datatype.go
index ee2450c..d9f95c5 100644
--- a/datatype.go
+++ b/datatype.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2019 Snowflake Computing Inc. All right reserved.
+// Copyright (c) 2017-2021 Snowflake Computing Inc. All right reserved.
 
 package gosnowflake
 
@@ -9,8 +9,10 @@ import (
 	"fmt"
 )
 
+type snowflakeType int
+
 const (
-	fixedType byte = iota
+	fixedType snowflakeType = iota
 	realType
 	textType
 	dateType
@@ -23,58 +25,86 @@ const (
 	binaryType
 	timeType
 	booleanType
+	// the following are not snowflake types per se but internal types
+	nullType
+	sliceType
+	changeType
+	unSupportedType
 )
 
+var snowflakeTypes = [...]string{"FIXED", "REAL", "TEXT", "DATE", "VARIANT",
+	"TIMESTAMP_LTZ", "TIMESTAMP_NTZ", "TIMESTAMP_TZ", "OBJECT", "ARRAY",
+	"BINARY", "TIME", "BOOLEAN", "NULL", "SLICE", "CHANGE_TYPE", "NOT_SUPPORTED"}
+
+func (st snowflakeType) String() string {
+	return snowflakeTypes[st]
+}
+
+func (st snowflakeType) Byte() byte {
+	return byte(st)
+}
+
+func getSnowflakeType(typ string) snowflakeType {
+	for i, sft := range snowflakeTypes {
+		if sft == typ {
+			return snowflakeType(i)
+		} else if snowflakeType(i) == nullType {
+			break
+		}
+	}
+	return nullType
+}
+
 var (
 	// DataTypeFixed is a FIXED datatype.
-	DataTypeFixed = []byte{fixedType}
+	DataTypeFixed = []byte{fixedType.Byte()}
 	// DataTypeReal is a REAL datatype.
-	DataTypeReal = []byte{realType}
+	DataTypeReal = []byte{realType.Byte()}
 	// DataTypeText is a TEXT datatype.
-	DataTypeText = []byte{textType}
+	DataTypeText = []byte{textType.Byte()}
 	// DataTypeDate is a Date datatype.
-	DataTypeDate = []byte{dateType}
+	DataTypeDate = []byte{dateType.Byte()}
 	// DataTypeVariant is a TEXT datatype.
-	DataTypeVariant = []byte{variantType}
+	DataTypeVariant = []byte{variantType.Byte()}
 	// DataTypeTimestampLtz is a TIMESTAMP_LTZ datatype.
-	DataTypeTimestampLtz = []byte{timestampLtzType}
+	DataTypeTimestampLtz = []byte{timestampLtzType.Byte()}
 	// DataTypeTimestampNtz is a TIMESTAMP_NTZ datatype.
-	DataTypeTimestampNtz = []byte{timestampNtzType}
+	DataTypeTimestampNtz = []byte{timestampNtzType.Byte()}
 	// DataTypeTimestampTz is a TIMESTAMP_TZ datatype.
-	DataTypeTimestampTz = []byte{timestampTzType}
+	DataTypeTimestampTz = []byte{timestampTzType.Byte()}
 	// DataTypeObject is a OBJECT datatype.
-	DataTypeObject = []byte{objectType}
+	DataTypeObject = []byte{objectType.Byte()}
 	// DataTypeArray is a ARRAY datatype.
-	DataTypeArray = []byte{arrayType}
+	DataTypeArray = []byte{arrayType.Byte()}
 	// DataTypeBinary is a BINARY datatype.
-	DataTypeBinary = []byte{binaryType}
+	DataTypeBinary = []byte{binaryType.Byte()}
 	// DataTypeTime is a TIME datatype.
-	DataTypeTime = []byte{timeType}
+	DataTypeTime = []byte{timeType.Byte()}
 	// DataTypeBoolean is a BOOLEAN datatype.
-	DataTypeBoolean = []byte{booleanType}
+	DataTypeBoolean = []byte{booleanType.Byte()}
 )
 
 // dataTypeMode returns the subsequent data type in a string representation.
-func dataTypeMode(v driver.Value) (tsmode string, err error) {
+func dataTypeMode(v driver.Value) (tsmode snowflakeType, err error) {
 	if bd, ok := v.([]byte); ok {
 		switch {
 		case bytes.Equal(bd, DataTypeDate):
-			tsmode = "DATE"
+			tsmode = dateType
 		case bytes.Equal(bd, DataTypeTime):
-			tsmode = "TIME"
+			tsmode = timeType
 		case bytes.Equal(bd, DataTypeTimestampLtz):
-			tsmode = "TIMESTAMP_LTZ"
+			tsmode = timestampLtzType
 		case bytes.Equal(bd, DataTypeTimestampNtz):
-			tsmode = "TIMESTAMP_NTZ"
+			tsmode = timestampNtzType
 		case bytes.Equal(bd, DataTypeTimestampTz):
-			tsmode = "TIMESTAMP_TZ"
+			tsmode = timestampTzType
 		case bytes.Equal(bd, DataTypeBinary):
-			tsmode = "BINARY"
+			tsmode = binaryType
 		default:
-			return "", fmt.Errorf(errMsgInvalidByteArray, v)
+			return nullType, fmt.Errorf(errMsgInvalidByteArray, v)
 		}
 	} else {
-		return "", fmt.Errorf(errMsgInvalidByteArray, v)
+		return nullType, fmt.Errorf(errMsgInvalidByteArray, v)
 	}
 	return tsmode, nil
 }
diff --git a/doc.go b/doc.go
index 8ee46ae..ff569d1 100644
--- a/doc.go
+++ b/doc.go
@@ -255,8 +255,8 @@ binds arrays to the parameters in the INSERT statement.
 	boolArray := []bool{true, false, true}
 	strArray := []string{"test1", "test2", "test3"}
 	...
-	// Insert the data from the arrays into the table.
-	_, err = db.Exec("insert into my_table values (?, ?, ?, ?)", intArray, fltArray, boolArray, strArray)
+	// Insert the data from the arrays and wrap in an Array() function into the table.
+	_, err = db.Exec("insert into my_table values (?, ?, ?, ?)", Array(&intArray), Array(&fltArray), Array(&boolArray), Array(&strArray))
 
 Note: For alternative ways to load data into the Snowflake database (including bulk loading using the COPY command), see
 Loading Data Into Snowflake (https://docs.snowflake.com/en/user-guide-data-load.html).
diff --git a/rows.go b/rows.go
index 3367915..a9778bf 100644
--- a/rows.go
+++ b/rows.go
@@ -130,7 +130,9 @@ func (rows *snowflakeRows) ColumnTypeScanType(index int) reflect.Type {
 	if err := rows.waitForAsyncQueryStatus(); err != nil {
 		return nil
 	}
-	return snowflakeTypeToGo(rows.ChunkDownloader.getRowType()[index].Type, rows.ChunkDownloader.getRowType()[index].Scale)
+	return snowflakeTypeToGo(
+		getSnowflakeType(strings.ToUpper(rows.ChunkDownloader.getRowType()[index].Type)),
+		rows.ChunkDownloader.getRowType()[index].Scale)
 }
 
 func (rows *snowflakeRows) GetQueryID() string {


Test patch:
diff --git a/bindings_test.go b/bindings_test.go
new file mode 100644
index 0000000..6e633e7
--- /dev/null
+++ b/bindings_test.go
@@ -0,0 +1,322 @@
+// Copyright (c) 2021 Snowflake Computing Inc. All right reserved.
+
+package gosnowflake
+
+import (
+	"bytes"
+	"fmt"
+	"math/big"
+	"testing"
+	"time"
+)
+
+const (
+	createTableSQL = "create or replace table test_prep_statement(c1 INTEGER, " +
+		"c2 FLOAT, c3 BOOLEAN, c4 STRING)"
+	deleteTableSQL   = "drop table if exists TEST_PREP_STATEMENT"
+	insertSQL        = "insert into TEST_PREP_STATEMENT values(?, ?, ?, ?)"
+	selectAllSQL     = "select * from TEST_PREP_STATEMENT ORDER BY 1"
+	updateSQL        = "update TEST_PREP_STATEMENT set C4 = 'newString' where C1 = ?"
+	deleteSQL        = "delete from TEST_PREP_STATEMENT where C1 = ?"
+	selectSQL        = "select * from TEST_PREP_STATEMENT where C1 = ?"
+	enableCacheReuse = "alter session set USE_CACHED_RESULT=true"
+	tableFuncSQL     = "select 1 from table(generator(rowCount => ?))"
+)
+
+func TestBindingFloat64(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		types := [2]string{"FLOAT", "DOUBLE"}
+		expected := 42.23
+		var out float64
+		var rows *RowsExtended
+		for _, v := range types {
+			dbt.mustExec(fmt.Sprintf("CREATE TABLE test (id int, value %v)", v))
+			dbt.mustExec("INSERT INTO test VALUES (1, ?)", expected)
+			rows = dbt.mustQuery("SELECT value FROM test WHERE id = ?", 1)
+			defer rows.Close()
+			if rows.Next() {
+				rows.Scan(&out)
+				if expected != out {
+					dbt.Errorf("%s: %g != %g", v, expected, out)
+				}
+			} else {
+				dbt.Errorf("%s: no data", v)
+			}
+			dbt.mustExec("DROP TABLE IF EXISTS test")
+		}
+	})
+}
+
+// TestBindingUint64 tests uint64 binding. Should fail as unit64 is not a
+// supported binding value by Go's sql package.
+func TestBindingUint64(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		types := []string{"INTEGER"}
+		expected := uint64(18446744073709551615)
+		for _, v := range types {
+			dbt.mustExec(fmt.Sprintf("CREATE TABLE test (id int, value %v)", v))
+			_, err := dbt.db.Exec("INSERT INTO test VALUES (1, ?)", expected)
+			if err == nil {
+				dbt.Fatal("should fail as uint64 values with high bit set are not supported.")
+			} else {
+				logger.Infof("expected err: %v", err)
+			}
+			dbt.mustExec("DROP TABLE IF EXISTS test")
+		}
+	})
+}
+
+func TestBindingDateTimeTimestamp(t *testing.T) {
+	createDSN("America/Los_Angeles")
+	runTests(t, dsn, func(dbt *DBTest) {
+		expected := time.Now()
+		dbt.mustExec(
+			"CREATE OR REPLACE TABLE tztest (id int, ntz timestamp_ntz, ltz timestamp_ltz, dt date, tm time)")
+		stmt, err := dbt.db.Prepare("INSERT INTO tztest(id,ntz,ltz,dt,tm) VALUES(1,?,?,?,?)")
+		if err != nil {
+			dbt.Fatal(err.Error())
+		}
+		defer stmt.Close()
+		_, err = stmt.Exec(
+			DataTypeTimestampNtz, expected,
+			DataTypeTimestampLtz, expected,
+			DataTypeDate, expected,
+			DataTypeTime, expected)
+		if err != nil {
+			dbt.Fatal(err)
+		}
+		rows := dbt.mustQuery("SELECT ntz,ltz,dt,tm FROM tztest WHERE id=?", 1)
+		defer rows.Close()
+		var ntz, vltz, dt, tm time.Time
+		columnTypes, err := rows.ColumnTypes()
+		if err != nil {
+			dbt.Errorf("column type error. err: %v", err)
+		}
+		if columnTypes[0].Name() != "NTZ" {
+			dbt.Errorf("expected column name: %v, got: %v", "TEST", columnTypes[0])
+		}
+		canNull := dbt.mustNullable(columnTypes[0])
+		if !canNull {
+			dbt.Errorf("expected nullable: %v, got: %v", true, canNull)
+		}
+		if columnTypes[0].DatabaseTypeName() != "TIMESTAMP_NTZ" {
+			dbt.Errorf("expected database type: %v, got: %v", "TIMESTAMP_NTZ", columnTypes[0].DatabaseTypeName())
+		}
+		dbt.mustFailDecimalSize(columnTypes[0])
+		dbt.mustFailLength(columnTypes[0])
+		cols, err := rows.Columns()
+		if err != nil {
+			dbt.Errorf("failed to get columns. err: %v", err)
+		}
+		if len(cols) != 4 || cols[0] != "NTZ" || cols[1] != "LTZ" || cols[2] != "DT" || cols[3] != "TM" {
+			dbt.Errorf("failed to get columns. got: %v", cols)
+		}
+		if rows.Next() {
+			rows.Scan(&ntz, &vltz, &dt, &tm)
+			if expected.UnixNano() != ntz.UnixNano() {
+				dbt.Errorf("returned TIMESTAMP_NTZ value didn't match. expected: %v:%v, got: %v:%v",
+					expected.UnixNano(), expected, ntz.UnixNano(), ntz)
+			}
+			if expected.UnixNano() != vltz.UnixNano() {
+				dbt.Errorf("returned TIMESTAMP_LTZ value didn't match. expected: %v:%v, got: %v:%v",
+					expected.UnixNano(), expected, vltz.UnixNano(), vltz)
+			}
+			if expected.Year() != dt.Year() || expected.Month() != dt.Month() || expected.Day() != dt.Day() {
+				dbt.Errorf("returned DATE value didn't match. expected: %v:%v, got: %v:%v",
+					expected.Unix()*1000, expected, dt.Unix()*1000, dt)
+			}
+			if expected.Hour() != tm.Hour() || expected.Minute() != tm.Minute() || expected.Second() != tm.Second() || expected.Nanosecond() != tm.Nanosecond() {
+				dbt.Errorf("returned TIME value didn't match. expected: %v:%v, got: %v:%v",
+					expected.UnixNano(), expected, tm.UnixNano(), tm)
+			}
+		} else {
+			dbt.Error("no data")
+		}
+		dbt.mustExec("DROP TABLE tztest")
+	})
+
+	createDSN("UTC")
+}
+
+func TestBindingBinary(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE OR REPLACE TABLE bintest (id int, b binary)")
+		var b = []byte{0x01, 0x02, 0x03}
+		dbt.mustExec("INSERT INTO bintest(id,b) VALUES(1, ?)", DataTypeBinary, b)
+		rows := dbt.mustQuery("SELECT b FROM bintest WHERE id=?", 1)
+		defer rows.Close()
+		if rows.Next() {
+			var rb []byte
+			if err := rows.Scan(&rb); err != nil {
+				dbt.Errorf("failed to scan data. err: %v", err)
+			}
+			if !bytes.Equal(b, rb) {
+				dbt.Errorf("failed to match data. expected: %v, got: %v", b, rb)
+			}
+		} else {
+			dbt.Errorf("no data")
+		}
+		dbt.mustExec("DROP TABLE bintest")
+	})
+}
+
+func TestBindingTimestampTZ(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		expected := time.Now()
+		dbt.mustExec("CREATE OR REPLACE TABLE tztest (id int, tz timestamp_tz)")
+		stmt, err := dbt.db.Prepare("INSERT INTO tztest(id,tz) VALUES(1, ?)")
+		if err != nil {
+			dbt.Fatal(err.Error())
+		}
+		defer stmt.Close()
+		_, err = stmt.Exec(DataTypeTimestampTz, expected)
+		if err != nil {
+			dbt.Fatal(err)
+		}
+		rows := dbt.mustQuery("SELECT tz FROM tztest WHERE id=?", 1)
+		defer rows.Close()
+		var v time.Time
+		if rows.Next() {
+			rows.Scan(&v)
+			if expected.UnixNano() != v.UnixNano() {
+				dbt.Errorf("returned value didn't match. expected: %v:%v, got: %v:%v",
+					expected.UnixNano(), expected, v.UnixNano(), v)
+			}
+			// fmt.Printf("returned value: %v, %v, %v\n", v, v.UnixNano(), expected.UnixNano())
+		} else {
+			dbt.Error("no data")
+		}
+		dbt.mustExec("DROP TABLE tztest")
+	})
+}
+
+func TestBindingInterface(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		var err error
+		rows := dbt.mustQuery(
+			"SELECT 1.0::NUMBER(30,2) as C1, 2::NUMBER(38,0) AS C2, 't3' AS C3, 4.2::DOUBLE AS C4, 'abcd'::BINARY AS C5, true AS C6")
+		defer rows.Close()
+		if !rows.Next() {
+			dbt.Error("failed to query")
+		}
+		var v1, v2, v3, v4, v5, v6 interface{}
+		err = rows.Scan(&v1, &v2, &v3, &v4, &v5, &v6)
+		if err != nil {
+			dbt.Errorf("failed to scan: %#v", err)
+		}
+		var s string
+		var ok bool
+		s, ok = v1.(string)
+		if !ok || s != "1.00" {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v1)
+		}
+		s, ok = v2.(string)
+		if !ok || s != "2" {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v2)
+		}
+		s, ok = v3.(string)
+		if !ok || s != "t3" {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v3)
+		}
+		s, ok = v4.(string)
+		if !ok || s != "4.2" {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v4)
+		}
+	})
+}
+
+func TestBindingArrowInterface(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec(enableArrow)
+		var err error
+		rows := dbt.mustQuery(
+			"SELECT 1.0::NUMBER(30,2) as C1, 2::NUMBER(38,0) AS C2, 't3' AS C3, 4.2::DOUBLE AS C4, 'abcd'::BINARY AS C5, true AS C6")
+		defer rows.Close()
+		if !rows.Next() {
+			dbt.Error("failed to query")
+		}
+		var v1, v2, v3, v4, v5, v6 interface{}
+		err = rows.Scan(&v1, &v2, &v3, &v4, &v5, &v6)
+		if err != nil {
+			dbt.Errorf("failed to scan: %#v", err)
+		}
+		var s1 *big.Float
+		var s2 int64
+		var s3 string
+		var s4 float64
+		var ok bool
+		s1, ok = v1.(*big.Float)
+		if !ok || s1.Cmp(big.NewFloat(1.0)) != 0 {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v1)
+		}
+		s2, ok = v2.(int64)
+		if !ok || s2 != 2 {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v2)
+		}
+		s3, ok = v3.(string)
+		if !ok || s3 != "t3" {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v3)
+		}
+		s4, ok = v4.(float64)
+		if !ok || s4 != 4.2 {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v4)
+		}
+	})
+}
+
+// TestBindingArray tests basic array binding via the usage of the Array
+// function that converts the passed Golang slice to a Snowflake array type
+func TestBindingArray(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec(createTableSQL)
+		defer dbt.mustExec(deleteTableSQL)
+
+		intArray := []int{1, 2, 3}
+		fltArray := []float64{0.1, 2.34, 5.678}
+		boolArray := []bool{true, false, true}
+		strArray := []string{"test1", "test2", "test3"}
+		dbt.mustExec(insertSQL, Array(&intArray), Array(&fltArray), Array(&boolArray), Array(&strArray))
+		rows := dbt.mustQuery(selectAllSQL)
+		defer rows.Close()
+
+		var v1 int
+		var v2 float64
+		var v3 bool
+		var v4 string
+		if rows.Next() {
+			err := rows.Scan(&v1, &v2, &v3, &v4)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if v1 != 1 && v2 != 0.1 && v3 != true && v4 != "test1" {
+				t.Fatalf("failed to fetch. expected: 1, 0.1, true, test1. got: %v, %v, %v, %v", v1, v2, v3, v4)
+			}
+		} else {
+			t.Error("failed to query")
+		}
+
+		if rows.Next() {
+			err := rows.Scan(&v1, &v2, &v3, &v4)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if v1 != 2 && v2 != 2.34 && v3 != false && v4 != "test2" {
+				t.Fatalf("failed to fetch. expected: 2, 2.34, false, test2. got: %v, %v, %v, %v", v1, v2, v3, v4)
+			}
+		} else {
+			t.Error("failed to query")
+		}
+
+		if rows.Next() {
+			err := rows.Scan(&v1, &v2, &v3, &v4)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if v1 != 3 && v2 != 5.678 && v3 != true && v4 != "test3" {
+				t.Fatalf("failed to fetch. expected: 3, test3. got: %v, %v, %v, %v", v1, v2, v3, v4)
+			}
+		} else {
+			t.Error("failed to query")
+		}
+	})
+}
diff --git a/connection_test.go b/connection_test.go
index 922ea5b..750cb8c 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -94,7 +94,7 @@ func TestGetQueryResultUsesTokenFromTokenAccessor(t *testing.T) {
 		}, nil
 	}
 	sr := &snowflakeRestful{
-		FuncGet: funcGetMock,
+		FuncGet:       funcGetMock,
 		TokenAccessor: ta,
 	}
 	sc := &snowflakeConn{
diff --git a/converter_test.go b/converter_test.go
index bed6457..9f2de53 100644
--- a/converter_test.go
+++ b/converter_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2020 Snowflake Computing Inc. All right reserved.
+// Copyright (c) 2017-2021 Snowflake Computing Inc. All right reserved.
 
 package gosnowflake
 
@@ -17,34 +17,35 @@ import (
 
 type tcGoTypeToSnowflake struct {
 	in    interface{}
-	tmode string
-	out   string
+	tmode snowflakeType
+	out   snowflakeType
 }
 
 func TestGoTypeToSnowflake(t *testing.T) {
 	testcases := []tcGoTypeToSnowflake{
-		{in: int64(123), tmode: "", out: "FIXED"},
-		{in: float64(234.56), tmode: "", out: "REAL"},
-		{in: true, tmode: "", out: "BOOLEAN"},
-		{in: "teststring", tmode: "", out: "TEXT"},
-		{in: nil, tmode: "", out: "TEXT"}, // nil is taken as TEXT
-		{in: []int{1}, tmode: "", out: "ARRAY"},
-		{in: DataTypeBinary, tmode: "", out: "CHANGE_TYPE"},
-		{in: DataTypeTimestampLtz, tmode: "", out: "CHANGE_TYPE"},
-		{in: DataTypeTimestampNtz, tmode: "", out: "CHANGE_TYPE"},
-		{in: DataTypeTimestampTz, tmode: "", out: "CHANGE_TYPE"},
-		{in: time.Now(), tmode: "TIMESTAMP_NTZ", out: "TIMESTAMP_NTZ"},
-		{in: time.Now(), tmode: "TIMESTAMP_TZ", out: "TIMESTAMP_TZ"},
-		{in: time.Now(), tmode: "TIMESTAMP_LTZ", out: "TIMESTAMP_LTZ"},
-		{in: []byte{1, 2, 3}, tmode: "BINARY", out: "BINARY"},
+		{in: int64(123), tmode: nullType, out: fixedType},
+		{in: float64(234.56), tmode: nullType, out: realType},
+		{in: true, tmode: nullType, out: booleanType},
+		{in: "teststring", tmode: nullType, out: textType},
+		{in: Array([]int{1}), tmode: nullType, out: sliceType},
+		{in: DataTypeBinary, tmode: nullType, out: changeType},
+		{in: DataTypeTimestampLtz, tmode: nullType, out: changeType},
+		{in: DataTypeTimestampNtz, tmode: nullType, out: changeType},
+		{in: DataTypeTimestampTz, tmode: nullType, out: changeType},
+		{in: time.Now(), tmode: timestampNtzType, out: timestampNtzType},
+		{in: time.Now(), tmode: timestampTzType, out: timestampTzType},
+		{in: time.Now(), tmode: timestampLtzType, out: timestampLtzType},
+		{in: []byte{1, 2, 3}, tmode: binaryType, out: binaryType},
 		// negative
-		{in: 123, tmode: "", out: "TEXT"},
-		{in: int8(12), tmode: "", out: "TEXT"},
-		{in: int32(456), tmode: "", out: "TEXT"},
-		{in: uint(456), tmode: "", out: "TEXT"},
-		{in: uint8(12), tmode: "", out: "TEXT"},
-		{in: uint64(456), tmode: "", out: "TEXT"},
-		{in: []byte{100}, tmode: "", out: "TEXT"},
+		{in: 123, tmode: nullType, out: unSupportedType},
+		{in: int8(12), tmode: nullType, out: unSupportedType},
+		{in: int32(456), tmode: nullType, out: unSupportedType},
+		{in: uint(456), tmode: nullType, out: unSupportedType},
+		{in: uint8(12), tmode: nullType, out: unSupportedType},
+		{in: uint64(456), tmode: nullType, out: unSupportedType},
+		{in: []byte{100}, tmode: nullType, out: unSupportedType},
+		{in: nil, tmode: nullType, out: unSupportedType},
+		{in: []int{1}, tmode: nullType, out: unSupportedType},
 	}
 	for _, test := range testcases {
 		a := goTypeToSnowflake(test.in, test.tmode)
@@ -55,27 +56,27 @@ func TestGoTypeToSnowflake(t *testing.T) {
 }
 
 type tcSnowflakeTypeToGo struct {
-	in    string
+	in    snowflakeType
 	scale int64
 	out   reflect.Type
 }
 
 func TestSnowflakeTypeToGo(t *testing.T) {
 	testcases := []tcSnowflakeTypeToGo{
-		{in: "fixed", scale: 0, out: reflect.TypeOf(int64(0))},
-		{in: "fixed", scale: 2, out: reflect.TypeOf(float64(0))},
-		{in: "real", scale: 0, out: reflect.TypeOf(float64(0))},
-		{in: "text", scale: 0, out: reflect.TypeOf("")},
-		{in: "date", scale: 0, out: reflect.TypeOf(time.Now())},
-		{in: "time", scale: 0, out: reflect.TypeOf(time.Now())},
-		{in: "timestamp_ltz", scale: 0, out: reflect.TypeOf(time.Now())},
-		{in: "timestamp_ntz", scale: 0, out: reflect.TypeOf(time.Now())},
-		{in: "timestamp_tz", scale: 0, out: reflect.TypeOf(time.Now())},
-		{in: "object", scale: 0, out: reflect.TypeOf("")},
-		{in: "variant", scale: 0, out: reflect.TypeOf("")},
-		{in: "array", scale: 0, out: reflect.TypeOf("")},
-		{in: "binary", scale: 0, out: reflect.TypeOf([]byte{})},
-		{in: "boolean", scale: 0, out: reflect.TypeOf(true)},
+		{in: fixedType, scale: 0, out: reflect.TypeOf(int64(0))},
+		{in: fixedType, scale: 2, out: reflect.TypeOf(float64(0))},
+		{in: realType, scale: 0, out: reflect.TypeOf(float64(0))},
+		{in: textType, scale: 0, out: reflect.TypeOf("")},
+		{in: dateType, scale: 0, out: reflect.TypeOf(time.Now())},
+		{in: timeType, scale: 0, out: reflect.TypeOf(time.Now())},
+		{in: timestampLtzType, scale: 0, out: reflect.TypeOf(time.Now())},
+		{in: timestampNtzType, scale: 0, out: reflect.TypeOf(time.Now())},
+		{in: timestampTzType, scale: 0, out: reflect.TypeOf(time.Now())},
+		{in: objectType, scale: 0, out: reflect.TypeOf("")},
+		{in: variantType, scale: 0, out: reflect.TypeOf("")},
+		{in: arrayType, scale: 0, out: reflect.TypeOf("")},
+		{in: binaryType, scale: 0, out: reflect.TypeOf([]byte{})},
+		{in: booleanType, scale: 0, out: reflect.TypeOf(true)},
 	}
 	for _, test := range testcases {
 		a := snowflakeTypeToGo(test.in, test.scale)
@@ -88,7 +89,7 @@ func TestSnowflakeTypeToGo(t *testing.T) {
 
 func TestValueToString(t *testing.T) {
 	v := cmplx.Sqrt(-5 + 12i) // should never happen as Go sql package must have already validated.
-	_, err := valueToString(v, "")
+	_, err := valueToString(v, nullType)
 	if err == nil {
 		t.Errorf("should raise error: %v", v)
 	}
@@ -98,7 +99,7 @@ func TestValueToString(t *testing.T) {
 	utcTime := time.Date(2019, 2, 6, 22, 17, 31, 123456789, time.UTC)
 	expectedUnixTime := "1549491451123456789" // time.Unix(1549491451, 123456789).Format(time.RFC3339) == "2019-02-06T14:17:31-08:00"
 
-	if s, err := valueToString(localTime, "TIMESTAMP_LTZ"); err != nil {
+	if s, err := valueToString(localTime, timestampLtzType); err != nil {
 		t.Error("unexpected error")
 	} else if s == nil {
 		t.Errorf("expected '%v', got %v", expectedUnixTime, s)
@@ -106,7 +107,7 @@ func TestValueToString(t *testing.T) {
 		t.Errorf("expected '%v', got '%v'", expectedUnixTime, *s)
 	}
 
-	if s, err := valueToString(utcTime, "TIMESTAMP_LTZ"); err != nil {
+	if s, err := valueToString(utcTime, timestampLtzType); err != nil {
 		t.Error("unexpected error")
 	} else if s == nil {
 		t.Errorf("expected '%v', got %v", expectedUnixTime, s)
@@ -186,21 +187,21 @@ func TestStringToValue(t *testing.T) {
 }
 
 type tcArrayToString struct {
-	in  interface{}
-	typ string
+	in  driver.NamedValue
+	typ snowflakeType
 	out []string
 }
 
 func TestArrayToString(t *testing.T) {
 	testcases := []tcArrayToString{
-		{in: []int{1, 2}, typ: "FIXED", out: []string{"1", "2"}},
-		{in: []int64{3, 4, 5}, typ: "FIXED", out: []string{"3", "4", "5"}},
-		{in: []float64{6.7}, typ: "REAL", out: []string{"6.7"}},
-		{in: []bool{true, false}, typ: "BOOLEAN", out: []string{"true", "false"}},
-		{in: []string{"foo", "bar", "baz"}, typ: "TEXT", out: []string{"foo", "bar", "baz"}},
+		{in: driver.NamedValue{Value: &intArray{1, 2}}, typ: fixedType, out: []string{"1", "2"}},
+		{in: driver.NamedValue{Value: &int64Array{3, 4, 5}}, typ: fixedType, out: []string{"3", "4", "5"}},
+		{in: driver.NamedValue{Value: &float64Array{6.7}}, typ: realType, out: []string{"6.7"}},
+		{in: driver.NamedValue{Value: &boolArray{true, false}}, typ: booleanType, out: []string{"true", "false"}},
+		{in: driver.NamedValue{Value: &stringArray{"foo", "bar", "baz"}}, typ: textType, out: []string{"foo", "bar", "baz"}},
 	}
 	for _, test := range testcases {
-		s, a := arrayToString(test.in)
+		s, a := snowflakeArrayToString(&test.in)
 		if s != test.typ {
 			t.Errorf("failed. in: %v, expected: %v, got: %v", test.in, test.typ, s)
 		}
diff --git a/datatype_test.go b/datatype_test.go
index 30bc59d..c331b59 100644
--- a/datatype_test.go
+++ b/datatype_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2019 Snowflake Computing Inc. All right reserved.
+// Copyright (c) 2017-2021 Snowflake Computing Inc. All right reserved.
 
 package gosnowflake
 
@@ -10,23 +10,23 @@ import (
 
 type tcDataTypeMode struct {
 	tp    driver.Value
-	tmode string
+	tmode snowflakeType
 	err   error
 }
 
 func TestDataTypeMode(t *testing.T) {
 	var testcases = []tcDataTypeMode{
-		{tp: DataTypeTimestampLtz, tmode: "TIMESTAMP_LTZ", err: nil},
-		{tp: DataTypeTimestampNtz, tmode: "TIMESTAMP_NTZ", err: nil},
-		{tp: DataTypeTimestampTz, tmode: "TIMESTAMP_TZ", err: nil},
-		{tp: DataTypeDate, tmode: "DATE", err: nil},
-		{tp: DataTypeTime, tmode: "TIME", err: nil},
-		{tp: DataTypeBinary, tmode: "BINARY", err: nil},
-		{tp: DataTypeFixed, tmode: "FIXED",
+		{tp: DataTypeTimestampLtz, tmode: timestampLtzType, err: nil},
+		{tp: DataTypeTimestampNtz, tmode: timestampNtzType, err: nil},
+		{tp: DataTypeTimestampTz, tmode: timestampTzType, err: nil},
+		{tp: DataTypeDate, tmode: dateType, err: nil},
+		{tp: DataTypeTime, tmode: timeType, err: nil},
+		{tp: DataTypeBinary, tmode: binaryType, err: nil},
+		{tp: DataTypeFixed, tmode: fixedType,
 			err: fmt.Errorf(errMsgInvalidByteArray, DataTypeFixed)},
-		{tp: DataTypeReal, tmode: "REAL",
+		{tp: DataTypeReal, tmode: realType,
 			err: fmt.Errorf(errMsgInvalidByteArray, DataTypeFixed)},
-		{tp: 123, tmode: "",
+		{tp: 123, tmode: nullType,
 			err: fmt.Errorf(errMsgInvalidByteArray, 123)},
 	}
 	for _, ts := range testcases {
diff --git a/driver_test.go b/driver_test.go
index 52eb65b..e25d2e1 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -39,6 +39,10 @@ var (
 	testPrivKey      *rsa.PrivateKey // Valid private key used for all test cases
 )
 
+const (
+	enableArrow = "ALTER SESSION SET GO_QUERY_RESULT_FORMAT = ARROW_FORCE "
+)
+
 // The tests require the following parameters in the environment variables.
 // SNOWFLAKE_TEST_USER, SNOWFLAKE_TEST_PASSWORD, SNOWFLAKE_TEST_ACCOUNT, SNOWFLAKE_TEST_DATABASE,
 // SNOWFLAKE_TEST_SCHEMA, SNOWFLAKE_TEST_WAREHOUSE.
@@ -286,6 +290,14 @@ func (dbt *DBTest) mustNullable(ct *sql.ColumnType) (canNull bool) {
 	return canNull
 }
 
+func (dbt *DBTest) mustPrepare(query string) (stmt *sql.Stmt) {
+	stmt, err := dbt.db.Prepare(query)
+	if err != nil {
+		dbt.fail("prepare", query, err)
+	}
+	return stmt
+}
+
 func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 	db, err := sql.Open("snowflake", dsn)
 	if err != nil {
@@ -543,7 +555,7 @@ func testInt(t *testing.T, arrow bool) {
 		// SIGNED
 		for _, v := range types {
 			if arrow {
-				dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+				dbt.mustExec(enableArrow)
 			}
 			dbt.mustExec("CREATE TABLE test (value " + v + ")")
 			dbt.mustExec("INSERT INTO test VALUES (?)", in)
@@ -587,7 +599,7 @@ func TestArrowBigInt(t *testing.T) {
 	}
 
 	for _, tc := range testcases {
-		dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+		dbt.mustExec(enableArrow)
 		rows := dbt.mustQuery(fmt.Sprintf("SELECT %s::NUMBER(%v, %v) AS C", tc.num, tc.prec, tc.sc))
 		if !rows.Next() {
 			dbt.Error("failed to query")
@@ -625,7 +637,7 @@ func testFloat32(t *testing.T, arrow bool) {
 		var rows *RowsExtended
 		for _, v := range types {
 			if arrow {
-				dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+				dbt.mustExec(enableArrow)
 			}
 			dbt.mustExec("CREATE TABLE test (value " + v + ")")
 			dbt.mustExec("INSERT INTO test VALUES (?)", in)
@@ -663,7 +675,7 @@ func testFloat64(t *testing.T, arrow bool) {
 		var rows *RowsExtended
 		for _, v := range types {
 			if arrow {
-				dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+				dbt.mustExec(enableArrow)
 			}
 			dbt.mustExec("CREATE TABLE test (value " + v + ")")
 			dbt.mustExec("INSERT INTO test VALUES (42.23)")
@@ -701,7 +713,7 @@ func TestArrowBigFloat(t *testing.T) {
 	}
 
 	for _, tc := range testcases {
-		dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+		dbt.mustExec(enableArrow)
 		rows := dbt.mustQuery(fmt.Sprintf("SELECT %s::NUMBER(%v, %v) AS C", tc.num, tc.prec, tc.sc))
 		if !rows.Next() {
 			dbt.Error("failed to query")
@@ -724,261 +736,6 @@ func TestArrowBigFloat(t *testing.T) {
 	}
 }
 
-func TestFloat64Placeholder(t *testing.T) {
-	testFloat64Placeholder(t, false)
-}
-
-func TestArrowFloat64Placeholder(t *testing.T) {
-	testFloat64Placeholder(t, true)
-}
-
-func testFloat64Placeholder(t *testing.T, arrow bool) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		types := [2]string{"FLOAT", "DOUBLE"}
-		expected := 42.23
-		var out float64
-		var rows *RowsExtended
-		for _, v := range types {
-			if arrow {
-				dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
-			}
-			dbt.mustExec(fmt.Sprintf("CREATE TABLE test (id int, value %v)", v))
-			dbt.mustExec("INSERT INTO test VALUES (1, ?)", expected)
-			rows = dbt.mustQuery("SELECT value FROM test WHERE id = ?", 1)
-			defer rows.Close()
-			if rows.Next() {
-				rows.Scan(&out)
-				if expected != out {
-					dbt.Errorf("%s: %g != %g", v, expected, out)
-				}
-			} else {
-				dbt.Errorf("%s: no data", v)
-			}
-			dbt.mustExec("DROP TABLE IF EXISTS test")
-		}
-	})
-}
-
-func TestUint64Placeholder(t *testing.T) {
-	testUint64Placeholder(t, false)
-}
-
-func TestArrowUint64Placeholder(t *testing.T) {
-	testUint64Placeholder(t, true)
-}
-
-// TestUint64Placeholder tests uint64 binding. Should fail as unit64 is not a supported binding value by Go's sql
-// package.
-func testUint64Placeholder(t *testing.T, arrow bool) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		types := []string{"INTEGER"}
-		expected := uint64(18446744073709551615)
-		for _, v := range types {
-			if arrow {
-				dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
-			}
-			dbt.mustExec(fmt.Sprintf("CREATE TABLE test (id int, value %v)", v))
-			_, err := dbt.db.Exec("INSERT INTO test VALUES (1, ?)", expected)
-			if err == nil {
-				dbt.Fatal("should fail as uint64 values with high bit set are not supported.")
-			} else {
-				logger.Infof("expected err: %v", err)
-			}
-			dbt.mustExec("DROP TABLE IF EXISTS test")
-		}
-	})
-}
-
-func TestDateTimeTimestampPlaceholder(t *testing.T) {
-	testDateTimeTimestampPlaceholder(t, false)
-}
-
-func TestArrowDateTimeTimestampPlaceholder(t *testing.T) {
-	testDateTimeTimestampPlaceholder(t, true)
-}
-
-func testDateTimeTimestampPlaceholder(t *testing.T, arrow bool) {
-	createDSN("America/Los_Angeles")
-	runTests(t, dsn, func(dbt *DBTest) {
-		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
-		}
-		expected := time.Now()
-		dbt.mustExec(
-			"CREATE OR REPLACE TABLE tztest (id int, ntz timestamp_ntz, ltz timestamp_ltz, dt date, tm time)")
-		stmt, err := dbt.db.Prepare("INSERT INTO tztest(id,ntz,ltz,dt,tm) VALUES(1,?,?,?,?)")
-		if err != nil {
-			dbt.Fatal(err.Error())
-		}
-		defer stmt.Close()
-		_, err = stmt.Exec(
-			DataTypeTimestampNtz, expected,
-			DataTypeTimestampLtz, expected,
-			DataTypeDate, expected,
-			DataTypeTime, expected)
-		if err != nil {
-			dbt.Fatal(err)
-		}
-		rows := dbt.mustQuery("SELECT ntz,ltz,dt,tm FROM tztest WHERE id=?", 1)
-		defer rows.Close()
-		var ntz, vltz, dt, tm time.Time
-		columnTypes, err := rows.ColumnTypes()
-		if err != nil {
-			dbt.Errorf("column type error. err: %v", err)
-		}
-		if columnTypes[0].Name() != "NTZ" {
-			dbt.Errorf("expected column name: %v, got: %v", "TEST", columnTypes[0])
-		}
-		canNull := dbt.mustNullable(columnTypes[0])
-		if !canNull {
-			dbt.Errorf("expected nullable: %v, got: %v", true, canNull)
-		}
-		if columnTypes[0].DatabaseTypeName() != "TIMESTAMP_NTZ" {
-			dbt.Errorf("expected database type: %v, got: %v", "TIMESTAMP_NTZ", columnTypes[0].DatabaseTypeName())
-		}
-		dbt.mustFailDecimalSize(columnTypes[0])
-		dbt.mustFailLength(columnTypes[0])
-		cols, err := rows.Columns()
-		if err != nil {
-			dbt.Errorf("failed to get columns. err: %v", err)
-		}
-		if len(cols) != 4 || cols[0] != "NTZ" || cols[1] != "LTZ" || cols[2] != "DT" || cols[3] != "TM" {
-			dbt.Errorf("failed to get columns. got: %v", cols)
-		}
-		if rows.Next() {
-			rows.Scan(&ntz, &vltz, &dt, &tm)
-			if expected.UnixNano() != ntz.UnixNano() {
-				dbt.Errorf("returned TIMESTAMP_NTZ value didn't match. expected: %v:%v, got: %v:%v",
-					expected.UnixNano(), expected, ntz.UnixNano(), ntz)
-			}
-			if expected.UnixNano() != vltz.UnixNano() {
-				dbt.Errorf("returned TIMESTAMP_LTZ value didn't match. expected: %v:%v, got: %v:%v",
-					expected.UnixNano(), expected, vltz.UnixNano(), vltz)
-			}
-			if expected.Year() != dt.Year() || expected.Month() != dt.Month() || expected.Day() != dt.Day() {
-				dbt.Errorf("returned DATE value didn't match. expected: %v:%v, got: %v:%v",
-					expected.Unix()*1000, expected, dt.Unix()*1000, dt)
-			}
-			if expected.Hour() != tm.Hour() || expected.Minute() != tm.Minute() || expected.Second() != tm.Second() || expected.Nanosecond() != tm.Nanosecond() {
-				dbt.Errorf("returned TIME value didn't match. expected: %v:%v, got: %v:%v",
-					expected.UnixNano(), expected, tm.UnixNano(), tm)
-			}
-		} else {
-			dbt.Error("no data")
-		}
-		dbt.mustExec("DROP TABLE tztest")
-	})
-
-	createDSN("UTC")
-}
-
-func TestBinaryPlaceholder(t *testing.T) {
-	testBinaryPlaceholder(t, false)
-}
-
-func TestArrowBinaryPlaceholder(t *testing.T) {
-	testBinaryPlaceholder(t, true)
-}
-
-func testBinaryPlaceholder(t *testing.T, arrow bool) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
-		}
-		dbt.mustExec("CREATE OR REPLACE TABLE bintest (id int, b binary)")
-		var b = []byte{0x01, 0x02, 0x03}
-		dbt.mustExec("INSERT INTO bintest(id,b) VALUES(1, ?)", DataTypeBinary, b)
-		rows := dbt.mustQuery("SELECT b FROM bintest WHERE id=?", 1)
-		defer rows.Close()
-		if rows.Next() {
-			var rb []byte
-			if err := rows.Scan(&rb); err != nil {
-				dbt.Errorf("failed to scan data. err: %v", err)
-			}
-			if !bytes.Equal(b, rb) {
-				dbt.Errorf("failed to match data. expected: %v, got: %v", b, rb)
-			}
-		} else {
-			dbt.Errorf("no data")
-		}
-		dbt.mustExec("DROP TABLE bintest")
-	})
-}
-
-func TestBindingInterface(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		var err error
-		rows := dbt.mustQuery(
-			"SELECT 1.0::NUMBER(30,2) as C1, 2::NUMBER(38,0) AS C2, 't3' AS C3, 4.2::DOUBLE AS C4, 'abcd'::BINARY AS C5, true AS C6")
-		defer rows.Close()
-		if !rows.Next() {
-			dbt.Error("failed to query")
-		}
-		var v1, v2, v3, v4, v5, v6 interface{}
-		err = rows.Scan(&v1, &v2, &v3, &v4, &v5, &v6)
-		if err != nil {
-			dbt.Errorf("failed to scan: %#v", err)
-		}
-		var s string
-		var ok bool
-		s, ok = v1.(string)
-		if !ok || s != "1.00" {
-			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v1)
-		}
-		s, ok = v2.(string)
-		if !ok || s != "2" {
-			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v2)
-		}
-		s, ok = v3.(string)
-		if !ok || s != "t3" {
-			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v3)
-		}
-		s, ok = v4.(string)
-		if !ok || s != "4.2" {
-			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v4)
-		}
-	})
-}
-
-func TestArrowBindingInterface(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
-		var err error
-		rows := dbt.mustQuery(
-			"SELECT 1.0::NUMBER(30,2) as C1, 2::NUMBER(38,0) AS C2, 't3' AS C3, 4.2::DOUBLE AS C4, 'abcd'::BINARY AS C5, true AS C6")
-		defer rows.Close()
-		if !rows.Next() {
-			dbt.Error("failed to query")
-		}
-		var v1, v2, v3, v4, v5, v6 interface{}
-		err = rows.Scan(&v1, &v2, &v3, &v4, &v5, &v6)
-		if err != nil {
-			dbt.Errorf("failed to scan: %#v", err)
-		}
-		var s1 *big.Float
-		var s2 int64
-		var s3 string
-		var s4 float64
-		var ok bool
-		s1, ok = v1.(*big.Float)
-		if !ok || s1.Cmp(big.NewFloat(1.0)) != 0 {
-			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v1)
-		}
-		s2, ok = v2.(int64)
-		if !ok || s2 != 2 {
-			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v2)
-		}
-		s3, ok = v3.(string)
-		if !ok || s3 != "t3" {
-			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v3)
-		}
-		s4, ok = v4.(float64)
-		if !ok || s4 != 4.2 {
-			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v4)
-		}
-	})
-}
-
 func TestVariousTypes(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		rows := dbt.mustQuery(
@@ -1092,7 +849,7 @@ func TestVariousTypes(t *testing.T) {
 
 func TestArrowVariousTypes(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+		dbt.mustExec(enableArrow)
 		rows := dbt.mustQuery(
 			"SELECT 1.0::NUMBER(30,2) as C1, 2::NUMBER(38,0) AS C2, 't3' AS C3, 4.2::DOUBLE AS C4, 'abcd'::BINARY AS C5, true AS C6")
 		defer rows.Close()
@@ -1202,47 +959,6 @@ func TestArrowVariousTypes(t *testing.T) {
 	})
 }
 
-func TestTimestampTZPlaceholder(t *testing.T) {
-	testTimestampTZPlaceholder(t, false)
-}
-
-func TestArrowTimestampTZPlaceholder(t *testing.T) {
-	testTimestampTZPlaceholder(t, true)
-}
-
-func testTimestampTZPlaceholder(t *testing.T, arrow bool) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
-		}
-		expected := time.Now()
-		dbt.mustExec("CREATE OR REPLACE TABLE tztest (id int, tz timestamp_tz)")
-		stmt, err := dbt.db.Prepare("INSERT INTO tztest(id,tz) VALUES(1, ?)")
-		if err != nil {
-			dbt.Fatal(err.Error())
-		}
-		defer stmt.Close()
-		_, err = stmt.Exec(DataTypeTimestampTz, expected)
-		if err != nil {
-			dbt.Fatal(err)
-		}
-		rows := dbt.mustQuery("SELECT tz FROM tztest WHERE id=?", 1)
-		defer rows.Close()
-		var v time.Time
-		if rows.Next() {
-			rows.Scan(&v)
-			if expected.UnixNano() != v.UnixNano() {
-				dbt.Errorf("returned value didn't match. expected: %v:%v, got: %v:%v",
-					expected.UnixNano(), expected, v.UnixNano(), v)
-			}
-			// fmt.Printf("returned value: %v, %v, %v\n", v, v.UnixNano(), expected.UnixNano())
-		} else {
-			dbt.Error("no data")
-		}
-		dbt.mustExec("DROP TABLE tztest")
-	})
-}
-
 func TestString(t *testing.T) {
 	testString(t, false)
 }
@@ -1254,7 +970,7 @@ func TestArrowString(t *testing.T) {
 func testString(t *testing.T, arrow bool) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		types := []string{"CHAR(255)", "VARCHAR(255)", "TEXT", "STRING"}
 		in := "κόσμε üöäßñóùéàâÿœ'îë Árvíztűrő いろはにほへとちりぬるを イロハニホヘト דג סקרן чащах  น่าฟังเอย"
@@ -1398,7 +1114,7 @@ func testSimpleDateTimeTimestampFetch(t *testing.T, arrow bool) {
 	}
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		for _, f := range fetchTypes {
 			rows := dbt.mustQuery("SELECT CURRENT_DATE(), CURRENT_TIME(), CURRENT_TIMESTAMP()")
@@ -1469,7 +1185,7 @@ func testDateTime(t *testing.T, arrow bool) {
 	}
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		for _, setups := range testcases {
 			for _, setup := range setups.tests {
@@ -1543,7 +1259,7 @@ func testTimestampLTZ(t *testing.T, arrow bool) {
 	}
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		for _, setups := range testcases {
 			for _, setup := range setups.tests {
@@ -1596,7 +1312,7 @@ func testTimestampTZ(t *testing.T, arrow bool) {
 	}
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		for _, setups := range testcases {
 			for _, setup := range setups.tests {
@@ -1621,7 +1337,7 @@ func TestArrowNULL(t *testing.T) {
 func testNULL(t *testing.T, arrow bool) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		nullStmt, err := dbt.db.Prepare("SELECT NULL")
 		if err != nil {
@@ -1784,7 +1500,7 @@ func TestArrowVariant(t *testing.T) {
 func testVariant(t *testing.T, arrow bool) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		rows := dbt.mustQuery(`select parse_json('[{"id":1, "name":"test1"},{"id":2, "name":"test2"}]')`)
 		defer rows.Close()
@@ -1811,7 +1527,7 @@ func TestArrowArray(t *testing.T) {
 func testArray(t *testing.T, arrow bool) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		rows := dbt.mustQuery(`select as_array(parse_json('[{"id":1, "name":"test1"},{"id":2, "name":"test2"}]'))`)
 		defer rows.Close()
@@ -1827,59 +1543,6 @@ func testArray(t *testing.T, arrow bool) {
 	})
 }
 
-func TestArrayBind(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("create or replace table test(c1 int, c2 float, c3 boolean, c4 string)")
-		intArray := []int{1, 2, 3}
-		fltArray := []float64{0.1, 2.34, 5.678}
-		boolArray := []bool{true, false, true}
-		strArray := []string{"test1", "test2", "test3"}
-		dbt.mustExec(`insert into test values (?, ?, ?, ?)`, intArray, fltArray, boolArray, strArray)
-		rows := dbt.mustQuery("select * from test order by 1")
-		defer rows.Close()
-
-		var v1 int
-		var v2 float64
-		var v3 bool
-		var v4 string
-		if rows.Next() {
-			err := rows.Scan(&v1, &v2, &v3, &v4)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if v1 != 1 && v2 != 0.1 && v3 != true && v4 != "test1" {
-				t.Fatalf("failed to fetch. expected: 1, 0.1, true, test1. got: %v, %v, %v, %v", v1, v2, v3, v4)
-			}
-		} else {
-			t.Error("failed to query")
-		}
-
-		if rows.Next() {
-			err := rows.Scan(&v1, &v2, &v3, &v4)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if v1 != 2 && v2 != 2.34 && v3 != false && v4 != "test2" {
-				t.Fatalf("failed to fetch. expected: 2, 2.34, false, test2. got: %v, %v, %v, %v", v1, v2, v3, v4)
-			}
-		} else {
-			t.Error("failed to query")
-		}
-
-		if rows.Next() {
-			err := rows.Scan(&v1, &v2, &v3, &v4)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if v1 != 3 && v2 != 5.678 && v3 != true && v4 != "test3" {
-				t.Fatalf("failed to fetch. expected: 3, test3. got: %v, %v, %v, %v", v1, v2, v3, v4)
-			}
-		} else {
-			t.Error("failed to query")
-		}
-	})
-}
-
 func TestLargeSetResult(t *testing.T) {
 	CustomJSONDecoderEnabled = false
 	testLargeSetResult(t, 100000, false)
@@ -1898,7 +1561,7 @@ func TestLargeSetResultWithArrowDecoder(t *testing.T) {
 func testLargeSetResult(t *testing.T, numrows int, arrow bool) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		if arrow {
-			dbt.mustExec("ALTER SESSION set go_query_result_format = arrow_force")
+			dbt.mustExec(enableArrow)
 		}
 		rows := dbt.mustQuery(fmt.Sprintf("SELECT SEQ8(), RANDSTR(1000, RANDOM()) FROM TABLE(GENERATOR(ROWCOUNT=>%v))", numrows))
 		defer rows.Close()
diff --git a/prepared_statement_test.go b/prepared_statement_test.go
index 17165af..853056e 100644
--- a/prepared_statement_test.go
+++ b/prepared_statement_test.go
@@ -3,51 +3,64 @@
 package gosnowflake
 
 import (
-	"database/sql"
 	"testing"
 )
 
 // TestPreparedStatement creates a basic prepared statement, inserting values
 // after the statement has been prepared
 func TestPreparedStatement(t *testing.T) {
-	var db *sql.DB
-	var err error
-
-	if db, err = sql.Open("snowflake", dsn); err != nil {
-		t.Fatalf("failed to open db. %v, err: %v", dsn, err)
-	}
-	defer db.Close()
-
-	db.Exec("create or replace table test(c1 int, c2 string)")
-	intArray := []int{1, 2, 3}
-	strArray := []string{"test1", "test2", "test3"}
-
-	stmt, err := db.Prepare("insert into test values (?, ?)")
-	if err != nil {
-		t.Error(err)
-	}
-	defer stmt.Close()
-	stmt.Exec(intArray, strArray)
-
-	cnt := 0
-	rows, _ := db.Query("select * from test order by 1")
-	defer rows.Close()
-	var v1 int
-	var v2 string
-	for rows.Next() {
-		err := rows.Scan(&v1, &v2)
-		if err != nil {
-			t.Fatal(err)
+
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec(createTableSQL)
+		defer dbt.mustExec(deleteTableSQL)
+
+		intArray := []int{1, 2, 3}
+		fltArray := []float64{0.1, 2.34, 5.678}
+		boolArray := []bool{true, false, true}
+		strArray := []string{"test1", "test2", "test3"}
+		stmt := dbt.mustPrepare(insertSQL)
+		stmt.Exec(Array(&intArray), Array(&fltArray), Array(&boolArray), Array(&strArray))
+		rows := dbt.mustQuery(selectAllSQL)
+		defer rows.Close()
+
+		var v1 int
+		var v2 float64
+		var v3 bool
+		var v4 string
+		if rows.Next() {
+			err := rows.Scan(&v1, &v2, &v3, &v4)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if v1 != 1 && v2 != 0.1 && v3 != true && v4 != "test1" {
+				t.Fatalf("failed to fetch. expected: 1, 0.1, true, test1. got: %v, %v, %v, %v", v1, v2, v3, v4)
+			}
+		} else {
+			t.Error("failed to query")
 		}
-		if intArray[cnt] != v1 {
-			t.Errorf("failed to scan. expected: %v, got %v", intArray[cnt], v1)
+
+		if rows.Next() {
+			err := rows.Scan(&v1, &v2, &v3, &v4)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if v1 != 2 && v2 != 2.34 && v3 != false && v4 != "test2" {
+				t.Fatalf("failed to fetch. expected: 2, 2.34, false, test2. got: %v, %v, %v, %v", v1, v2, v3, v4)
+			}
+		} else {
+			t.Error("failed to query")
 		}
-		if strArray[cnt] != v2 {
-			t.Errorf("failed to scan. expected: %v, got %v", strArray[cnt], v2)
+
+		if rows.Next() {
+			err := rows.Scan(&v1, &v2, &v3, &v4)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if v1 != 3 && v2 != 5.678 && v3 != true && v4 != "test3" {
+				t.Fatalf("failed to fetch. expected: 3, test3. got: %v, %v, %v, %v", v1, v2, v3, v4)
+			}
+		} else {
+			t.Error("failed to query")
 		}
-		cnt++
-	}
-	if cnt != len(intArray) {
-		t.Errorf("number of rows didn't match. expected: %v, got: %v", len(intArray), cnt)
-	}
+	})
 }