Repository: snowflakedb/gosnowflake
Pull request #453 (2021-07-19 02:47:42)

SNOW-370765 Arrow High Precision Flag
### Description
Enable client side flag that enables higher precision for Arrow.

### 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
- [x] Extended the README / documentation, if necessary



Patch:
diff --git a/chunk_arrow.go b/chunk_arrow.go
index 190c748..4d7af56 100644
--- a/chunk_arrow.go
+++ b/chunk_arrow.go
@@ -17,9 +17,8 @@ type arrowResultChunk struct {
 	allocator        memory.Allocator
 }
 
-func (arc *arrowResultChunk) decodeArrowChunk(rowType []execResponseRowType) ([]chunkRowType, error) {
+func (arc *arrowResultChunk) decodeArrowChunk(rowType []execResponseRowType, highPrec bool) ([]chunkRowType, error) {
 	logger.Debug("Arrow Decoder")
-
 	var chunkRows []chunkRowType
 
 	for {
@@ -36,8 +35,7 @@ func (arc *arrowResultChunk) decodeArrowChunk(rowType []execResponseRowType) ([]
 
 		for colIdx, col := range columns {
 			destcol := make([]snowflakeValue, numRows)
-			err := arrowToValue(&destcol, rowType[colIdx], col)
-			if err != nil {
+			if err = arrowToValue(&destcol, rowType[colIdx], col, highPrec); err != nil {
 				return nil, err
 			}
 
diff --git a/chunk_downloader.go b/chunk_downloader.go
index 6103ad4..598fa8d 100644
--- a/chunk_downloader.go
+++ b/chunk_downloader.go
@@ -98,7 +98,8 @@ func (scd *snowflakeChunkDownloader) start() error {
 		// if the rowsetbase64 retrieved from the server is empty, move on to downloading chunks
 		var err error
 		firstArrowChunk := buildFirstArrowChunk(scd.RowSet.RowSetBase64)
-		scd.CurrentChunk, err = firstArrowChunk.decodeArrowChunk(scd.RowSet.RowType)
+		higherPrecision := higherPrecisionEnabled(scd.ctx)
+		scd.CurrentChunk, err = firstArrowChunk.decodeArrowChunk(scd.RowSet.RowType, higherPrecision)
 		scd.CurrentChunkSize = firstArrowChunk.rowCount
 		if err != nil {
 			return err
@@ -381,7 +382,8 @@ func decodeChunk(scd *snowflakeChunkDownloader, idx int, bufStream *bufio.Reader
 			int(scd.totalUncompressedSize()),
 			memory.NewGoAllocator(),
 		}
-		respd, err = arc.decodeArrowChunk(scd.RowSet.RowType)
+		highPrec := higherPrecisionEnabled(scd.ctx)
+		respd, err = arc.decodeArrowChunk(scd.RowSet.RowType, highPrec)
 		if err != nil {
 			return err
 		}
diff --git a/converter.go b/converter.go
index ac3e4b8..5fffafd 100644
--- a/converter.go
+++ b/converter.go
@@ -3,6 +3,7 @@
 package gosnowflake
 
 import (
+	"context"
 	"database/sql/driver"
 	"encoding/hex"
 	"fmt"
@@ -298,9 +299,13 @@ func stringFloatToDecimal(src string, scale int64) (decimal128.Num, bool) {
 	return decimal128.New(high.Int64(), low.Uint64()), ok
 }
 
-// Arrow Interface (Column) converter. This is called when Arrow chunks are downloaded to convert to the corresponding
-// row type.
-func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType, srcValue array.Interface) error {
+// Arrow Interface (Column) converter. This is called when Arrow chunks are
+// downloaded to convert to the corresponding row type.
+func arrowToValue(
+	destcol *[]snowflakeValue,
+	srcColumnMeta execResponseRowType,
+	srcValue array.Interface,
+	higherPrecision bool) error {
 	data := srcValue.Data()
 	var err error
 	if len(*destcol) != srcValue.Data().Len() {
@@ -315,9 +320,18 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			for i, num := range array.NewDecimal128Data(data).Values() {
 				if !srcValue.IsNull(i) {
 					if srcColumnMeta.Scale == 0 {
-						(*destcol)[i] = decimalToBigInt(num)
+						if higherPrecision {
+							(*destcol)[i] = decimalToBigInt(num)
+						} else {
+							(*destcol)[i] = decimalToBigInt(num).String()
+						}
 					} else {
-						(*destcol)[i] = decimalToBigFloat(num, srcColumnMeta.Scale)
+						f := decimalToBigFloat(num, srcColumnMeta.Scale)
+						if higherPrecision {
+							(*destcol)[i] = f
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%f", f)
+						}
 					}
 				}
 			}
@@ -325,10 +339,18 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			for i, val := range array.NewInt64Data(data).Int64Values() {
 				if !srcValue.IsNull(i) {
 					if srcColumnMeta.Scale == 0 {
-						(*destcol)[i] = val
+						if higherPrecision {
+							(*destcol)[i] = val
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%d", val)
+						}
 					} else {
-						f := intToBigFloat(val, srcColumnMeta.Scale)
-						(*destcol)[i] = f
+						if higherPrecision {
+							f := intToBigFloat(val, srcColumnMeta.Scale)
+							(*destcol)[i] = f
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%f", float64(val)/math.Pow10(int(srcColumnMeta.Scale)))
+						}
 					}
 				}
 			}
@@ -336,10 +358,18 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			for i, val := range array.NewInt32Data(data).Int32Values() {
 				if !srcValue.IsNull(i) {
 					if srcColumnMeta.Scale == 0 {
-						(*destcol)[i] = int64(val)
+						if higherPrecision {
+							(*destcol)[i] = int64(val)
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%d", val)
+						}
 					} else {
-						f := intToBigFloat(int64(val), srcColumnMeta.Scale)
-						(*destcol)[i] = f
+						if higherPrecision {
+							f := intToBigFloat(int64(val), srcColumnMeta.Scale)
+							(*destcol)[i] = f
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%f", float64(val)/math.Pow10(int(srcColumnMeta.Scale)))
+						}
 					}
 				}
 			}
@@ -347,10 +377,18 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			for i, val := range array.NewInt16Data(data).Int16Values() {
 				if !srcValue.IsNull(i) {
 					if srcColumnMeta.Scale == 0 {
-						(*destcol)[i] = int64(val)
+						if higherPrecision {
+							(*destcol)[i] = int64(val)
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%d", val)
+						}
 					} else {
-						f := intToBigFloat(int64(val), srcColumnMeta.Scale)
-						(*destcol)[i] = f
+						if higherPrecision {
+							f := intToBigFloat(int64(val), srcColumnMeta.Scale)
+							(*destcol)[i] = f
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%f", float64(val)/math.Pow10(int(srcColumnMeta.Scale)))
+						}
 					}
 				}
 			}
@@ -358,10 +396,18 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 			for i, val := range array.NewInt8Data(data).Int8Values() {
 				if !srcValue.IsNull(i) {
 					if srcColumnMeta.Scale == 0 {
-						(*destcol)[i] = int64(val)
+						if higherPrecision {
+							(*destcol)[i] = int64(val)
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%d", val)
+						}
 					} else {
-						f := intToBigFloat(int64(val), srcColumnMeta.Scale)
-						(*destcol)[i] = f
+						if higherPrecision {
+							f := intToBigFloat(int64(val), srcColumnMeta.Scale)
+							(*destcol)[i] = f
+						} else {
+							(*destcol)[i] = fmt.Sprintf("%f", float64(val)/math.Pow10(int(srcColumnMeta.Scale)))
+						}
 					}
 				}
 			}
@@ -376,9 +422,13 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 		}
 		return err
 	case realType:
-		for i, float64 := range array.NewFloat64Data(data).Float64Values() {
+		for i, flt64 := range array.NewFloat64Data(data).Float64Values() {
 			if !srcValue.IsNull(i) {
-				(*destcol)[i] = float64
+				if higherPrecision {
+					(*destcol)[i] = flt64
+				} else {
+					(*destcol)[i] = fmt.Sprintf("%f", flt64)
+				}
 			}
 		}
 		return err
@@ -408,17 +458,17 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 		return err
 	case timeType:
 		if srcValue.DataType().ID() == arrow.INT64 {
-			for i, int64 := range array.NewInt64Data(data).Int64Values() {
+			for i, i64 := range array.NewInt64Data(data).Int64Values() {
 				if !srcValue.IsNull(i) {
 					t0 := time.Time{}
-					(*destcol)[i] = t0.Add(time.Duration(int64))
+					(*destcol)[i] = t0.Add(time.Duration(i64))
 				}
 			}
 		} else {
-			for i, int32 := range array.NewInt32Data(data).Int32Values() {
+			for i, i32 := range array.NewInt32Data(data).Int32Values() {
 				if !srcValue.IsNull(i) {
 					t0 := time.Time{}
-					(*destcol)[i] = t0.Add(time.Duration(int64(int32) * int64(math.Pow10(9-int(srcColumnMeta.Scale)))))
+					(*destcol)[i] = t0.Add(time.Duration(int64(i32) * int64(math.Pow10(9-int(srcColumnMeta.Scale)))))
 				}
 			}
 		}
@@ -488,8 +538,7 @@ func arrowToValue(destcol *[]snowflakeValue, srcColumnMeta execResponseRowType,
 		return err
 	}
 
-	err = fmt.Errorf("unsupported data type")
-	return err
+	return fmt.Errorf("unsupported data type")
 }
 
 type (
@@ -704,3 +753,12 @@ func snowflakeArrayToString(nv *driver.NamedValue, stream bool) (snowflakeType,
 	}
 	return t, arr
 }
+
+func higherPrecisionEnabled(ctx context.Context) bool {
+	v := ctx.Value(enableHigherPrecision)
+	if v == nil {
+		return false
+	}
+	d, ok := v.(bool)
+	return ok && d
+}
diff --git a/doc.go b/doc.go
index bb1e6d1..991e8fa 100644
--- a/doc.go
+++ b/doc.go
@@ -192,81 +192,25 @@ See cmd/selectmany.go for the full example.
 
 Supported Data Types
 
-Queries return SQL column type information in the ColumnType type. The
-DatabaseTypeName method returns strings representing Snowflake data types.
-The following table shows those strings, the corresponding Snowflake data
-type, and the corresponding Golang native data type. The columns are:
-
-   1. The string representation of the data type.
-   2. The SQL data type.
-   3. The default Golang data type that is returned when you use snowflakeRows.Scan() to read data from
-      JSON data format via an interface{}. (All returned values are JSON strings.)
-   4. The standard Golang data type that is returned when you use snowflakeRows.Scan() to read data from
-      JSON data format directly.
-   5. Footnotes numbers.
-
-This table shows the data types:
-
-  =============================================================================================
-                 |                                    | Default Go  | Supported  |
-  String         |                                    | Data Type   | Go Data    |
-  Representation | Snowflake Data Type                | for Scan()  | Types for  | Footnotes
-                 |                                    | interface{} | Scan()     |
-                 |                                    | (JSON)      | (JSON)     |
-  ==============================================================================================
-  BOOLEAN        | BOOLEAN                            | string      | bool       |
-  TEXT           | VARCHAR/STRING                     | string      | string     |
-  REAL           | REAL/DOUBLE                        | string      | float64    | [1]  [2]
-  FIXED          | INTEGER that fits in int64         | string      | int64      | [1]  [2]
-  FIXED          | NUMBER(P, S) where S > 0           | string      |            | [1]  [3]
-  DATE           | DATE                               | string      | time.Time  |
-  TIME           | TIME                               | string      | time.Time  |
-  TIMESTAMP_LTZ  | TIMESTAMP_LTZ                      | string      | time.Time  |
-  TIMESTAMP_NTZ  | TIMESTAMP_NTZ                      | string      | time.Time  |
-  TIMESTAMP_TZ   | TIMESTAMP_TZ                       | string      | time.Time  |
-  BINARY         | BINARY                             | string      | []byte     |
-  ARRAY          | ARRAY                              | string      | string     |
-  OBJECT         | OBJECT                             | string      | string     |
-  VARIANT        | VARIANT                            | string      | string     |
-
-Footnotes:
-
-  [1] Converting from a higher precision data type to a lower precision data type via the snowflakeRows.Scan() method can lose low bits (lose precision), lose high bits (completely change the value), or result in error.
-
-  [2] Attempting to convert from a higher precision data type to a lower precision data type via interface{} causes an error.
-
-  [3] If the value in Snowflake is too large to fit into the corresponding Golang data type, then conversion can return either an int64 with the high bits truncated or an error.
-
-Note: SQL NULL values are converted to Golang nil values, and vice-versa.
-
-
-Support for Arrow Data Format (Preview Feature)
-
-This feature is in preview. Snowflake recommends using this feature only in development systems, not production systems.
-This preview is available to all customers.
-
-The Go Snowflake Driver now supports the Arrow data format for data transfers between Snowflake and the Golang client. The Arrow data format
-avoids extra conversions between binary and textual representations of the data. The Arrow data format can improve performance and reduce
-memory consumption in clients.
+The Go Snowflake Driver now supports the Arrow data format for data transfers
+between Snowflake and the Golang client. The Arrow data format avoids extra
+conversions between binary and textual representations of the data. The Arrow
+data format can improve performance and reduce memory consumption in clients.
 
 Snowflake continues to support the JSON data format.
 
-The data format is controlled by the session-level parameter GO_QUERY_RESULT_FORMAT. To use Arrow format, execute:
-
-	ALTER SESSION SET GO_QUERY_RESULT_FORMAT = 'ARROW';
-
-To use JSON format, execute:
+The data format is controlled by the session-level parameter
+GO_QUERY_RESULT_FORMAT. To use JSON format, execute:
 
 	ALTER SESSION SET GO_QUERY_RESULT_FORMAT = 'JSON';
 
 The valid values for the parameter are:
 
-	* ARROW
+	* ARROW (default)
 	* JSON
 
-The default value is 'JSON'. The default will change in the future.
-
-If the user attempts to set the parameter to an invalid value, an error is returned.
+If the user attempts to set the parameter to an invalid value, an error is
+returned.
 
 The parameter name and the parameter value are case-insensitive.
 
@@ -274,72 +218,99 @@ This parameter can be set only at the session level.
 
 Usage notes:
 
-	* The Arrow data format can reduce rounding errors in floating point numbers.
-          You might see slightly different values for floating point numbers when using Arrow format than when using JSON format.
+	* The Arrow data format reduces rounding errors in floating point numbers. You might see slightly
+	  different values for floating point numbers when using Arrow format than when using JSON format.
+	  In order to take advantage of the increased precision, you must pass in the context.Context object
+	  provided by the WithHigherPrecision function when querying.
 
-	* For some numeric data types, the driver can retrieve larger values when using the Arrow format than when using the
-	  JSON format. For example, using Arrow format allows the full range of SQL NUMERIC(38,0) values to be retrieved,
-	  while using JSON format allows only values in the range supported by the Golang int64 data type.
+	* For some numeric data types, the driver can retrieve larger values when using the Arrow format than
+	  when using the JSON format. For example, using Arrow format allows the full range of SQL NUMERIC(38,0)
+	  values to be retrieved, while using JSON format allows only values in the range supported by the
+	  Golang int64 data type.
 
-	  Users should ensure that Golang variables are declared using the appropriate data type for the full range of values
-	  contained in the column. For an example, see below.
+	  Users should ensure that Golang variables are declared using the appropriate data type for the full
+	  range of values contained in the column. For an example, see below.
 
-When using the Arrow format, the driver supports more Golang data types and more ways to convert SQL values to those Golang data types.
-The table below lists the supported Snowflake SQL data types and the corresponding Golang data types. The columns are:
+When using the Arrow format, the driver supports more Golang data types and
+more ways to convert SQL values to those Golang data types. The table below
+lists the supported Snowflake SQL data types and the corresponding Golang
+data types. The columns are:
 
    1. The SQL data type.
    2. The default Golang data type that is returned when you use snowflakeRows.Scan() to read data from
       Arrow data format via an interface{}.
-   3. The possible Golang data types that can be returned when you use snowflakeRows.Scan() to read data from
-      Arrow data format directly.
+   3. The possible Golang data types that can be returned when you use snowflakeRows.Scan() to read data
+      from Arrow data format directly.
    4. The default Golang data type that is returned when you use snowflakeRows.Scan() to read data from
-      JSON data format via an interface{}. (All returned values are JSON strings.)
+      JSON data format via an interface{}. (All returned values are strings.)
    5. The standard Golang data type that is returned when you use snowflakeRows.Scan() to read data from
       JSON data format directly.
 
-  ==================================================================================================================================
-  SQL Data Type                       | Default Go  | Supported Go Data Types for Scan() (ARROW)           | Default Go  | Supported
-                                      | Data Type   |                                                      | Data Type   | Go Data
-                                      | for Scan()  |                                                      | for Scan()  | Type for
-                                      | interface{} |                                                      | interface{} | Scan()
-                                      | (ARROW)     |                                                      | (JSON)      | (JSON)
-  ==================================================================================================================================
-    BOOLEAN                           | bool        | bool                                                 | string      | bool
-    VARCHAR                           | string      | string                                               | string      | string
-    DOUBLE                            | float64     | float32, float64                           [1] , [2] | string      | float64
-    INTEGER that fits in int64        | int64       | int, int8, int16, int32, int64             [1] , [2] | string      | int64
-    INTEGER that doesn't fit in int64 | \*big.Int   | int, int8, int16, int32, int64, \*big.Int  [1] , [3] | string      | [5]
-    NUMBER(P, S) where S > 0          | \*big.Float | float32, float64, \*big.Float              [1] , [4] | string      | [5]
-    DATE                              | time.Time   | time.Time                                            | string      | time.Time
-    TIME                              | time.Time   | time.Time                                            | string      | time.Time
-    TIMESTAMP_LTZ                     | time.Time   | time.Time                                            | string      | time.Time
-    TIMESTAMP_NTZ                     | time.Time   | time.Time                                            | string      | time.Time
-    TIMESTAMP_TZ                      | time.Time   | time.Time                                            | string      | time.Time
-    BINARY                            | []byte      | []byte                                               | string      | []byte
-    ARRAY                             | string      | string                                               | string      | string
-    OBJECT                            | string      | string                                               | string      | string
-    VARIANT                           | string      | string                                               | string      | string
-
-  [1] Converting from a higher precision data type to a lower precision data type via the snowflakeRows.Scan() method
-      can lose low bits (lose precision), lose high bits (completely change the value), or result in error.
-
-  [2] Attempting to convert from a higher precision data type to a lower precision data type via interface{} causes an error.
-
-  [3] You cannot directly Scan() into the alternative data types via snowflakeRows.Scan(), but can convert to those data
-      types by using .Int64()/.String()/.Uint64() methods. For an example, see below.
-
-  [4] You cannot directly Scan() into the alternative data types via snowflakeRows.Scan(), but can convert to those data
-      types by using .Float32()/.String()/.Float64() methods. For an example, see below.
-
-  [5] Returns either an int64 with the high bits truncated or an error.
+  Go Data Types for Scan()
+  ===================================================================================================================
+                         |                    ARROW                    |                    JSON
+  ===================================================================================================================
+  SQL Data Type          | Default Go Data Type   | Supported Go Data  | Default Go Data Type   | Supported Go Data
+                         | for Scan() interface{} | Types for Scan()   | for Scan() interface{} | Types for Scan()
+  ===================================================================================================================
+    BOOLEAN              | bool                                        | string                 | bool
+  -------------------------------------------------------------------------------------------------------------------
+    VARCHAR              | string                                      | string
+  -------------------------------------------------------------------------------------------------------------------
+    DOUBLE               | float32, float64                  [1] , [2] | string                 | float32, float64
+  -------------------------------------------------------------------------------------------------------------------
+    INTEGER that         | int, int8, int16, int32, int64              | string                 | int, int8, int16,
+    fits in int64        |                                   [1] , [2] |                        | int32, int64
+  -------------------------------------------------------------------------------------------------------------------
+    INTEGER that doesn't | int, int8, int16, int32, int64, \*big.Int   | string                 | error
+    fit in int64         |                       [1] , [2] , [3] , [4] |
+  -------------------------------------------------------------------------------------------------------------------
+    NUMBER(P, S)         | float32, float64, \*big.Float               | string                 | float32, float64
+    where S > 0          |                       [1] , [2] , [3] , [5] |
+  -------------------------------------------------------------------------------------------------------------------
+    DATE                 | time.Time                                   | string                 | time.Time
+  -------------------------------------------------------------------------------------------------------------------
+    TIME                 | time.Time                                   | string                 | time.Time
+  -------------------------------------------------------------------------------------------------------------------
+    TIMESTAMP_LTZ        | time.Time                                   | string                 | time.Time
+  -------------------------------------------------------------------------------------------------------------------
+    TIMESTAMP_NTZ        | time.Time                                   | string                 | time.Time
+  -------------------------------------------------------------------------------------------------------------------
+    TIMESTAMP_TZ         | time.Time                                   | string                 | time.Time
+  -------------------------------------------------------------------------------------------------------------------
+    BINARY               | []byte                                      | string                 | []byte
+  -------------------------------------------------------------------------------------------------------------------
+    ARRAY                | string                                      | string
+  -------------------------------------------------------------------------------------------------------------------
+    OBJECT               | string                                      | string
+  -------------------------------------------------------------------------------------------------------------------
+    VARIANT              | string                                      | string
+
+  [1] Converting from a higher precision data type to a lower precision data type via the snowflakeRows.Scan()
+      method can lose low bits (lose precision), lose high bits (completely change the value), or result in error.
+
+  [2] Attempting to convert from a higher precision data type to a lower precision data type via interface{}
+      causes an error.
+
+  [3] Higher precision data types like \*big.Int and \*big.Float can be accessed by querying with a context
+      returned by WithHigherPrecision().
+
+  [4] You cannot directly Scan() into the alternative data types via snowflakeRows.Scan(), but can convert to
+      those data types by using .Int64()/.String()/.Uint64() methods. For an example, see below.
+
+  [5] You cannot directly Scan() into the alternative data types via snowflakeRows.Scan(), but can convert to
+      those data types by using .Float32()/.String()/.Float64() methods. For an example, see below.
 
 Note: SQL NULL values are converted to Golang nil values, and vice-versa.
 
-The following example shows how to retrieve very large values using the math/big package. This example retrieves a large
-INTEGER value to an interface and then extracts a big.Int value from that interface. If the value
-fits into an int64, then the code also copies the value to a variable of type int64.
+The following example shows how to retrieve very large values using the math/big
+package. This example retrieves a large INTEGER value to an interface and then
+extracts a big.Int value from that interface. If the value fits into an int64,
+then the code also copies the value to a variable of type int64. Note that a
+context that enables higher precision must be passed in with the query.
 
-       import math/big
+       import "context"
+       import "math/big"
 
        ...
 
@@ -349,7 +320,7 @@ fits into an int64, then the code also copies the value to a variable of type in
        var rows snowflakeRows
 
        ...
-
+       rows = db.QueryContext(WithHigherPrecision(context.Background), <query>)
        rows.Scan(&my_interface)
        my_big_int_pointer, ok = my_interface.(*big.Int)
        if my_big_int_pointer.IsInt64() {
@@ -365,7 +336,7 @@ If the variable named "rows" contains a big.Int, then each of the following fail
 
        rows.Scan(&my_int64)
 
-       my_int64, ok = my_interface.(int64)
+       my_int64, _ = my_interface.(int64)
 
 Similar code and rules also apply to big.Float values.
 
diff --git a/driver.go b/driver.go
index 049b850..f0a17f8 100644
--- a/driver.go
+++ b/driver.go
@@ -9,8 +9,7 @@ import (
 )
 
 // SnowflakeDriver is a context of Go Driver
-type SnowflakeDriver struct {
-}
+type SnowflakeDriver struct{}
 
 // Open creates a new connection.
 func (d SnowflakeDriver) Open(dsn string) (driver.Conn, error) {
diff --git a/util.go b/util.go
index 3a4ca31..ea2b2bf 100644
--- a/util.go
+++ b/util.go
@@ -25,20 +25,20 @@ const (
 	queryIDChannel contextKey = "QUERY_ID_CHANNEL"
 	// snowflakeRequestIDKey is optional context key to specify request id
 	snowflakeRequestIDKey contextKey = "SNOWFLAKE_REQUEST_ID"
-	// streamChunkDownload determines whether to use a stream based chunk downloader
-	streamChunkDownload contextKey = "STREAM_CHUNK_DOWNLOAD"
 	// fetchResultByID the queryID of query result to fetch
 	fetchResultByID contextKey = "SF_FETCH_RESULT_BY_ID"
 	// fileStreamFile is the address of the file to be uploaded via PUT
 	fileStreamFile contextKey = "STREAMING_PUT_FILE"
 	// fileTransferOptions allows the user to pass in custom
 	fileTransferOptions contextKey = "FILE_TRANSFER_OPTIONS"
-	// describeOnly returns the description of the query
-	describeOnly contextKey = "DESCRIBE_ONLY"
+	// enableHigherPrecision returns numbers with higher precision in a *big format
+	enableHigherPrecision contextKey = "ENABLE_HIGHER_PRECISION"
 )
 
 const (
-	cancelRetry contextKey = "CANCEL_RETRY"
+	describeOnly        contextKey = "DESCRIBE_ONLY"
+	cancelRetry         contextKey = "CANCEL_RETRY"
+	streamChunkDownload contextKey = "STREAM_CHUNK_DOWNLOAD"
 )
 
 // WithMultiStatement returns a context that allows the user to execute the desired number of sql queries in one query
@@ -86,6 +86,13 @@ func WithDescribeOnly(ctx context.Context) context.Context {
 	return context.WithValue(ctx, describeOnly, true)
 }
 
+// WithHigherPrecision returns a context that enables higher precision by
+// returning a *big.Int or *big.Float variable when querying rows for column
+// types with numbers that don't fit into its native Golang counterpart
+func WithHigherPrecision(ctx context.Context) context.Context {
+	return context.WithValue(ctx, enableHigherPrecision, true)
+}
+
 // Get the request ID from the context if specified, otherwise generate one
 func getOrGenerateRequestIDFromContext(ctx context.Context) uuid.UUID {
 	requestID, ok := ctx.Value(snowflakeRequestIDKey).(uuid.UUID)


Test patch:
diff --git a/bindings_test.go b/bindings_test.go
index a6717a7..5a5deef 100644
--- a/bindings_test.go
+++ b/bindings_test.go
@@ -4,6 +4,7 @@ package gosnowflake
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"math/big"
 	"strconv"
@@ -227,7 +228,8 @@ func TestBindingArrowInterface(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		dbt.mustExec(forceArrow)
 		var err error
-		rows := dbt.mustQuery(
+		rows := dbt.mustQueryContext(
+			WithHigherPrecision(context.Background()),
 			"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() {
@@ -261,6 +263,42 @@ func TestBindingArrowInterface(t *testing.T) {
 	})
 }
 
+func TestBindingArrowInterfaceString(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec(forceArrow)
+		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{}
+		if err := rows.Scan(&v1, &v2, &v3, &v4, &v5, &v6); err != nil {
+			dbt.Errorf("failed to scan: %#v", err)
+		}
+		if s, ok := v1.(string); !ok {
+			dbt.Error("failed to convert to string")
+		} else if d, err := strconv.ParseFloat(s, 64); err != nil {
+			dbt.Errorf("failed to convert to float. value: %v, err: %v", v1, err)
+		} else if d != 1.00 {
+			dbt.Errorf("failed to fetch. expected: 1.00, value: %v", v1)
+		}
+		if s, ok := v2.(string); !ok || s != "2" {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v2)
+		}
+		if s, ok := v3.(string); !ok || s != "t3" {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v3)
+		}
+		if s, ok := v4.(string); !ok {
+			dbt.Fatalf("failed to fetch. ok: %v, value: %v", ok, v4)
+		} else if d, err := strconv.ParseFloat(s, 64); err != nil {
+			dbt.Errorf("failed to convert to float. value: %v, err: %v", v1, err)
+		} else if d != 4.2 {
+			dbt.Errorf("failed to fetch. expected: 4.2, value: %v", v1)
+		}
+	})
+}
+
 // 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) {
diff --git a/connection_test.go b/connection_test.go
index 9d32326..e288aab 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -190,8 +190,8 @@ func TestCloseIgnoreSessionGone(t *testing.T) {
 		FuncCloseSession: closeSessionMock,
 	}
 	sc := &snowflakeConn{
-		cfg:  &Config{Params: map[string]*string{}},
-		rest: sr,
+		cfg:       &Config{Params: map[string]*string{}},
+		rest:      sr,
 		telemetry: testTelemetry,
 	}
 
@@ -205,8 +205,8 @@ func TestClientSessionPersist(t *testing.T) {
 		FuncCloseSession: closeSessionMock,
 	}
 	sc := &snowflakeConn{
-		cfg:  &Config{Params: map[string]*string{}},
-		rest: sr,
+		cfg:       &Config{Params: map[string]*string{}},
+		rest:      sr,
 		telemetry: testTelemetry,
 	}
 	sc.cfg.KeepSessionAlive = true
diff --git a/converter_test.go b/converter_test.go
index a1b4baa..33e96e5 100644
--- a/converter_test.go
+++ b/converter_test.go
@@ -533,7 +533,7 @@ func TestArrowToValue(t *testing.T) {
 			meta := tc.rowType
 			meta.Type = tc.logical
 
-			err := arrowToValue(&dest, meta, arr)
+			err := arrowToValue(&dest, meta, arr, true)
 			if err != nil {
 				t.Fatalf("error: %s", err)
 			}
diff --git a/driver_test.go b/driver_test.go
index 2f1f617..7d74ecb 100644
--- a/driver_test.go
+++ b/driver_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
 
@@ -40,8 +40,9 @@ var (
 )
 
 const (
-	forceArrow  = "ALTER SESSION SET GO_QUERY_RESULT_FORMAT = ARROW_FORCE"
-	PSTLocation = "America/Los_Angeles"
+	forceArrow      = "ALTER SESSION SET GO_QUERY_RESULT_FORMAT = ARROW_FORCE"
+	selectNumberSQL = "SELECT %s::NUMBER(%v, %v) AS C"
+	PSTLocation     = "America/Los_Angeles"
 )
 
 // The tests require the following parameters in the environment variables.
@@ -649,11 +650,7 @@ type tcBigNum struct {
 }
 
 func TestArrowBigInt(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)
-	}
+	db := openDB(t)
 	dbt := &DBTest{t, db}
 
 	testcases := []tcBigNum{
@@ -667,14 +664,14 @@ func TestArrowBigInt(t *testing.T) {
 
 	for _, tc := range testcases {
 		dbt.mustExec(forceArrow)
-		rows := dbt.mustQuery(fmt.Sprintf("SELECT %s::NUMBER(%v, %v) AS C", tc.num, tc.prec, tc.sc))
+		rows := dbt.mustQueryContext(WithHigherPrecision(context.Background()),
+			fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
 		if !rows.Next() {
 			dbt.Error("failed to query")
 		}
 		defer rows.Close()
 		var v *big.Int
-		err := rows.Scan(&v)
-		if err != nil {
+		if err := rows.Scan(&v); err != nil {
 			dbt.Errorf("failed to scan. %#v", err)
 		}
 
@@ -762,11 +759,7 @@ func testFloat64(t *testing.T, arrow bool) {
 }
 
 func TestArrowBigFloat(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)
-	}
+	db := openDB(t)
 	dbt := &DBTest{t, db}
 
 	testcases := []tcBigNum{
@@ -781,14 +774,15 @@ func TestArrowBigFloat(t *testing.T) {
 
 	for _, tc := range testcases {
 		dbt.mustExec(forceArrow)
-		rows := dbt.mustQuery(fmt.Sprintf("SELECT %s::NUMBER(%v, %v) AS C", tc.num, tc.prec, tc.sc))
+		rows := dbt.mustQueryContext(WithHigherPrecision(context.Background()),
+			fmt.Sprintf("SELECT %s::NUMBER(%v, %v) AS C",
+				tc.num, tc.prec, tc.sc))
 		if !rows.Next() {
 			dbt.Error("failed to query")
 		}
 		defer rows.Close()
 		var v *big.Float
-		err := rows.Scan(&v)
-		if err != nil {
+		if err := rows.Scan(&v); err != nil {
 			dbt.Errorf("failed to scan. %#v", err)
 		}
 
@@ -803,6 +797,228 @@ func TestArrowBigFloat(t *testing.T) {
 	}
 }
 
+func TestArrowIntPrecision(t *testing.T) {
+	db := openDB(t)
+	dbt := &DBTest{t, db}
+
+	intTestcases := []struct {
+		num  string
+		prec int
+		sc   int
+	}{
+		{"10000000000000000000000000000000000000", 38, 0},
+		{"-10000000000000000000000000000000000000", 38, 0},
+		{"12345678901234567890123456789012345678", 38, 0},
+		{"-12345678901234567890123456789012345678", 38, 0},
+		{"99999999999999999999999999999999999999", 38, 0},
+		{"-99999999999999999999999999999999999999", 38, 0},
+	}
+
+	t.Run("arrow_disabled_scan_int64", func(t *testing.T) {
+		for _, tc := range intTestcases {
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v int64
+			if err := rows.Scan(&v); err == nil {
+				dbt.Error("should fail to scan")
+			}
+		}
+	})
+	t.Run("arrow_disabled_scan_string", func(t *testing.T) {
+		for _, tc := range intTestcases {
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v int64
+			if err := rows.Scan(&v); err == nil {
+				dbt.Error("should fail to scan")
+			}
+		}
+	})
+	t.Run("arrow_enabled_scan_big_int", func(t *testing.T) {
+		for _, tc := range intTestcases {
+			dbt.mustExec(forceArrow)
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v string
+			if err := rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+			if !strings.EqualFold(v, tc.num) {
+				dbt.Errorf("int value mismatch: expected %v, got %v", tc.num, v)
+			}
+		}
+	})
+	t.Run("arrow_high_precision_enabled_scan_big_int", func(t *testing.T) {
+		for _, tc := range intTestcases {
+			dbt.mustExec(forceArrow)
+			rows := dbt.mustQueryContext(
+				WithHigherPrecision(context.Background()),
+				fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v *big.Int
+			if err := rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+
+			b, ok := new(big.Int).SetString(tc.num, 10)
+			if !ok {
+				dbt.Errorf("failed to convert %v big.Int.", tc.num)
+			}
+			if v.Cmp(b) != 0 {
+				dbt.Errorf("big.Int value mismatch: expected %v, got %v", b, v)
+			}
+		}
+	})
+}
+
+// TestArrowFloatPrecision tests the different variable types allowed in the
+// rows.Scan() method. Note that for lower precision types we do not attempt
+// to check the value as precision could be lost.
+func TestArrowFloatPrecision(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)
+	}
+	dbt := &DBTest{t, db}
+
+	fltTestcases := []struct {
+		num  string
+		prec int
+		sc   int
+	}{
+		{"1.23", 30, 2},
+		{"1.0000000000000000000000000000000000000", 38, 37},
+		{"-1.0000000000000000000000000000000000000", 38, 37},
+		{"1.2345678901234567890123456789012345678", 38, 37},
+		{"-1.2345678901234567890123456789012345678", 38, 37},
+		{"9.9999999999999999999999999999999999999", 38, 37},
+		{"-9.9999999999999999999999999999999999999", 38, 37},
+	}
+
+	t.Run("arrow_disabled_scan_float64", func(t *testing.T) {
+		for _, tc := range fltTestcases {
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v float64
+			if err = rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+		}
+	})
+	t.Run("arrow_disabled_scan_float32", func(t *testing.T) {
+		for _, tc := range fltTestcases {
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v float32
+			if err = rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+		}
+	})
+	t.Run("arrow_disabled_scan_string", func(t *testing.T) {
+		for _, tc := range fltTestcases {
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v string
+			if err = rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+			if !strings.EqualFold(v, tc.num) {
+				dbt.Errorf("int value mismatch: expected %v, got %v", tc.num, v)
+			}
+		}
+	})
+	t.Run("arrow_enabled_scan_float64", func(t *testing.T) {
+		for _, tc := range fltTestcases {
+			dbt.mustExec(forceArrow)
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v float64
+			if err = rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+		}
+	})
+	t.Run("arrow_enabled_scan_float32", func(t *testing.T) {
+		for _, tc := range fltTestcases {
+			dbt.mustExec(forceArrow)
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v float32
+			if err = rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+		}
+	})
+	t.Run("arrow_enabled_scan_string", func(t *testing.T) {
+		for _, tc := range fltTestcases {
+			dbt.mustExec(forceArrow)
+			rows := dbt.mustQuery(fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v string
+			if err = rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+		}
+	})
+	t.Run("arrow_high_precision_enabled_scan_big_float", func(t *testing.T) {
+		for _, tc := range fltTestcases {
+			dbt.mustExec(forceArrow)
+			rows := dbt.mustQueryContext(
+				WithHigherPrecision(context.Background()),
+				fmt.Sprintf(selectNumberSQL, tc.num, tc.prec, tc.sc))
+			if !rows.Next() {
+				dbt.Error("failed to query")
+			}
+			defer rows.Close()
+			var v *big.Float
+			if err = rows.Scan(&v); err != nil {
+				dbt.Errorf("failed to scan. %#v", err)
+			}
+
+			prec := v.Prec()
+			b, ok := new(big.Float).SetPrec(prec).SetString(tc.num)
+			if !ok {
+				dbt.Errorf("failed to convert %v to big.Float.", tc.num)
+			}
+			if v.Cmp(b) != 0 {
+				dbt.Errorf("big.Float value mismatch: expected %v, got %v", b, v)
+			}
+		}
+	})
+}
+
 func TestVariousTypes(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		rows := dbt.mustQuery(
@@ -917,7 +1133,8 @@ func TestVariousTypes(t *testing.T) {
 func TestArrowVariousTypes(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		dbt.mustExec(forceArrow)
-		rows := dbt.mustQuery(
+		rows := dbt.mustQueryContext(
+			WithHigherPrecision(context.Background()),
 			"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() {
@@ -937,8 +1154,7 @@ func TestArrowVariousTypes(t *testing.T) {
 		var v4 float64
 		var v5 []byte
 		var v6 bool
-		err = rows.Scan(&v1, &v2, &v3, &v4, &v5, &v6)
-		if err != nil {
+		if err = rows.Scan(&v1, &v2, &v3, &v4, &v5, &v6); err != nil {
 			dbt.Errorf("failed to scan: %#v", err)
 		}
 		if v1.Cmp(big.NewFloat(1.0)) != 0 {
diff --git a/rows_test.go b/rows_test.go
index ca3dc20..4835365 100644
--- a/rows_test.go
+++ b/rows_test.go
@@ -47,8 +47,7 @@ func TestRowsWithoutChunkDownloader(t *testing.T) {
 	rows.ChunkDownloader.start()
 	dest := make([]driver.Value, 2)
 	for i = 0; i < len(cc); i++ {
-		err := rows.Next(dest)
-		if err != nil {
+		if err := rows.Next(dest); err != nil {
 			t.Fatalf("failed to get value. err: %v", err)
 		}
 		if dest[0] != sts1 {
@@ -58,8 +57,7 @@ func TestRowsWithoutChunkDownloader(t *testing.T) {
 			t.Fatalf("failed to get value. expected: %v, got: %v", sts2, dest[1])
 		}
 	}
-	err := rows.Next(dest)
-	if err != io.EOF {
+	if err := rows.Next(dest); err != io.EOF {
 		t.Fatalf("failed to finish getting data. err: %v", err)
 	}
 	logger.Infof("dest: %v", dest)