Repository: snowflakedb/gosnowflake
Pull request #382 (2021-02-17 02:04:00)

SNOW-209100 UnExport Fields
### Description
UnExporting fields

### 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


sfc-gh-dwu: Is there a test that shows + verifies how this is intended to be used?
The code LGTM, just wonder if a test can be added to demonstrate usage + have test coverage.

sfc-gh-dwu: For reference, when `sql.Open` will return the DB object, which can create wrapper DB Conn structs.
The `snowflakeConn` is actually wrapped inside this struct, and there's a method `Raw()` that exposes the actual `snowflakeConn` as just `interface{}`

This mechanism allows us to use reflection on the interface to retrieve the unexported `internal` client field
![image](https://user-images.githubusercontent.com/71672948/111708463-21e70600-8803-11eb-82b3-780ae4c77a1a.png)


Patch:
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..22896dc
--- /dev/null
+++ b/client.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2021 Snowflake Computing Inc. All right reserved.
+
+package gosnowflake
+
+import (
+	"context"
+	"net/http"
+	"net/url"
+	"time"
+)
+
+// InternalClient is implemented by HTTPClient
+type InternalClient interface {
+	Get(context.Context, *url.URL, map[string]string, time.Duration) (*http.Response, error)
+	Post(context.Context, *url.URL, map[string]string, []byte, time.Duration, bool) (*http.Response, error)
+}
+
+type httpClient struct {
+	sr *snowflakeRestful
+}
+
+func (cli *httpClient) Get(
+	ctx context.Context,
+	url *url.URL,
+	headers map[string]string,
+	timeout time.Duration) (*http.Response, error) {
+	return cli.sr.FuncGet(ctx, cli.sr, url, headers, timeout)
+}
+
+func (cli *httpClient) Post(
+	ctx context.Context,
+	url *url.URL,
+	headers map[string]string,
+	body []byte,
+	timeout time.Duration,
+	raise4xx bool) (*http.Response, error) {
+	return cli.sr.FuncPost(ctx, cli.sr, url, headers, body, timeout, raise4xx)
+}
diff --git a/connection.go b/connection.go
index 2403cba..fe65c10 100644
--- a/connection.go
+++ b/connection.go
@@ -59,6 +59,7 @@ type snowflakeConn struct {
 	SequenceCounter uint64
 	QueryID         string
 	SQLState        string
+	internal        InternalClient
 }
 
 // isDml returns true if the statement type code is in the range of DML.
@@ -91,8 +92,8 @@ func (sc *snowflakeConn) exec(
 		DescribeOnly: describeOnly,
 		SequenceID:   counter,
 	}
-	if key := ctx.Value(MultiStatementCount); key != nil {
-		req.Parameters[string(MultiStatementCount)] = key
+	if key := ctx.Value(multiStatementCount); key != nil {
+		req.Parameters[string(multiStatementCount)] = key
 	}
 	logger.WithContext(ctx).Infof("parameters: %v", req.Parameters)
 
@@ -123,7 +124,7 @@ func (sc *snowflakeConn) exec(
 
 	headers := getHeaders()
 	if isFileTransfer(query) {
-		headers[httpHeaderUserAgent] = headerContentTypeApplicationJSON
+		headers[httpHeaderAccept] = headerContentTypeApplicationJSON
 	}
 	if serviceName, ok := sc.cfg.Params[serviceName]; ok {
 		headers[httpHeaderServiceName] = *serviceName
@@ -725,7 +726,7 @@ func getAsync(
 }
 
 func getQueryIDChan(ctx context.Context) chan<- string {
-	v := ctx.Value(QueryIDChan)
+	v := ctx.Value(queryIDChan)
 	if v == nil {
 		return nil
 	}
diff --git a/driver.go b/driver.go
index 2c369f8..70ba159 100644
--- a/driver.go
+++ b/driver.go
@@ -127,6 +127,7 @@ func (d SnowflakeDriver) OpenWithConfig(ctx context.Context, config Config) (dri
 	sc.populateSessionParameters(authData.Parameters)
 	sc.ctx = context.WithValue(sc.ctx, SFSessionIDKey, authData.SessionID)
 	sc.startHeartBeat()
+	sc.internal = &httpClient{sr: sc.rest}
 	return sc, nil
 }
 
diff --git a/statement.go b/statement.go
index 5371ad0..7370727 100644
--- a/statement.go
+++ b/statement.go
@@ -7,8 +7,6 @@ import (
 	"database/sql/driver"
 )
 
-type paramKey string
-
 type snowflakeStmt struct {
 	sc    *snowflakeConn
 	query string
diff --git a/util.go b/util.go
index 6e1e118..c61da36 100644
--- a/util.go
+++ b/util.go
@@ -15,21 +15,21 @@ import (
 type contextKey string
 
 const (
-	// MultiStatementCount controls the number of queries to execute in a single API call
-	MultiStatementCount paramKey = "MULTI_STATEMENT_COUNT"
-	// AsyncMode tells the server to not block the request on executing the entire query
-	asyncMode paramKey = "ASYNC_MODE_QUERY"
-	// QueryIDChan is the channel to receive the query ID from
-	QueryIDChan paramKey = "QUERY_ID_CHANNEL"
-	// SnowflakeRequestIDKey is optional context key to specify request id
-	SnowflakeRequestIDKey contextKey = "SNOWFLAKE_REQUEST_ID"
+	// multiStatementCount controls the number of queries to execute in a single API call
+	multiStatementCount contextKey = "MULTI_STATEMENT_COUNT"
+	// asyncMode tells the server to not block the request on executing the entire query
+	asyncMode contextKey = "ASYNC_MODE_QUERY"
+	// queryIDChan is the channel to receive the query ID from
+	queryIDChan 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 paramKey = "STREAM_CHUNK_DOWNLOAD"
+	streamChunkDownload contextKey = "STREAM_CHUNK_DOWNLOAD"
 )
 
 // WithMultiStatement returns a context that allows the user to execute the desired number of sql queries in one query
 func WithMultiStatement(ctx context.Context, num int) (context.Context, error) {
-	return context.WithValue(ctx, MultiStatementCount, num), nil
+	return context.WithValue(ctx, multiStatementCount, num), nil
 }
 
 // WithAsyncMode returns a context that allows execution of query in async mode
@@ -39,12 +39,12 @@ func WithAsyncMode(ctx context.Context) context.Context {
 
 // WithQueryIDChan returns a context that contains the channel to receive the query ID
 func WithQueryIDChan(ctx context.Context, c chan<- string) context.Context {
-	return context.WithValue(ctx, QueryIDChan, c)
+	return context.WithValue(ctx, queryIDChan, c)
 }
 
 // WithRequestID returns a new context with the specified snowflake request id
 func WithRequestID(ctx context.Context, requestID uuid.UUID) context.Context {
-	return context.WithValue(ctx, SnowflakeRequestIDKey, requestID)
+	return context.WithValue(ctx, snowflakeRequestIDKey, requestID)
 }
 
 // WithStreamDownloader returns a context that allows the use of a stream based chunk downloader
@@ -54,7 +54,7 @@ func WithStreamDownloader(ctx context.Context) context.Context {
 
 // 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)
+	requestID, ok := ctx.Value(snowflakeRequestIDKey).(uuid.UUID)
 	if ok && requestID != uuid.Nil {
 		return requestID
 	}


Test patch:
diff --git a/client_test.go b/client_test.go
new file mode 100644
index 0000000..bac1b04
--- /dev/null
+++ b/client_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2021 Snowflake Computing Inc. All right reserved.
+
+package gosnowflake
+
+import (
+	"context"
+	"net/http"
+	"net/url"
+	"testing"
+)
+
+type DummyTransport struct {
+	postRequests int
+	getRequests  int
+}
+
+func (t *DummyTransport) RoundTrip(r *http.Request) (*http.Response, error) {
+	if r.URL.Path == "" {
+		if r.Method == "GET" {
+			t.getRequests++
+		} else if r.Method == "POST" {
+			t.postRequests++
+		}
+		return &http.Response{StatusCode: 200}, nil
+	}
+	return snowflakeInsecureTransport.RoundTrip(r)
+}
+
+func TestInternalClient(t *testing.T) {
+	config, err := ParseDSN(dsn)
+	if err != nil {
+		t.Fatalf("failed to parse dsn. dsn: %v, err: %v", dsn, err)
+	}
+	transport := DummyTransport{}
+	config.Transporter = &transport
+	driver := SnowflakeDriver{}
+	db, err := driver.OpenWithConfig(context.Background(), *config)
+	if err != nil {
+		t.Fatalf("failed to open with config. config: %v, err: %v", config, err)
+	}
+
+	internalClient := (db.(*snowflakeConn)).internal
+	resp, err := internalClient.Get(context.Background(), &url.URL{}, make(map[string]string), 0)
+	if err != nil || resp.StatusCode != 200 {
+		t.Fail()
+	}
+	if transport.getRequests != 1 {
+		t.Fatalf("Expected exactly one GET request, got %v", transport.getRequests)
+	}
+
+	resp, err = internalClient.Post(context.Background(), &url.URL{}, make(map[string]string), make([]byte, 0), 0, false)
+	if err != nil || resp.StatusCode != 200 {
+		t.Fail()
+	}
+	if transport.postRequests != 1 {
+		t.Fatalf("Expected exactly one POST request, got %v", transport.postRequests)
+	}
+
+	db.Close()
+}