Repository: firebolt-db/firebolt-go-sdk
Pull request #130: feat: FIR-43725 add streaming to go sdk
Author: login: stepansergeevitch
Created: 2025-03-03T11:57:28Z | Merged: 2025-03-05T15:08:59Z

Added streaming option to Go SDK. In order to stream, users need to pass `streaming=true` in the context manually, or just wrap it with `context.WithStreaming` wrapper


Diff:
diff --git a/README.md b/README.md
index 327cca3b..5c5fe343 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,19 @@ Firebolt GO driver is an implementation of `database/sql/driver`.
 go get github.com/firebolt-db/firebolt-go-sdk
 ```
 
-### Example
+### DSN (Data source name)
+All information for the connection should be specified using the DSN string. The firebolt dsn string has the following format:
+```
+firebolt://[/database]?account_name=account_name&client_id=client_id&client_secret=client_secret[&engine=engine]
+```
+
+- **client_id** - credentials client id.
+- **client_secret** - credentials client secret.
+- **account_name** - the name of Firebolt account to log in to.
+- **database** - (optional) the name of the database to connect to.
+- **engine** - (optional) the name of the engine to run SQL on.
+
+### Querying example
 Here is an example of establishing a connection and executing a simple select query.
 For it to run successfully, you have to specify your credentials, and have a default engine up and running.
 
@@ -24,44 +36,44 @@ package main
 import (
 	"database/sql"
 	"fmt"
-	// we need to import firebolt-go-sdk, so it is able to register its driver
+	// we need to import firebolt-go-sdk in order to register the driver
 	_ "github.com/firebolt-db/firebolt-go-sdk"
 )
 
 func main() {
 
-	// constructing a dsn string, you need to set your credentials
+	// set your Firebolt credentials to construct a dsn string
 	clientId := ""
 	clientSecret := ""
 	accountName := ""
 	databaseName := ""
 	dsn := fmt.Sprintf("firebolt:///%s?account_name=%s&client_id=%s&client_secret=%s", databaseName, accountName, clientId, clientSecret)
 
-	// opening the firebolt driver
+	// open a Firebolt connection
 	db, err := sql.Open("firebolt", dsn)
 	if err != nil {
 		fmt.Printf("error during opening a driver: %v", err)
 	}
 
-	// Create table
+	// create a table
 	_, err = db.Query("CREATE TABLE test_table(id INT, value TEXT)")
 	if err != nil {
 		fmt.Printf("error during select query: %v", err)
 	}
 
-	// Parametrized insert (only ? placeholders are supported)
+	// execute a parametrized insert (only ? placeholders are supported)
 	_, err = db.Query("INSERT INTO test_table VALUES (?, ?)", 1, "my value")
 	if err != nil {
 		fmt.Printf("error during select query: %v", err)
 	}
 
-	// executing a simple select query
+	// execute a simple select query
 	rows, err := db.Query("SELECT id FROM test_table")
 	if err != nil {
 		fmt.Printf("error during select query: %v", err)
 	}
 
-	// iterating over the resulting rows
+	// iterate over the result
 	defer func() {
 		if err := rows.Close(); err != nil {
 			fmt.Printf("error during rows.Close(): %v\n", err)
@@ -82,18 +94,72 @@ func main() {
 }
 ```
 
+### Streaming example
+In order to stream the query result (and not store it in memory fully), you need to pass a special context with streaming enabled.
+> **Warning**: If you enable streaming the result, the query execution might finish successfully, but the actual error might be returned during the iteration over the rows.   
 
-### DSN (Data source name)
-All information for the connection should be specified using the DSN string. The firebolt dsn string has the following format:
-```
-firebolt://[/database]?account_name=account_name&client_id=client_id&client_secret=client_secret[&engine=engine]
+Here is an example of how to do it:
+
+```go
+package main
+
+import (
+    "context"
+    "database/sql"
+    "fmt"
+	
+    // we need to import firebolt-go-sdk in order to register the driver
+    _ "github.com/firebolt-db/firebolt-go-sdk"
+	fireboltContext "github.com/firebolt-db/firebolt-go-sdk/context"
+)
+
+func main() {
+	// set your Firebolt credentials to construct a dsn string
+	clientId := ""
+	clientSecret := ""
+	accountName := ""
+	databaseName := ""
+	dsn := fmt.Sprintf("firebolt:///%s?account_name=%s&client_id=%s&client_secret=%s", databaseName, accountName, clientId, clientSecret)
+
+	// open a Firebolt connection
+	db, err := sql.Open("firebolt", dsn)
+	if err != nil {
+		fmt.Printf("error during opening a driver: %v", err)
+	}
+	
+	// create a streaming context
+	streamingCtx := fireboltContext.WithStreaming(context.Background())
+
+	// execute a large select query
+	rows, err := db.QueryContext(streamingCtx, "SELECT \"abc\" FROM generate_series(1, 100000000)")
+	if err != nil {
+		fmt.Printf("error during select query: %v", err)
+	}
+
+	// iterating over the result is exactly the same as in the previous example
+	defer func() {
+		if err := rows.Close(); err != nil {
+			fmt.Printf("error during rows.Close(): %v\n", err)
+		}
+	}()
+
+	for rows.Next() {
+		var id int
+		if err := rows.Scan(&id); err != nil {
+			fmt.Printf("error during scan: %v", err)
+		}
+		fmt.Println(id)
+	}
+
+	if err := rows.Err(); err != nil {
+		fmt.Printf("error during rows iteration: %v\n", err)
+	}
+}
 ```
 
-- **client_id** - credentials client id.
-- **client_secret** - credentials client secret.
-- **account_name** - the name of Firebolt account to log in to.
-- **database** - (optional) the name of the database to connect to.
-- **engine** - (optional) the name of the engine to run SQL on.
+#### Errors in streaming
+If you enable streaming the result, the query execution might finish successfully, but the actual error might be returned during the iteration over the rows.
+
 
 ### Limitations
 Although, all interfaces are available, not all of them are implemented or could be implemented:
diff --git a/client/auth.go b/client/auth.go
index d406e9a3..cef2aa35 100644
--- a/client/auth.go
+++ b/client/auth.go
@@ -62,8 +62,13 @@ func getAccessTokenUsernamePassword(username string, password string, apiEndpoin
 			return "", errors.ConstructNestedError("authentication request failed", resp.err)
 		}
 
+		content, err := resp.Content()
+		if err != nil {
+			return "", errors.ConstructNestedError("error during reading response content", err)
+		}
+
 		var authResp AuthenticationResponse
-		if err = jsonStrictUnmarshall(resp.data, &authResp); err != nil {
+		if err = jsonStrictUnmarshall(content, &authResp); err != nil {
 			return "", errors.ConstructNestedError("failed to unmarshal authentication response with error", err)
 		}
 		logging.Infolog.Printf("Authentication was successful")
@@ -111,8 +116,13 @@ func getAccessTokenServiceAccount(clientId string, clientSecret string, apiEndpo
 			return "", errors.ConstructNestedError("authentication request failed", resp.err)
 		}
 
+		content, err := resp.Content()
+		if err != nil {
+			return "", errors.ConstructNestedError("error during reading response content", err)
+		}
+
 		var authResp AuthenticationResponse
-		if err = jsonStrictUnmarshall(resp.data, &authResp); err != nil {
+		if err = jsonStrictUnmarshall(content, &authResp); err != nil {
 			return "", errors.ConstructNestedError("failed to unmarshal authentication response with error", err)
 		}
 		logging.Infolog.Printf("Authentication was successful")
diff --git a/client/client.go b/client/client.go
index c8086cd4..c92baefd 100644
--- a/client/client.go
+++ b/client/client.go
@@ -6,6 +6,8 @@ import (
 	"errors"
 	"fmt"
 
+	contextUtils "github.com/firebolt-db/firebolt-go-sdk/context"
+
 	"github.com/firebolt-db/firebolt-go-sdk/types"
 
 	errorUtils "github.com/firebolt-db/firebolt-go-sdk/errors"
@@ -50,6 +52,7 @@ func MakeClient(settings *types.FireboltSettings, apiEndpoint string) (*ClientIm
 			ClientSecret: settings.ClientSecret,
 			ApiEndpoint:  apiEndpoint,
 			UserAgent:    ConstructUserAgentString(),
+			newVersion:   settings.NewVersion,
 		},
 		AccountName: settings.AccountName,
 	}
@@ -106,9 +109,14 @@ func (c *ClientImpl) getSystemEngineURLAndParameters(ctx context.Context, accoun
 		return "", nil, errorUtils.ConstructNestedError("error during system engine url http request", resp.err)
 	}
 
+	content, err := resp.Content()
+	if err != nil {
+		return "", nil, errorUtils.ConstructNestedError("error during reading response content", err)
+	}
+
 	var systemEngineURLResponse SystemEngineURLResponse
-	if err := json.Unmarshal(resp.data, &systemEngineURLResponse); err != nil {
-		return "", nil, errorUtils.ConstructNestedError("error during unmarshalling system engine URL Response", errors.New(string(resp.data)))
+	if err := json.Unmarshal(content, &systemEngineURLResponse); err != nil {
+		return "", nil, errorUtils.ConstructNestedError("error during unmarshalling system engine URL response", errors.New(string(content)))
 	}
 	if URLCache != nil {
 		URLCache.Put(url, systemEngineURLResponse, 0) //nolint:errcheck
@@ -123,8 +131,15 @@ func (c *ClientImpl) getSystemEngineURLAndParameters(ctx context.Context, accoun
 	return engineUrl, parameters, nil
 }
 
-func (c *ClientImpl) GetQueryParams(setStatements map[string]string) (map[string]string, error) {
-	params := map[string]string{"output_format": outputFormat}
+func (c *ClientImpl) getOutputFormat(ctx context.Context) string {
+	if contextUtils.IsStreaming(ctx) {
+		return jsonLinesOutputFormat
+	}
+	return jsonOutputFormat
+}
+
+func (c *ClientImpl) GetQueryParams(ctx context.Context, setStatements map[string]string) (map[string]string, error) {
+	params := map[string]string{"output_format": c.getOutputFormat(ctx)}
 	for setKey, setValue := range setStatements {
 		params[setKey] = setValue
 	}
diff --git a/client/client_base.go b/client/client_base.go
index 72a72e29..af7abcd1 100644
--- a/client/client_base.go
+++ b/client/client_base.go
@@ -2,7 +2,6 @@ package client
 
 import (
 	"context"
-	"encoding/json"
 	"errors"
 	"net/http"
 	"net/url"
@@ -12,10 +11,10 @@ import (
 
 	errorUtils "github.com/firebolt-db/firebolt-go-sdk/errors"
 	"github.com/firebolt-db/firebolt-go-sdk/logging"
-	"github.com/firebolt-db/firebolt-go-sdk/types"
 )
 
-const outputFormat = "JSON_Compact"
+const jsonOutputFormat = "JSON_Compact"
+const jsonLinesOutputFormat = "JSONLines_Compact"
 const protocolVersionHeader = "Firebolt-Protocol-Version"
 const protocolVersion = "2.1"
 
@@ -27,7 +26,8 @@ var allowedUpdateParameters = []string{"database"}
 
 type Client interface {
 	GetConnectionParameters(ctx context.Context, engineName string, databaseName string) (string, map[string]string, error)
-	Query(ctx context.Context, engineUrl, query string, parameters map[string]string, control ConnectionControl) (*types.QueryResponse, error)
+	Query(ctx context.Context, engineUrl, query string, parameters map[string]string, control ConnectionControl) (*Response, error)
+	IsNewVersion() bool
 }
 
 type BaseClient struct {
@@ -35,8 +35,9 @@ type BaseClient struct {
 	ClientSecret      string
 	ApiEndpoint       string
 	UserAgent         string
-	ParameterGetter   func(map[string]string) (map[string]string, error)
+	ParameterGetter   func(context.Context, map[string]string) (map[string]string, error)
 	AccessTokenGetter func() (string, error)
+	newVersion        bool
 }
 
 // ConnectionControl is a struct that holds methods for updating connection properties
@@ -47,14 +48,19 @@ type ConnectionControl struct {
 	SetEngineURL     func(string)
 }
 
+// IsNewVersion returns true if the client is using the 2.0 version of Firebolt
+func (c *BaseClient) IsNewVersion() bool {
+	return c.newVersion
+}
+
 // Query sends a query to the engine URL and populates queryResponse, if query was successful
-func (c *BaseClient) Query(ctx context.Context, engineUrl, query string, parameters map[string]string, control ConnectionControl) (*types.QueryResponse, error) {
+func (c *BaseClient) Query(ctx context.Context, engineUrl, query string, parameters map[string]string, control ConnectionControl) (*Response, error) {
 	logging.Infolog.Printf("Query engine '%s' with '%s'", engineUrl, query)
 
 	if c.ParameterGetter == nil {
 		return nil, errors.New("ParameterGetter is not set")
 	}
-	params, err := c.ParameterGetter(parameters)
+	params, err := c.ParameterGetter(ctx, parameters)
 	if err != nil {
 		return nil, err
 	}
@@ -67,19 +73,7 @@ func (c *BaseClient) Query(ctx context.Context, engineUrl, query string, paramet
 	if err = c.processResponseHeaders(resp.headers, control); err != nil {
 		return nil, errorUtils.ConstructNestedError("error during processing response headers", err)
 	}
-
-	var queryResponse types.QueryResponse
-	if len(resp.data) == 0 {
-		// response could be empty, which doesn't mean it is an error
-		return &queryResponse, nil
-	}
-
-	if err = json.Unmarshal(resp.data, &queryResponse); err != nil {
-		return nil, errorUtils.ConstructNestedError("wrong Response", errors.New(string(resp.data)))
-	}
-
-	logging.Infolog.Printf("Query was successful")
-	return &queryResponse, nil
+	return resp, nil
 }
 
 func handleUpdateParameters(updateParameters func(string, string), updateParametersRaw string) {
@@ -111,7 +105,7 @@ func splitEngineEndpoint(endpoint string) (string, url.Values, error) {
 }
 
 func (c *BaseClient) handleUpdateEndpoint(updateEndpointRaw string, control ConnectionControl) error {
-	// split URL containted into updateEndpointRaw into endpoint and parameters
+	// split URL contained into updateEndpointRaw into endpoint and parameters
 	// Update parameters and set client engine endpoint
 
 	corruptUrlError := errors.New("failed to execute USE ENGINE command: corrupt update endpoint - contact support")
@@ -145,18 +139,18 @@ func (c *BaseClient) processResponseHeaders(headers http.Header, control Connect
 	return nil
 }
 
-// request fetches an access token from the cache or re-authenticate when the access token is not available in the cache
+// requestWithAuthRetry fetches an access token from the cache or re-authenticate when the access token is not available in the cache
 // and sends a request using that token
-func (c *BaseClient) requestWithAuthRetry(ctx context.Context, method string, url string, params map[string]string, bodyStr string) Response {
+func (c *BaseClient) requestWithAuthRetry(ctx context.Context, method string, url string, params map[string]string, bodyStr string) *Response {
 	var err error
 
 	if c.AccessTokenGetter == nil {
-		return Response{nil, 0, nil, errors.New("AccessTokenGetter is not set")}
+		return MakeResponse(nil, 0, nil, errors.New("AccessTokenGetter is not set"))
 	}
 
 	accessToken, err := c.AccessTokenGetter()
 	if err != nil {
-		return Response{nil, 0, nil, errorUtils.ConstructNestedError("error while getting access token", err)}
+		return MakeResponse(nil, 0, nil, errorUtils.ConstructNestedError("error while getting access token", err))
 	}
 	resp := DoHttpRequest(requestParameters{ctx, accessToken, method, url, c.UserAgent, params, bodyStr, ContentTypeJSON})
 	if resp.statusCode == http.StatusUnauthorized {
@@ -165,7 +159,7 @@ func (c *BaseClient) requestWithAuthRetry(ctx context.Context, method string, ur
 		// Refreshing the access token as it is expired
 		accessToken, err = c.AccessTokenGetter()
 		if err != nil {
-			return Response{nil, 0, nil, errorUtils.ConstructNestedError("error while getting access token", err)}
+			return MakeResponse(nil, 0, nil, errorUtils.ConstructNestedError("error while getting access token", err))
 		}
 		// Trying to send the same request again now that the access token has been refreshed
 		resp = DoHttpRequest(requestParameters{ctx, accessToken, method, url, c.UserAgent, params, bodyStr, ContentTypeJSON})
diff --git a/client/client_common_test.go b/client/client_common_test.go
index 05b6d411..a011d897 100644
--- a/client/client_common_test.go
+++ b/client/client_common_test.go
@@ -7,6 +7,8 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	contextUtils "github.com/firebolt-db/firebolt-go-sdk/context"
+
 	"github.com/firebolt-db/firebolt-go-sdk/utils"
 )
 
@@ -88,7 +90,7 @@ func testAdditionalHeaders(t *testing.T, clientFactory func(string) Client) {
 	prepareEnvVariablesForTest(t, server)
 	client := clientFactory(server.URL)
 
-	ctx := context.WithValue(context.TODO(), ContextKey("additionalHeaders"), additionalHeaders)
+	ctx := contextUtils.WithAdditionalHeaders(context.Background(), additionalHeaders)
 
 	_, _ = client.Query(ctx, server.URL, selectOne, map[string]string{}, ConnectionControl{})
 
diff --git a/client/client_integration_common_test.go b/client/client_integration_common_test.go
new file mode 100644
index 00000000..1f3770a0
--- /dev/null
+++ b/client/client_integration_common_test.go
@@ -0,0 +1,87 @@
+//go:build integration || integration_v0
+// +build integration integration_v0
+
+package client
+
+import (
+	"context"
+	"encoding/json"
+	errorUtils "errors"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/firebolt-db/firebolt-go-sdk/errors"
+	"github.com/firebolt-db/firebolt-go-sdk/types"
+)
+
+func parseResponse(t *testing.T, response *Response, expectedValue interface{}) (*types.QueryResponse, error) {
+	content, err := response.Content()
+	if err != nil {
+		return nil, fmt.Errorf("Error getting response content: %v", err)
+	}
+
+	// Unmarshal the response content
+	var queryResponse types.QueryResponse
+	// Response could be empty, which doesn't mean it is an error
+	if len(content) != 0 {
+		if err = json.Unmarshal(content, &queryResponse); err != nil {
+			return nil, errors.ConstructNestedError("wrong response", errorUtils.New(string(content)))
+		}
+	}
+	return &queryResponse, nil
+
+}
+
+// TestQuery tests simple query
+func TestQuery(t *testing.T) {
+	response, err := clientMock.Query(context.TODO(), engineUrlMock, "SELECT 1", map[string]string{"database": databaseMock}, ConnectionControl{})
+	if err != nil {
+		t.Errorf("Query returned an error: %v", err)
+		t.FailNow()
+	}
+	queryResponse, err := parseResponse(t, response, 1)
+	if err != nil {
+		t.Errorf("Error parsing response: %v", err)
+		t.FailNow()
+	}
+	if queryResponse.Rows != 1 {
+		t.Errorf("Query response has an invalid number of rows %d != %d", queryResponse.Rows, 1)
+		t.FailNow()
+	}
+
+	if queryResponse.Data[0][0].(float64) != 1 {
+		t.Errorf("queryResponse data is not correct")
+	}
+}
+
+// TestQuery with set statements
+func TestQuerySetStatements(t *testing.T) {
+	query := "SELECT '2024-01-01 00:00:00'::timestamptz"
+	response, err := clientMock.Query(
+		context.TODO(),
+		engineUrlMock,
+		query,
+		map[string]string{"timezone": "America/New_York", "database": databaseMock},
+		ConnectionControl{},
+	)
+	if err != nil {
+		t.Errorf("Query returned an error: %v", err)
+		t.FailNow()
+	}
+
+	queryResponse, err := parseResponse(t, response, 1)
+	if err != nil {
+		t.Errorf("Error parsing response: %v", err)
+		t.FailNow()
+	}
+
+	date, err := time.Parse("2006-01-02 15:04:05-07", queryResponse.Data[0][0].(string))
+	if err != nil {
+		t.Errorf("Error parsing date: %v", err)
+		t.FailNow()
+	}
+	if date.UTC().Hour() != 5 {
+		t.Errorf("Invalid date returned: %s", date)
+	}
+}
diff --git a/client/client_integration_test.go b/client/client_integration_test.go
index 57c9f23f..5f469587 100644
--- a/client/client_integration_test.go
+++ b/client/client_integration_test.go
@@ -8,11 +8,8 @@ import (
 	"fmt"
 	"os"
 	"testing"
-	"time"
 
 	"github.com/firebolt-db/firebolt-go-sdk/types"
-
-	"github.com/firebolt-db/firebolt-go-sdk/rows"
 )
 
 var (
@@ -77,41 +74,3 @@ func TestGetEnginePropsByName(t *testing.T) {
 		t.Errorf("Empty system engine url returned by getSystemEngineURL for account: %s", accountName)
 	}
 }
-
-// TestQuery tests simple query
-func TestQuery(t *testing.T) {
-	queryResponse, err := clientMock.Query(context.TODO(), engineUrlMock, "SELECT 1", map[string]string{"database": databaseMock}, ConnectionControl{})
-	if err != nil {
-		t.Errorf("Query returned an error: %v", err)
-	}
-	if queryResponse.Rows != 1 {
-		t.Errorf("Query response has an invalid number of rows %d != %d", queryResponse.Rows, 1)
-	}
-
-	if queryResponse.Data[0][0].(float64) != 1 {
-		t.Errorf("queryResponse data is not correct")
-	}
-}
-
-// TestQuery with set statements
-func TestQuerySetStatements(t *testing.T) {
-	query := "SELECT '2024-01-01 00:00:00'::timestamptz"
-	resp, err := clientMock.Query(
-		context.TODO(),
-		engineUrlMock,
-		query,
-		map[string]string{"timezone": "America/New_York", "database": databaseMock},
-		ConnectionControl{},
-	)
-	if err != nil {
-		t.Errorf("Query returned an error: %v", err)
-	}
-
-	date, err := rows.ParseTimestampTz(resp.Data[0][0].(string))
-	if err != nil {
-		t.Errorf("Error parsing date: %v", err)
-	}
-	if date.(time.Time).UTC().Hour() != 5 {
-		t.Errorf("Invalid date returned: %s", date)
-	}
-}
diff --git a/client/client_integration_v0_test.go b/client/client_integration_v0_test.go
index 003efaa3..12c91254 100644
--- a/client/client_integration_v0_test.go
+++ b/client/client_integration_v0_test.go
@@ -8,9 +8,7 @@ import (
 	"fmt"
 	"os"
 	"testing"
-	"time"
 
-	"github.com/firebolt-db/firebolt-go-sdk/rows"
 	"github.com/firebolt-db/firebolt-go-sdk/types"
 )
 
@@ -120,44 +118,6 @@ func TestGetEngineUrlByDatabase(t *testing.T) {
 	}
 }
 
-// TestQuery tests simple query
-func TestQuery(t *testing.T) {
-	queryResponse, err := clientMock.Query(context.TODO(), engineUrlMock, "SELECT 1", map[string]string{"database": databaseMock}, ConnectionControl{})
-	if err != nil {
-		t.Errorf("Query returned an error: %v", err)
-	}
-	if queryResponse.Rows != 1 {
-		t.Errorf("Query response has an invalid number of rows %d != %d", queryResponse.Rows, 1)
-	}
-
-	if queryResponse.Data[0][0].(float64) != 1 {
-		t.Errorf("queryResponse data is not correct")
-	}
-}
-
-// TestQuery with set statements
-func TestQuerySetStatements(t *testing.T) {
-	query := "SELECT '2024-01-01 00:00:00'::timestamptz"
-	resp, err := clientMock.Query(
-		context.TODO(),
-		engineUrlMock,
-		query,
-		map[string]string{"time_zone": "America/New_York", "database": databaseMock},
-		ConnectionControl{},
-	)
-	if err != nil {
-		t.Errorf("Query returned an error: %v", err)
-	}
-
-	date, err := rows.ParseTimestampTz(resp.Data[0][0].(string))
-	if err != nil {
-		t.Errorf("Error parsing date: %v", err)
-	}
-	if date.(time.Time).UTC().Hour() != 5 {
-		t.Errorf("Invalid date returned: %s", date)
-	}
-}
-
 func TestGetDefaultAccountID(t *testing.T) {
 	var defaultAccountId, accountIdFromName string
 	var err error
diff --git a/client/client_v0.go b/client/client_v0.go
index a1e741db..574fb825 100644
--- a/client/client_v0.go
+++ b/client/client_v0.go
@@ -25,6 +25,7 @@ func MakeClientV0(settings *types.FireboltSettings, apiEndpoint string) (*Client
 			ClientSecret: settings.ClientSecret,
 			ApiEndpoint:  apiEndpoint,
 			UserAgent:    ConstructUserAgentString(),
+			newVersion:   settings.NewVersion,
 		},
 	}
 	client.ParameterGetter = client.getQueryParams
@@ -53,9 +54,14 @@ func (c *ClientImplV0) getAccountIDByName(ctx context.Context, accountName strin
 		return "", errorUtils.ConstructNestedError("error during getting account id by name request", resp.err)
 	}
 
+	content, err := resp.Content()
+	if err != nil {
+		return "", errorUtils.ConstructNestedError("error during reading response content", err)
+	}
+
 	var accountIdByNameResponse AccountIdByNameResponse
-	if err := json.Unmarshal(resp.data, &accountIdByNameResponse); err != nil {
-		return "", errorUtils.ConstructNestedError("error during unmarshalling account id by name Response", errors.New(string(resp.data)))
+	if err := json.Unmarshal(content, &accountIdByNameResponse); err != nil {
+		return "", errorUtils.ConstructNestedError("error during unmarshalling account id by name response", errors.New(string(content)))
 	}
 	return accountIdByNameResponse.AccountId, nil
 }
@@ -75,9 +81,14 @@ func (c *ClientImplV0) getDefaultAccountID(ctx context.Context) (string, error)
 		return "", errorUtils.ConstructNestedError("error during getting default account id request", resp.err)
 	}
 
+	content, err := resp.Content()
+	if err != nil {
+		return "", errorUtils.ConstructNestedError("error during reading response content", err)
+	}
+
 	var defaultAccountResponse DefaultAccountResponse
-	if err := json.Unmarshal(resp.data, &defaultAccountResponse); err != nil {
-		return "", errorUtils.ConstructNestedError("error during unmarshalling default account Response", errors.New(string(resp.data)))
+	if err := json.Unmarshal(content, &defaultAccountResponse); err != nil {
+		return "", errorUtils.ConstructNestedError("error during unmarshalling default account response", errors.New(string(content)))
 	}
 
 	return defaultAccountResponse.Account.Id, nil
@@ -116,9 +127,14 @@ func (c *ClientImplV0) getEngineIdByName(ctx context.Context, engineName string,
 		return "", errorUtils.ConstructNestedError("error during getting engine id by name request", resp.err)
 	}
 
+	content, err := resp.Content()
+	if err != nil {
+		return "", errorUtils.ConstructNestedError("error during reading response content", err)
+	}
+
 	var engineIdByNameResponse EngineIdByNameResponse
-	if err := json.Unmarshal(resp.data, &engineIdByNameResponse); err != nil {
-		return "", errorUtils.ConstructNestedError("error during unmarshalling engine id by name Response", errors.New(string(resp.data)))
+	if err := json.Unmarshal(content, &engineIdByNameResponse); err != nil {
+		return "", errorUtils.ConstructNestedError("error during unmarshalling engine id by name response", errors.New(string(content)))
 	}
 	return engineIdByNameResponse.EngineId.EngineId, nil
 }
@@ -140,9 +156,14 @@ func (c *ClientImplV0) getEngineUrlById(ctx context.Context, engineId string, ac
 		return "", errorUtils.ConstructNestedError("error during getting engine url by id request", resp.err)
 	}
 
+	content, err := resp.Content()
+	if err != nil {
+		return "", errorUtils.ConstructNestedError("error during reading response content", err)
+	}
+
 	var engineByIdResponse EngineByIdResponse
-	if err := json.Unmarshal(resp.data, &engineByIdResponse); err != nil {
-		return "", errorUtils.ConstructNestedError("error during unmarshalling engine url by id Response", errors.New(string(resp.data)))
+	if err := json.Unmarshal(content, &engineByIdResponse); err != nil {
+		return "", errorUtils.ConstructNestedError("error during unmarshalling engine url by id response", errors.New(string(content)))
 	}
 	return MakeCanonicalUrl(engineByIdResponse.Engine.Endpoint), nil
 }
@@ -178,9 +199,14 @@ func (c *ClientImplV0) getEngineUrlByDatabase(ctx context.Context, databaseName
 		return "", errorUtils.ConstructNestedError("error during getting engine url by database request", resp.err)
 	}
 
+	content, err := resp.Content()
+	if err != nil {
+		return "", errorUtils.ConstructNestedError("error during reading response content", err)
+	}
+
 	var engineUrlByDatabaseResponse EngineUrlByDatabaseResponse
-	if err := json.Unmarshal(resp.data, &engineUrlByDatabaseResponse); err != nil {
-		return "", errorUtils.ConstructNestedError("error during unmarshalling engine url by database Response", errors.New(string(resp.data)))
+	if err := json.Unmarshal(content, &engineUrlByDatabaseResponse); err != nil {
+		return "", errorUtils.ConstructNestedError("error during unmarshalling engine url by database response", errors.New(string(content)))
 	}
 	return engineUrlByDatabaseResponse.EngineUrl, nil
 }
@@ -209,8 +235,8 @@ func (c *ClientImplV0) GetConnectionParameters(ctx context.Context, engineName,
 
 }
 
-func (c *ClientImplV0) getQueryParams(setStatements map[string]string) (map[string]string, error) {
-	params := map[string]string{"output_format": outputFormat}
+func (c *ClientImplV0) getQueryParams(_ context.Context, setStatements map[string]string) (map[string]string, error) {
+	params := map[string]string{"output_format": jsonOutputFormat}
 	for setKey, setValue := range setStatements {
 		params[setKey] = setValue
 	}
diff --git a/client/http_client.go b/client/http_client.go
index ebf57397..c1c0c0df 100644
--- a/client/http_client.go
+++ b/client/http_client.go
@@ -9,18 +9,52 @@ import (
 	"net/http"
 	"strings"
 
+	contextUtils "github.com/firebolt-db/firebolt-go-sdk/context"
+
 	errorUtils "github.com/firebolt-db/firebolt-go-sdk/errors"
 	"github.com/firebolt-db/firebolt-go-sdk/logging"
-	"github.com/firebolt-db/firebolt-go-sdk/types"
 )
 
 type Response struct {
-	data       []byte
+	body       io.ReadCloser
+	content    []byte
 	statusCode int
 	headers    http.Header
 	err        error
 }
 
+func MakeResponse(body io.ReadCloser, statusCode int, headers http.Header, err error) *Response {
+	response := &Response{body, nil, statusCode, headers, err}
+
+	if response.err == nil && !(statusCode >= 200 && statusCode < 300) {
+		if err := checkErrorResponse(response); err != nil {
+			response.err = errorUtils.ConstructNestedError("request returned an error", err)
+		} else if statusCode == 500 {
+			response.err = errors.New(string(response.content))
+		} else {
+			response.err = errorUtils.ConstructNestedError("request returned non ok status code", errors.New(string(response.content)))
+		}
+	}
+	return response
+}
+
+func (r *Response) Body() io.ReadCloser {
+	return r.body
+}
+
+func (r *Response) Content() ([]byte, error) {
+	var err error
+	if r.content == nil {
+		if r.body == nil {
+			r.content = []byte{}
+		} else {
+			defer r.body.Close()
+			r.content, err = io.ReadAll(r.body)
+		}
+	}
+	return r.content, err
+}
+
 // Collect arguments for request function
 type requestParameters struct {
 	ctx         context.Context
@@ -33,11 +67,9 @@ type requestParameters struct {
 	contentType string
 }
 
-type ContextKey string
-
-// checkErrorResponse, checks whether error response is returned instead of a desired Response.
-func checkErrorResponse(response []byte) error {
-	// ErrorResponse definition of any response with some error
+// checkErrorResponse, checks whether error Response is returned instead of a desired Response.
+func checkErrorResponse(response *Response) error {
+	// ErrorResponse definition of any Response with some error
 	type ErrorResponse struct {
 		Error   string        `json:"error"`
 		Code    int           `json:"code"`
@@ -45,10 +77,15 @@ func checkErrorResponse(response []byte) error {
 		Details []interface{} `json:"details"`
 	}
 
+	content, err := response.Content()
+	if err != nil {
+		return errorUtils.ConstructNestedError("error during reading response from the server", err)
+	}
+
 	var errorResponse ErrorResponse
 
-	if err := json.Unmarshal(response, &errorResponse); err == nil && errorResponse.Code != 0 {
-		// return error only if error response was
+	if err := json.Unmarshal(content, &errorResponse); err == nil && errorResponse.Code != 0 {
+		// return error only if error Response was
 		// unmarshalled correctly and error code is not zero
 		return errors.New(errorResponse.Message)
 	}
@@ -56,7 +93,7 @@ func checkErrorResponse(response []byte) error {
 }
 
 func extractAdditionalHeaders(ctx context.Context) map[string]string {
-	additionalHeaders, ok := ctx.Value(ContextKey("additionalHeaders")).(map[string]string)
+	additionalHeaders, ok := contextUtils.GetAdditionalHeaders(ctx)
 	if ok {
 		// only take headers that start with Firebolt- prefix
 		filteredHeaders := make(map[string]string)
@@ -70,11 +107,11 @@ func extractAdditionalHeaders(ctx context.Context) map[string]string {
 	return map[string]string{}
 }
 
-// request sends a request using "POST" or "GET" method on a specified url
+// DoHttpRequest sends a request using "POST" or "GET" method on a specified url
 // additionally it passes the parameters and a bodyStr as a payload
 // if accessToken is passed, it is used for authorization
-// returns response and an error
-func DoHttpRequest(reqParams requestParameters) Response {
+// returns Response struct
+func DoHttpRequest(reqParams requestParameters) *Response {
 	req, _ := http.NewRequestWithContext(reqParams.ctx, reqParams.method, MakeCanonicalUrl(reqParams.url), strings.NewReader(reqParams.bodyStr))
 
 	// adding sdk usage tracking
@@ -107,17 +144,11 @@ func DoHttpRequest(reqParams requestParameters) Response {
 	resp, err := client.Do(req)
 	if err != nil {
 		logging.Infolog.Println(err)
-		return Response{nil, 0, nil, errorUtils.ConstructNestedError("error during a request execution", err)}
+		return MakeResponse(nil, 0, nil, errorUtils.ConstructNestedError("error during a request execution", err))
 	}
 
-	defer resp.Body.Close()
-
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		logging.Infolog.Println(err)
-		return Response{nil, 0, nil, errorUtils.ConstructNestedError("error during reading a request Response", err)}
-	}
-	// Error might be in the response body, despite the status code 200
+	/*defer resp.Body.Close()
+	// Error might be in the Response body, despite the status code 200
 	errorResponse := struct {
 		Errors []types.ErrorDetails `json:"errors"`
 	}{}
@@ -125,20 +156,8 @@ func DoHttpRequest(reqParams requestParameters) Response {
 		if errorResponse.Errors != nil {
 			return Response{nil, resp.StatusCode, nil, errorUtils.NewStructuredError(errorResponse.Errors)}
 		}
-	}
-
-	if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
-		if err = checkErrorResponse(body); err != nil {
-			return Response{nil, resp.StatusCode, nil, errorUtils.ConstructNestedError("request returned an error", err)}
-		}
-		if resp.StatusCode == 500 {
-			// this is a database error
-			return Response{nil, resp.StatusCode, nil, fmt.Errorf("%s", string(body))}
-		}
-		return Response{nil, resp.StatusCode, nil, fmt.Errorf("request returned non ok status code: %d, %s", resp.StatusCode, string(body))}
-	}
-
-	return Response{body, resp.StatusCode, resp.Header, nil}
+	}*/
+	return MakeResponse(resp.Body, resp.StatusCode, resp.Header, nil)
 }
 
 // MakeCanonicalUrl checks whether url starts with https:// and if not prepends it
diff --git a/client/mock_client.go b/client/mock_client.go
index e90594bd..3dce09c6 100644
--- a/client/mock_client.go
+++ b/client/mock_client.go
@@ -2,8 +2,6 @@ package client
 
 import (
 	"context"
-
-	"github.com/firebolt-db/firebolt-go-sdk/types"
 )
 
 // MockClient rudimentary mocks Client and tracks the parameters passed to Query
@@ -20,7 +18,7 @@ func MakeMockClientWithError(errorToRaise error) *MockClient {
 	return &MockClient{errorToRaise: errorToRaise}
 }
 
-func (m *MockClient) Query(ctx context.Context, engineUrl, query string, parameters map[string]string, control ConnectionControl) (*types.QueryResponse, error) {
+func (m *MockClient) Query(ctx context.Context, engineUrl, query string, parameters map[string]string, control ConnectionControl) (*Response, error) {
 	m.ParametersCalled = append(m.ParametersCalled, parameters)
 	return nil, m.errorToRaise
 }
@@ -29,3 +27,8 @@ func (m *MockClient) GetConnectionParameters(ctx context.Context, engineName str
 	// Implement to satisfy Client interface
 	return "", nil, nil
 }
+
+func (m *MockClient) IsNewVersion() bool {
+	// It's only used now to determine if we can do streaming, so we can return true
+	return true
+}
diff --git a/connection.go b/connection.go
index df549a32..6db7c928 100644
--- a/connection.go
+++ b/connection.go
@@ -6,12 +6,13 @@ import (
 	"errors"
 	"fmt"
 
+	contextUtils "github.com/firebolt-db/firebolt-go-sdk/context"
+	"github.com/firebolt-db/firebolt-go-sdk/rows"
+
 	"github.com/firebolt-db/firebolt-go-sdk/client"
 	"github.com/firebolt-db/firebolt-go-sdk/utils"
 
 	errorUtils "github.com/firebolt-db/firebolt-go-sdk/errors"
-	"github.com/firebolt-db/firebolt-go-sdk/rows"
-	"github.com/firebolt-db/firebolt-go-sdk/types"
 )
 
 type fireboltConnection struct {
@@ -54,6 +55,14 @@ func (c *fireboltConnection) QueryContext(ctx context.Context, query string, arg
 	return c.queryContextInternal(ctx, query, args, true)
 }
 
+func (c *fireboltConnection) makeRows(ctx context.Context) rows.ExtendableRows {
+	isStreaming := contextUtils.IsStreaming(ctx)
+	if isStreaming && c.client.IsNewVersion() {
+		return &rows.StreamRows{}
+	}
+	return &rows.InMemoryRows{}
+}
+
 func (c *fireboltConnection) queryContextInternal(ctx context.Context, query string, args []driver.NamedValue, isMultiStatementAllowed bool) (driver.Rows, error) {
 	query, err := prepareStatement(query, args)
 	if err != nil {
@@ -67,28 +76,28 @@ func (c *fireboltConnection) queryContextInternal(ctx context.Context, query str
 		return nil, fmt.Errorf("multistatement is not allowed")
 	}
 
-	var rows rows.InMemoryRows
+	var rowsInst = c.makeRows(ctx)
+
 	for _, query := range queries {
 		if isSetStatement, err := processSetStatement(ctx, c, query); isSetStatement {
-			if err == nil {
-				rows.AppendResponse(types.QueryResponse{})
-				continue
-			} else {
-				return &rows, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			if err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			} else if err = rowsInst.ProcessAndAppendResponse(client.MakeResponse(nil, 200, nil, nil)); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
 			}
-		}
-
-		if response, err := c.client.Query(ctx, c.engineUrl, query, c.parameters, client.ConnectionControl{
-			UpdateParameters: c.setParameter,
-			SetEngineURL:     c.setEngineURL,
-			ResetParameters:  c.resetParameters,
-		}); err != nil {
-			return &rows, errorUtils.ConstructNestedError("error during query execution", err)
 		} else {
-			rows.AppendResponse(*response)
+			if response, err := c.client.Query(ctx, c.engineUrl, query, c.parameters, client.ConnectionControl{
+				UpdateParameters: c.setParameter,
+				SetEngineURL:     c.setEngineURL,
+				ResetParameters:  c.resetParameters,
+			}); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
+			} else if err = rowsInst.ProcessAndAppendResponse(response); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
+			}
 		}
 	}
-	return &rows, nil
+	return rowsInst, nil
 }
 
 // processSetStatement is an internal function for checking whether query is a valid set statement
diff --git a/connection_common_integration_test.go b/connection_common_integration_test.go
index b2979c76..d865ed6d 100644
--- a/connection_common_integration_test.go
+++ b/connection_common_integration_test.go
@@ -14,6 +14,8 @@ import (
 	"testing"
 	"time"
 
+	contextUtils "github.com/firebolt-db/firebolt-go-sdk/context"
+
 	"github.com/firebolt-db/firebolt-go-sdk/logging"
 	"github.com/firebolt-db/firebolt-go-sdk/utils"
 )
@@ -40,345 +42,373 @@ func init() {
 
 // TestConnectionPrepareStatement, tests that prepare statement doesn't result into an error
 func TestConnectionSetStatement(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
-
-	_, err = conn.ExecContext(context.TODO(), "SET time_zone=America/New_York")
-	utils.AssertEqual(err, nil, t, "set time_zone returned an error, but shouldn't")
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
 
-	_, err = conn.QueryContext(context.TODO(), "SELECT * FROM information_schema.tables")
-	utils.AssertEqual(err, nil, t, "query returned an error, but shouldn't")
+		_, err = conn.ExecContext(ctx, "SET time_zone=America/New_York")
+		utils.AssertEqual(err, nil, t, "set time_zone returned an error, but shouldn't")
 
+		_, err = conn.QueryContext(ctx, "SELECT * FROM information_schema.tables")
+		utils.AssertEqual(err, nil, t, "query returned an error, but shouldn't")
+	})
 }
 
 // TestConnectionQuery checks simple SELECT 1 exec
 func TestConnectionQueryWrong(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
 
-	if _, err = conn.ExecContext(context.TODO(), "SELECT wrong query"); err == nil {
-		t.Errorf("wrong statement didn't return an error")
-	}
+		if _, err = conn.ExecContext(ctx, "SELECT wrong query"); err == nil {
+			t.Errorf("wrong statement didn't return an error")
+		}
+	})
 }
 
 // TestConnectionInsertQuery checks simple Insert works
 func TestConnectionInsertQuery(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
 
-	createTableSQL := "CREATE FACT TABLE integration_tests (id INT, name STRING) PRIMARY INDEX id"
-	deleteTableSQL := "DROP TABLE IF EXISTS integration_tests"
-	insertSQL := "INSERT INTO integration_tests (id, name) VALUES (0, 'some_text')"
+		createTableSQL := "CREATE FACT TABLE integration_tests (id INT, name STRING) PRIMARY INDEX id"
+		deleteTableSQL := "DROP TABLE IF EXISTS integration_tests"
+		insertSQL := "INSERT INTO integration_tests (id, name) VALUES (0, 'some_text')"
 
-	if _, err = conn.ExecContext(context.TODO(), createTableSQL); err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
-	if _, err = conn.ExecContext(context.TODO(), insertSQL); err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
-	if _, err = conn.ExecContext(context.TODO(), deleteTableSQL); err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		if _, err = conn.ExecContext(ctx, createTableSQL); err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
+		if _, err = conn.ExecContext(ctx, insertSQL); err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
+		if _, err = conn.ExecContext(ctx, deleteTableSQL); err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
+	})
 }
 
 // TestConnectionQuery checks simple SELECT query
 func TestConnectionQuery(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
 
-	sql := "SELECT -3213212 as \"const\", 2.3 as \"float\", 'some_text' as \"text\""
-	rows, err := conn.QueryContext(context.TODO(), sql)
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		sql := "SELECT -3213212 as \"const\", 2.3 as \"float\", 'some_text' as \"text\""
+		rows, err := conn.QueryContext(ctx, sql)
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	columnNames := []string{"const", "float", "text"}
-	columns, err := rows.Columns()
-	if err != nil {
-		t.Errorf("columns returned an error, but shouldn't")
-	}
-	if !reflect.DeepEqual(columns, columnNames) {
-		t.Errorf("column lists are not equal")
-	}
+		columnNames := []string{"const", "float", "text"}
+		columns, err := rows.Columns()
+		if err != nil {
+			t.Errorf("columns returned an error, but shouldn't")
+		}
+		if !reflect.DeepEqual(columns, columnNames) {
+			t.Errorf("column lists are not equal")
+		}
 
-	var i int32
-	var f float64
-	var s string
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	err = rows.Scan(&i, &f, &s)
-	if err != nil {
-		t.Errorf("Next returned an error, but shouldn't")
-	}
-	utils.AssertEqual(i, int32(-3213212), t, "dest[0] is not equal")
-	utils.AssertEqual(f, float64(2.3), t, "dest[1] is not equal")
-	utils.AssertEqual(s, "some_text", t, "dest[2] is not equal")
+		var i int32
+		var f float64
+		var s string
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		err = rows.Scan(&i, &f, &s)
+		if err != nil {
+			t.Errorf("Next returned an error, but shouldn't: %s", err)
+			t.FailNow()
+		}
+		utils.AssertEqual(i, int32(-3213212), t, "dest[0] is not equal")
+		utils.AssertEqual(f, float64(2.3), t, "dest[1] is not equal")
+		utils.AssertEqual(s, "some_text", t, "dest[2] is not equal")
 
-	utils.AssertEqual(rows.Next(), false, t, "end of data didn't return io.EOF")
+		utils.AssertEqual(rows.Next(), false, t, "end of data didn't return io.EOF")
+	})
 }
 
 func TestConnectionQueryDate32Type(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
-	loc, _ := time.LoadLocation("UTC")
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
+		loc, _ := time.LoadLocation("UTC")
 
-	rows, err := conn.QueryContext(context.TODO(), "select '2004-07-09'::DATE")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		rows, err := conn.QueryContext(ctx, "select '2004-07-09'::DATE")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var dest time.Time
+		var dest time.Time
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&dest); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	if dest != time.Date(2004, 7, 9, 0, 0, 0, 0, loc) {
-		t.Errorf("values are not equal: %v\n", dest)
-	}
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&dest); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		if dest != time.Date(2004, 7, 9, 0, 0, 0, 0, loc) {
+			t.Errorf("values are not equal: %v\n", dest)
+		}
+	})
 }
 
 func TestConnectionQueryDecimalType(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
 
-	rows, err := conn.QueryContext(context.TODO(), "SELECT cast (123.23 as NUMERIC (12,6))")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		rows, err := conn.QueryContext(ctx, "SELECT cast (123.23 as NUMERIC (12,6))")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var dest float64
+		var dest float64
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&dest); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	if dest != 123.23 {
-		t.Errorf("values are not equal: %v\n", dest)
-	}
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&dest); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		if dest != 123.23 {
+			t.Errorf("values are not equal: %v\n", dest)
+		}
+	})
 }
 
 func TestConnectionQueryDateTime64Type(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
-	loc, _ := time.LoadLocation("UTC")
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
+		loc, _ := time.LoadLocation("UTC")
 
-	rows, err := conn.QueryContext(context.TODO(), "SELECT '1980-01-01 02:03:04.321321'::TIMESTAMPNTZ;")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		rows, err := conn.QueryContext(ctx, "SELECT '1980-01-01 02:03:04.321321'::TIMESTAMPNTZ;")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var dest time.Time
+		var dest time.Time
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&dest); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	if expected := time.Date(1980, 1, 1, 2, 3, 4, 321321000, loc); expected != dest {
-		t.Errorf(VALUES_ARE_NOT_EQUAL_ERROR_MSG, dest, expected)
-	}
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&dest); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		if expected := time.Date(1980, 1, 1, 2, 3, 4, 321321000, loc); expected != dest {
+			t.Errorf(VALUES_ARE_NOT_EQUAL_ERROR_MSG, dest, expected)
+		}
+	})
 }
 
 func TestConnectionQueryPGDateType(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
-	loc, _ := time.LoadLocation("UTC")
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
+		loc, _ := time.LoadLocation("UTC")
 
-	// Value 0001-01-01 is outside of range of regular DATE
-	rows, err := conn.QueryContext(context.TODO(), "SELECT '0001-01-01' :: PGDATE;")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		// Value 0001-01-01 is outside of range of regular DATE
+		rows, err := conn.QueryContext(ctx, "SELECT '0001-01-01' :: PGDATE;")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var dest time.Time
+		var dest time.Time
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&dest); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	if expected := time.Date(0001, 1, 1, 0, 0, 0, 0, loc); expected != dest {
-		t.Errorf(VALUES_ARE_NOT_EQUAL_ERROR_MSG, dest, expected)
-	}
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&dest); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		if expected := time.Date(0001, 1, 1, 0, 0, 0, 0, loc); expected != dest {
+			t.Errorf(VALUES_ARE_NOT_EQUAL_ERROR_MSG, dest, expected)
+		}
+	})
 }
 
 func TestConnectionQueryTimestampNTZType(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
-	loc, _ := time.LoadLocation("UTC")
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
+		loc, _ := time.LoadLocation("UTC")
 
-	rows, err := conn.QueryContext(context.TODO(), "SELECT '0001-01-05 17:04:42.123456' :: TIMESTAMPNTZ;")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		rows, err := conn.QueryContext(ctx, "SELECT '0001-01-05 17:04:42.123456' :: TIMESTAMPNTZ;")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var dest time.Time
+		var dest time.Time
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&dest); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	if expected := time.Date(0001, 1, 5, 17, 4, 42, 123456000, loc); expected != dest {
-		t.Errorf(VALUES_ARE_NOT_EQUAL_ERROR_MSG, dest, expected)
-	}
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&dest); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		if expected := time.Date(0001, 1, 5, 17, 4, 42, 123456000, loc); expected != dest {
+			t.Errorf(VALUES_ARE_NOT_EQUAL_ERROR_MSG, dest, expected)
+		}
+	})
 }
 
 func TestConnectionQueryTimestampTZType(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
-	loc, _ := time.LoadLocation("UTC")
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
+		loc, _ := time.LoadLocation("UTC")
 
-	rows, err := conn.QueryContext(context.TODO(), "SELECT '2023-01-05 17:04:42.1234 Europe/Berlin'::TIMESTAMPTZ;")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		rows, err := conn.QueryContext(ctx, "SELECT '2023-01-05 17:04:42.1234 Europe/Berlin'::TIMESTAMPTZ;")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var dest time.Time
+		var dest time.Time
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&dest); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	// Expected offset by 1 hour when converted to UTC
-	expected := time.Date(2023, 1, 5, 16, 4, 42, 123400000, loc)
-	if !dest.Equal(expected) {
-		t.Errorf("values are not equal Expected: %v Got: %v\n", expected, dest)
-	}
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&dest); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		// Expected offset by 1 hour when converted to UTC
+		expected := time.Date(2023, 1, 5, 16, 4, 42, 123400000, loc)
+		if !dest.Equal(expected) {
+			t.Errorf("values are not equal Expected: %v Got: %v\n", expected, dest)
+		}
+	})
 }
 
 func TestConnectionQueryTimestampTZTypeAsia(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
-	if _, err = conn.ExecContext(context.Background(), "SET time_zone=Asia/Calcutta"); err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-		t.FailNow()
-	}
-	loc, _ := time.LoadLocation("Asia/Calcutta")
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
+		if _, err = conn.ExecContext(ctx, "SET time_zone=Asia/Calcutta"); err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+			t.FailNow()
+		}
+		loc, _ := time.LoadLocation("Asia/Calcutta")
 
-	rows, err := conn.QueryContext(context.TODO(), "SELECT '2023-01-05 17:04:42.123456 Europe/Berlin'::TIMESTAMPTZ;")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		rows, err := conn.QueryContext(ctx, "SELECT '2023-01-05 17:04:42.123456 Europe/Berlin'::TIMESTAMPTZ;")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var dest time.Time
+		var dest time.Time
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&dest); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	// Expected offset by 5:30 when converted to Asia/Calcutta
-	expected := time.Date(2023, 1, 5, 21, 34, 42, 123456000, loc)
-	if !dest.Equal(expected) {
-		t.Errorf("%s date with half-timezone check failed Expected: %s Got: %s", err, expected, dest)
-	}
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&dest); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		// Expected offset by 5:30 when converted to Asia/Calcutta
+		expected := time.Date(2023, 1, 5, 21, 34, 42, 123456000, loc)
+		if !dest.Equal(expected) {
+			t.Errorf("%s date with half-timezone check failed Expected: %s Got: %s", err, expected, dest)
+		}
+	})
 }
 
 func TestConnectionMultipleStatement(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
-	if rows, err := conn.QueryContext(context.TODO(), "SELECT -1; SELECT -2"); err != nil {
-		t.Errorf("Query multistement returned err: %v", err)
-	} else {
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
+		if rows, err := conn.QueryContext(ctx, "SELECT -1; SELECT -2"); err != nil {
+			t.Errorf("Query multistement returned err: %v", err)
+		} else {
 
-		var dest int32
+			var dest int32
 
-		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-		err = rows.Scan(&dest)
-		utils.AssertEqual(err, nil, t, "rows.Scan returned an error")
-		utils.AssertEqual(dest, int32(-1), t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
+			utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+			err = rows.Scan(&dest)
+			utils.AssertEqual(err, nil, t, "rows.Scan returned an error")
+			utils.AssertEqual(dest, int32(-1), t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
 
-		utils.AssertEqual(rows.NextResultSet(), true, t, "NextResultSet returned false")
-		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-		err = rows.Scan(&dest)
-		utils.AssertEqual(err, nil, t, "rows.Scan returned an error")
-		utils.AssertEqual(dest, int32(-2), t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
+			utils.AssertEqual(rows.NextResultSet(), true, t, "NextResultSet returned false")
+			utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+			err = rows.Scan(&dest)
+			utils.AssertEqual(err, nil, t, "rows.Scan returned an error")
+			utils.AssertEqual(dest, int32(-2), t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
 
-		utils.AssertEqual(rows.NextResultSet(), false, t, "NextResultSet returned true")
-		utils.AssertEqual(rows.Next(), false, t, "Next returned true")
-	}
+			utils.AssertEqual(rows.NextResultSet(), false, t, "NextResultSet returned true")
+			utils.AssertEqual(rows.Next(), false, t, "Next returned true")
+		}
+	})
 }
 
 func TestConnectionQueryBooleanType(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
 
-	rows, err := conn.QueryContext(context.TODO(), "SELECT true, false, null::boolean;")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		rows, err := conn.QueryContext(ctx, "SELECT true, false, null::boolean;")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var b1, b2 bool
-	// Nil value can only be assigned to an interface{}
-	var b3 interface{}
+		var b1, b2 bool
+		// Nil value can only be assigned to an interface{}
+		var b3 interface{}
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&b1, &b2, &b3); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	utils.AssertEqual(b1, true, t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
-	utils.AssertEqual(b2, false, t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
-	utils.AssertEqual(b3, nil, t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&b1, &b2, &b3); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		utils.AssertEqual(b1, true, t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
+		utils.AssertEqual(b2, false, t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
+		utils.AssertEqual(b3, nil, t, RESULTS_ARE_NOT_EQUAL_ERROR_MSG)
+	})
 }
 
 func TestConnectionQueryByteaType(t *testing.T) {
-	conn, err := sql.Open("firebolt", dsnMock)
-	if err != nil {
-		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
-		t.FailNow()
-	}
+	utils.RunInMemoryAndStream(t, func(t *testing.T, ctx context.Context) {
+		conn, err := sql.Open("firebolt", dsnMock)
+		if err != nil {
+			t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+			t.FailNow()
+		}
 
-	rows, err := conn.QueryContext(context.TODO(), "SELECT 'abc123'::bytea")
-	if err != nil {
-		t.Errorf(STATEMENT_ERROR_MSG, err)
-	}
+		rows, err := conn.QueryContext(ctx, "SELECT 'abc123'::bytea")
+		if err != nil {
+			t.Errorf(STATEMENT_ERROR_MSG, err)
+		}
 
-	var dest []byte
+		var dest []byte
 
-	utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
-	if err = rows.Scan(&dest); err != nil {
-		t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
-	}
-	expected := []byte("abc123")
-	if !bytes.Equal(dest, expected) {
-		t.Errorf("Bytea type check failed Expected: %s Got: %s", expected, dest)
-	}
+		utils.AssertEqual(rows.Next(), true, t, NEXT_STATEMENT_ERROR_MSG)
+		if err = rows.Scan(&dest); err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		expected := []byte("abc123")
+		if !bytes.Equal(dest, expected) {
+			t.Errorf("Bytea type check failed Expected: %s Got: %s", expected, dest)
+		}
+	})
 }
 
 func TestLongQuery(t *testing.T) {
@@ -406,3 +436,40 @@ func TestLongQuery(t *testing.T) {
 		t.Errorf("Long query didn't finish in 10 minutes")
 	}
 }
+
+func TestStreamMultipleDataBlocks(t *testing.T) {
+	conn, err := sql.Open("firebolt", dsnMock)
+	if err != nil {
+		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+		t.FailNow()
+	}
+
+	expectedValue, rowCount := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 100000
+	query := fmt.Sprintf("SELECT '%s' FROM generate_series(0, %d)", expectedValue, rowCount)
+
+	rows, err := conn.QueryContext(contextUtils.WithStreaming(context.Background()), query)
+
+	if err != nil {
+		t.Errorf(STATEMENT_ERROR_MSG, err)
+		t.FailNow()
+	}
+
+	var dest string
+
+	for i := 0; i <= rowCount; i++ {
+		if !rows.Next() {
+			t.Errorf("Next() call returned false with error: %v", rows.Err())
+			t.FailNow()
+		}
+		err = rows.Scan(&dest)
+		if err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+		utils.AssertEqual(dest, expectedValue, t, "dest is not equal")
+	}
+
+	if rows.Next() {
+		t.Errorf("Next() call returned true, but it should return false")
+	}
+
+}
diff --git a/connection_integration_test.go b/connection_integration_test.go
index d14b247a..ecc27b68 100644
--- a/connection_integration_test.go
+++ b/connection_integration_test.go
@@ -90,6 +90,49 @@ func TestConnectionUseDatabase(t *testing.T) {
 	}
 }
 
+func TestConnectionUppercaseNames(t *testing.T) {
+	systemConnection, err := sql.Open("firebolt", dsnSystemEngineMock)
+	if err != nil {
+		t.Errorf("opening a system connection failed unexpectedly %v", err)
+		t.FailNow()
+	}
+
+	engineName := engineNameMock + "_UPPERCASE"
+	databaseName := databaseMock + "_UPPERCASE"
+
+	_, err = systemConnection.Exec(fmt.Sprintf("CREATE DATABASE \"%s\"", databaseName))
+	if err != nil {
+		t.Errorf("creating a database failed unexpectedly %v", err)
+		t.FailNow()
+	}
+	defer systemConnection.Exec(fmt.Sprintf("DROP DATABASE \"%s\"", databaseName))
+	_, err = systemConnection.Exec(fmt.Sprintf("CREATE ENGINE \"%s\"", engineName))
+	if err != nil {
+		t.Errorf("creating an engine failed unexpectedly %v", err)
+		t.FailNow()
+	}
+	defer systemConnection.Exec(fmt.Sprintf("DROP ENGINE \"%s\"", engineName))
+	// defers run in reverse order so we stop the engine before dropping it
+	defer systemConnection.Exec(fmt.Sprintf("STOP ENGINE \"%s\"", engineName))
+
+	dsnUppercase := fmt.Sprintf(
+		"firebolt:///%s?account_name=%s&engine=%s&client_id=%s&client_secret=%s",
+		databaseName, accountName, engineName, clientIdMock, clientSecretMock,
+	)
+
+	conn, err := sql.Open("firebolt", dsnUppercase)
+	if err != nil {
+		t.Errorf("opening a connection failed unexpectedly")
+		t.FailNow()
+	}
+
+	_, err = conn.Exec("SELECT 1")
+	if err != nil {
+		t.Errorf("query failed with %v", err)
+		t.FailNow()
+	}
+}
+
 func TestConnectionUseDatabaseEngine(t *testing.T) {
 
 	const createTableSQL = "CREATE TABLE IF NOT EXISTS test_use (id INT)"
@@ -151,49 +194,6 @@ func TestConnectionUseDatabaseEngine(t *testing.T) {
 	}
 }
 
-func TestConnectionUppercaseNames(t *testing.T) {
-	systemConnection, err := sql.Open("firebolt", dsnSystemEngineMock)
-	if err != nil {
-		t.Errorf("opening a system connection failed unexpectedly %v", err)
-		t.FailNow()
-	}
-
-	engineName := engineNameMock + "_UPPERCASE"
-	databaseName := databaseMock + "_UPPERCASE"
-
-	_, err = systemConnection.Exec(fmt.Sprintf("CREATE DATABASE \"%s\"", databaseName))
-	if err != nil {
-		t.Errorf("creating a database failed unexpectedly %v", err)
-		t.FailNow()
-	}
-	defer systemConnection.Exec(fmt.Sprintf("DROP DATABASE \"%s\"", databaseName))
-	_, err = systemConnection.Exec(fmt.Sprintf("CREATE ENGINE \"%s\"", engineName))
-	if err != nil {
-		t.Errorf("creating an engine failed unexpectedly %v", err)
-		t.FailNow()
-	}
-	defer systemConnection.Exec(fmt.Sprintf("DROP ENGINE \"%s\"", engineName))
-	// defers run in reverse order so we stop the engine before dropping it
-	defer systemConnection.Exec(fmt.Sprintf("STOP ENGINE \"%s\"", engineName))
-
-	dsnUppercase := fmt.Sprintf(
-		"firebolt:///%s?account_name=%s&engine=%s&client_id=%s&client_secret=%s",
-		databaseName, accountName, engineName, clientIdMock, clientSecretMock,
-	)
-
-	conn, err := sql.Open("firebolt", dsnUppercase)
-	if err != nil {
-		t.Errorf("opening a connection failed unexpectedly")
-		t.FailNow()
-	}
-
-	_, err = conn.Exec("SELECT 1")
-	if err != nil {
-		t.Errorf("query failed with %v", err)
-		t.FailNow()
-	}
-}
-
 func TestConnectionPreparedStatement(t *testing.T) {
 	conn, err := sql.Open("firebolt", dsnMock)
 	if err != nil {
diff --git a/context/context.go b/context/context.go
new file mode 100644
index 00000000..77332854
--- /dev/null
+++ b/context/context.go
@@ -0,0 +1,28 @@
+package context
+
+import (
+	"context"
+)
+
+type ContextKey string
+
+const AdditionalHeadersContextKey = ContextKey("additionalHeaders")
+const StreamingContextKey = ContextKey("streaming")
+
+func WithStreaming(ctx context.Context) context.Context {
+	return context.WithValue(ctx, StreamingContextKey, true)
+}
+
+func WithAdditionalHeaders(ctx context.Context, headers map[string]string) context.Context {
+	return context.WithValue(ctx, AdditionalHeadersContextKey, headers)
+}
+
+func IsStreaming(ctx context.Context) bool {
+	IsStreaming, ok := ctx.Value(StreamingContextKey).(bool)
+	return ok && IsStreaming
+}
+
+func GetAdditionalHeaders(ctx context.Context) (map[string]string, bool) {
+	headers, ok := ctx.Value(AdditionalHeadersContextKey).(map[string]string)
+	return headers, ok
+}
diff --git a/driver_options_integration_test.go b/driver_options_integration_test.go
index ee654549..6c9ff0a0 100644
--- a/driver_options_integration_test.go
+++ b/driver_options_integration_test.go
@@ -5,8 +5,11 @@ package fireboltgosdk
 
 import (
 	"context"
+	"database/sql/driver"
 	"testing"
 
+	"github.com/firebolt-db/firebolt-go-sdk/rows"
+
 	"github.com/firebolt-db/firebolt-go-sdk/client"
 	"github.com/firebolt-db/firebolt-go-sdk/types"
 	"github.com/firebolt-db/firebolt-go-sdk/utils"
@@ -52,7 +55,20 @@ func TestFireboltConnectorWithOptions(t *testing.T) {
 		t.Errorf("failed unexpectedly with: %v", err)
 		t.FailNow()
 	}
-	utils.AssertEqual(len(resp.Data), 1, t, "result data length is not 1")
-	utils.AssertEqual(len(resp.Data[0]), 1, t, "result value is invalid")
-	utils.AssertEqual(resp.Data[0][0].(float64), float64(1), t, "result is not 1")
+
+	rows := rows.InMemoryRows{}
+	if err := rows.ProcessAndAppendResponse(resp); err != nil {
+		t.Errorf("failed to process response: %v", err)
+		t.FailNow()
+	}
+
+	var values []driver.Value = make([]driver.Value, len(rows.Columns()))
+
+	if err := rows.Next(values); err != nil {
+		t.Errorf("failed to get result: %v", err)
+		t.FailNow()
+	}
+
+	utils.AssertEqual(len(values), 1, t, "returned more that one value")
+	utils.AssertEqual(values[0], int32(1), t, "result is not 1")
 }
diff --git a/rows/in_memoty_rows_test.go b/rows/common_tests.go
similarity index 67%
rename from rows/in_memoty_rows_test.go
rename to rows/common_tests.go
index dd05751f..4b9569f2 100644
--- a/rows/in_memoty_rows_test.go
+++ b/rows/common_tests.go
@@ -14,105 +14,14 @@ import (
 
 	"github.com/firebolt-db/firebolt-go-sdk/types"
 	"github.com/firebolt-db/firebolt-go-sdk/utils"
-
 	"github.com/shopspring/decimal"
 )
 
 const nextErrorMessage = "Next returned an error: %s"
 
-func mockRows(isMultiStatement bool) driver.RowsNextResultSet {
-	resultJson := []string{
-		`{
-        "query":{"query_id":"16FF2A0300ECA753"},
-        "meta":[
-        	{"name":"int_col","type":"int null"},
-        	{"name":"bigint_col","type":"long"},
-        	{"name":"float_col","type":"float"},
-        	{"name":"double_col","type":"double"},
-        	{"name":"text_col","type":"text"},
-        	{"name":"date_col","type":"date"},
-        	{"name":"timestamp_col","type":"timestamp"},
-        	{"name":"pgdate_col","type":"pgdate"},
-        	{"name":"timestampntz_col","type":"timestampntz"},
-        	{"name":"timestamptz_col","type":"timestamptz"},
-        	{"name":"legacy_bool_col","type":"int"},
-        	{"name":"array_col","type":"array(int)"},
-        	{"name":"nested_array_col","type":"array(array(text))"},
-        	{"name":"new_bool_col","type":"boolean"},
-        	{"name":"decimal_col","type":"Decimal(38, 30) null"},
-        	{"name":"decimal_array_col","type":"array(Decimal(38, 30) null)"},
-        	{"name":"bytea_col","type":"bytea null"},
-        	{"name":"geography_col","type":"geography null"},
-            {"name":"struct_col","type":"struct(a int, s struct(a array(int), b text))"}
-        ],
-        "data":[
-        	[null,1,0.312321,123213.321321,"text", "2080-12-31","1989-04-15 01:02:03","0002-01-01","1989-04-15 01:02:03.123456","1989-04-15 02:02:03.123456+00",1,[1,2,3],[[]],true, 123.12345678, [123.12345678], "\\x616263313233", "0101000020E6100000FEFFFFFFFFFFEF3F000000000000F03F", {"a":1,"s":{"a":[1,2,3],"b":"text"}}],
-        	[2,"37237366456",0.312321,123213.321321,"text","1970-01-01","1970-01-01 00:00:00","0001-01-01","1989-04-15 01:02:03.123457","1989-04-15 01:02:03.1234+05:30",1,[1,2,3],[[]],true, -123.12345678, [-123.12345678, 0.0], "\\x6162630A0AE3858D20E3858E5C", null, {"a": 2, "s": null}],
-        	[3,null,"inf",123213.321321,"text","1970-01-01","1970-01-01 00:00:00","0001-01-01","1989-04-15 01:02:03.123458","1989-04-15 01:02:03+01",1,[5,2,3,2],[["TEST","TEST1"],["TEST3"]],false, 0.0, [0.0], null, null, null],
-        	[2,1,"-inf",123213.321321,"text","1970-01-01","1970-01-01 00:00:00","0001-01-01","1989-04-15 01:02:03.123457","1111-01-05 17:04:42.123456+05:53:28",1,[1,2,3],[[]],false, 123456781234567812345678.123456781234567812345678, [123456781234567812345678.12345678123456781234567812345678], null, null, null],
-    	    [2,1,"-nan",123213.321321,"text","1970-01-01","1970-01-01 00:00:00","0001-01-01","1989-04-15 01:02:03.123457","1989-04-15 02:02:03.123456-01",1,[1,2,3],[[]],null, null, [null], null, null, null, null]
-        ],
-        "rows":5,
-        "statistics":{
-        	"elapsed":0.001797702,
-        	"rows_read":3,
-        	"bytes_read":293,
-        	"time_before_execution":0.001251613,
-        	"time_to_execute":0.000544098,
-        	"scanned_bytes_cache":2003,
-            "scanned_bytes_storage":0
-        }
-    }`,
-
-		`{
-        "query":{"query_id":"16FF2A0300ECA753"},
-        "meta":[{"name":"int_col","type":"int null"}],
-        "data":[[3], [null]],
-        "rows":2,
-        "statistics":{
-        	"elapsed":0.001797702,
-        	"rows_read":2,
-        	"bytes_read":293,
-        	"time_before_execution":0.001251613,
-        	"time_to_execute":0.000544098,
-        	"scanned_bytes_cache":2003,
-            "scanned_bytes_storage":0
-        }
-    }`,
-	}
-
-	var responses []types.QueryResponse
-	for i := 0; i < 2; i += 1 {
-		if i != 0 && !isMultiStatement {
-			break
-		}
-		var response types.QueryResponse
-		if err := json.Unmarshal([]byte(resultJson[i]), &response); err != nil {
-			panic(err)
-		} else {
-			responses = append(responses, response)
-		}
-	}
-
-	return &InMemoryRows{responses, 0, 0}
-}
-
-func mockRowsSingleValue(value interface{}, columnType string) driver.RowsNextResultSet {
-	response := types.QueryResponse{
-		Query:      map[string]string{"query_id": "16FF2A0300ECA753"},
-		Meta:       []types.Column{{Name: "single_col", Type: columnType}},
-		Data:       [][]interface{}{{value}},
-		Rows:       1,
-		Errors:     []types.ErrorDetails{},
-		Statistics: map[string]interface{}{},
-	}
-
-	return &InMemoryRows{[]types.QueryResponse{response}, 0, 0}
-}
-
-// TestRowsColumns checks, that correct column names are returned
-func TestRowsColumns(t *testing.T) {
-	rows := mockRows(false)
+// testRowsColumns checks, that correct column names are returned
+func testRowsColumns(t *testing.T, rowsFactory func(isMultiStatement bool) driver.RowsNextResultSet) {
+	rows := rowsFactory(false)
 
 	columnNames := []string{"int_col", "bigint_col", "float_col", "double_col", "text_col", "date_col", "timestamp_col", "pgdate_col", "timestampntz_col", "timestamptz_col", "legacy_bool_col", "array_col", "nested_array_col", "new_bool_col", "decimal_col", "decimal_array_col", "bytea_col", "geography_col", "struct_col"}
 	if !reflect.DeepEqual(rows.Columns(), columnNames) {
@@ -120,9 +29,9 @@ func TestRowsColumns(t *testing.T) {
 	}
 }
 
-// TestRowsClose checks Close method, and inability to use rows afterward
-func TestRowsClose(t *testing.T) {
-	rows := mockRows(false)
+// testRowsClose checks Close method, and inability to use rows afterward
+func testRowsClose(t *testing.T, rowsFactory func(isMultiStatement bool) driver.RowsNextResultSet) {
+	rows := rowsFactory(false)
 	if rows.Close() != nil {
 		t.Errorf("Closing rows was not successful")
 	}
@@ -133,9 +42,9 @@ func TestRowsClose(t *testing.T) {
 	}
 }
 
-// TestRowsNext check Next method
-func TestRowsNext(t *testing.T) {
-	rows := mockRows(false)
+// testRowsNext check Next method
+func testRowsNext(t *testing.T, rowsFactory func(isMultiStatement bool) driver.RowsNextResultSet) {
+	rows := rowsFactory(false)
 	var dest = make([]driver.Value, 19)
 
 	// First row
@@ -219,9 +128,9 @@ func TestRowsNext(t *testing.T) {
 	utils.AssertEqual(rows.NextResultSet(), io.EOF, t, "Next result set didn't return false at the end")
 }
 
-// TestRowsNextSet check rows with multiple statements
-func TestRowsNextSet(t *testing.T) {
-	rows := mockRows(true)
+// testRowsNextSet check rows with multiple statements
+func testRowsNextSet(t *testing.T, rowsFactory func(isMultiStatement bool) driver.RowsNextResultSet) {
+	rows := rowsFactory(true)
 
 	// check next result set functions
 	utils.AssertEqual(rows.HasNextResultSet(), true, t, "HasNextResultSet returned false, but shouldn't")
@@ -245,7 +154,7 @@ func TestRowsNextSet(t *testing.T) {
 	utils.AssertEqual(io.EOF, rows.Next(dest), t, "Next should return io.EOF if no data available anymore")
 }
 
-func TestRowsNextStructError(t *testing.T) {
+func testRowsNextStructError(t *testing.T, rowsFactory func(isMultiStatement bool) driver.RowsNextResultSet) {
 	rowsJson := `{
         "query":{"query_id":"16FF2A0300ECA753"},
         "meta":[{"name":"struct_col","type":"struct(a int, s struct(b timestamp))"}],
@@ -265,7 +174,7 @@ func TestRowsNextStructError(t *testing.T) {
 	}
 }
 
-func TestRowsNextStructWithNestedSpaces(t *testing.T) {
+func testRowsNextStructWithNestedSpaces(t *testing.T, rowsFactory func(isMultiStatement bool) driver.RowsNextResultSet) {
 	rowsJson := `{
         "query":{"query_id":"16FF2A0300ECA753"},
         "meta":[{"name":"struct_col","type":"struct(` + "`a b`" + ` int, s struct(` + "`c d`" + ` timestamp))"}],
@@ -292,10 +201,10 @@ func TestRowsNextStructWithNestedSpaces(t *testing.T) {
 	}
 }
 
-func TestRowsQuotedLong(t *testing.T) {
+func testRowsQuotedLong(t *testing.T, rowsFactorySingleValue func(interface{}, string) driver.RowsNextResultSet) {
 	intRaw := "-9223372036854775808"
 	intValue, _ := strconv.ParseInt(intRaw, 10, 64)
-	rows := mockRowsSingleValue(intRaw, "long")
+	rows := rowsFactorySingleValue(intRaw, "long")
 	var dest = make([]driver.Value, 1)
 	if err := rows.Next(dest); err != nil {
 		t.Errorf(nextErrorMessage, err)
@@ -303,7 +212,7 @@ func TestRowsQuotedLong(t *testing.T) {
 	utils.AssertEqual(dest[0], intValue, t, "results are not equal for long")
 }
 
-func TestRowsDecimalType(t *testing.T) {
+func testRowsDecimalType(t *testing.T, rowsFactorySingleValue func(interface{}, string) driver.RowsNextResultSet) {
 	floatValue := 123456789.123456789
 	stringValue := "1234567890123456789012345678901234567890"
 	stringDecimalValue, _ := decimal.NewFromString(stringValue)
@@ -313,7 +222,7 @@ func TestRowsDecimalType(t *testing.T) {
 		{stringValue, stringDecimalValue},
 	}
 	for _, c := range cases {
-		rows := mockRowsSingleValue(c[0], "Decimal(10, 35)")
+		rows := rowsFactorySingleValue(c[0], "Decimal(10, 35)")
 		var dest = make([]driver.Value, 1)
 		if err := rows.Next(dest); err != nil {
 			t.Errorf(nextErrorMessage, err)
diff --git a/rows/extendable_rows.go b/rows/extendable_rows.go
new file mode 100644
index 00000000..3559a17d
--- /dev/null
+++ b/rows/extendable_rows.go
@@ -0,0 +1,12 @@
+package rows
+
+import (
+	"database/sql/driver"
+
+	"github.com/firebolt-db/firebolt-go-sdk/client"
+)
+
+type ExtendableRows interface {
+	driver.Rows
+	ProcessAndAppendResponse(response *client.Response) error
+}
diff --git a/rows/fixtures/error.jsonl b/rows/fixtures/error.jsonl
new file mode 100644
index 00000000..b92197b3
--- /dev/null
+++ b/rows/fixtures/error.jsonl
@@ -0,0 +1,3 @@
+{  "message_type": "START",  "query_id": "16FF2A0300ECA753",  "result_columns": [    {      "name": "int_col",      "type": "int null"    }  ]}
+{"message_type":"DATA",  "data": [    [      3    ],    [      null    ]  ]}
+{"message_type":"FINISHED_WITH_ERROR","errors": {"description":  "error"},",statistics":{"elapsed":0.001797702,"rows_read":2,"bytes_read":293,"time_before_execution":0.001251613,"time_to_execute":0.000544098,"scanned_bytes_cache":2003,"scanned_bytes_storage":0}}
\ No newline at end of file
diff --git a/rows/fixtures/result1.jsonl b/rows/fixtures/result1.jsonl
new file mode 100644
index 00000000..50710638
--- /dev/null
+++ b/rows/fixtures/result1.jsonl
@@ -0,0 +1,3 @@
+{  "message_type": "START",  "query_id": "16FF2A0300ECA753",  "result_columns": [    {      "name": "int_col",      "type": "int null"    },    {      "name": "bigint_col",      "type": "long"    },    {      "name": "float_col",      "type": "float"    },    {      "name": "double_col",      "type": "double"    },    {      "name": "text_col",      "type": "text"    },    {      "name": "date_col",      "type": "date"    },    {      "name": "timestamp_col",      "type": "timestamp"    },    {      "name": "pgdate_col",      "type": "pgdate"    },    {      "name": "timestampntz_col",      "type": "timestampntz"    },    {      "name": "timestamptz_col",      "type": "timestamptz"    },    {      "name": "legacy_bool_col",      "type": "int"    },    {      "name": "array_col",      "type": "array(int)"    },    {      "name": "nested_array_col",      "type": "array(array(text))"    },    {      "name": "new_bool_col",      "type": "boolean"    },    {      "name": "decimal_col",      "type": "Decimal(38, 30) null"    },    {      "name": "decimal_array_col",      "type": "array(Decimal(38, 30) null)"    },    {      "name": "bytea_col",      "type": "bytea null"    },    {      "name": "geography_col",      "type": "geography null"    },    {      "name": "struct_col",      "type": "struct(a int, s struct(a array(int), b text))"    }  ]}
+{  "message_type": "DATA",  "data": [    [      null,      1,      0.312321,      123213.321321,      "text",      "2080-12-31",      "1989-04-15 01:02:03",      "0002-01-01",      "1989-04-15 01:02:03.123456",      "1989-04-15 02:02:03.123456+00",      1,      [        1,        2,        3      ],      [        []      ],      true,      123.12345678,      [        123.12345678      ],      "\\x616263313233",      "0101000020E6100000FEFFFFFFFFFFEF3F000000000000F03F",      {        "a": 1,        "s": {          "a": [            1,            2,            3          ],          "b": "text"        }      }    ],    [      2,      "37237366456",      0.312321,      123213.321321,      "text",      "1970-01-01",      "1970-01-01 00:00:00",      "0001-01-01",      "1989-04-15 01:02:03.123457",      "1989-04-15 01:02:03.1234+05:30",      1,      [        1,        2,        3      ],      [        []      ],      true,      -123.12345678,      [        -123.12345678,        0.0      ],      "\\x6162630A0AE3858D20E3858E5C",      null,      {        "a": 2,        "s": null      }    ],    [      3,      null,      "inf",      123213.321321,      "text",      "1970-01-01",      "1970-01-01 00:00:00",      "0001-01-01",      "1989-04-15 01:02:03.123458",      "1989-04-15 01:02:03+01",      1,      [        5,        2,        3,        2      ],      [        [          "TEST",          "TEST1"        ],        [          "TEST3"        ]      ],      false,      0.0,      [        0.0      ],      null,      null,      null    ],    [      2,      1,      "-inf",      123213.321321,      "text",      "1970-01-01",      "1970-01-01 00:00:00",      "0001-01-01",      "1989-04-15 01:02:03.123457",      "1111-01-05 17:04:42.123456+05:53:28",      1,      [        1,        2,        3      ],      [        []      ],      false,      123456781234567812345678.123456781234567812345678,      [        123456781234567812345678.12345678123456781234567812345678      ],      null,      null,      null    ],    [      2,      1,      "-nan",      123213.321321,      "text",      "1970-01-01",      "1970-01-01 00:00:00",      "0001-01-01",      "1989-04-15 01:02:03.123457",      "1989-04-15 02:02:03.123456-01",      1,      [        1,        2,        3      ],      [        []      ],      null,      null,      [        null      ],      null,      null,      null,      null    ]  ]}
+{  "message_type": "FINISHED_WITH_SUCCESS",  "statistics":{    "elapsed":0.001797702,    "rows_read":3,    "bytes_read":293,    "time_before_execution":0.001251613,    "time_to_execute":0.000544098,    "scanned_bytes_cache":2003,    "scanned_bytes_storage":0  }}
\ No newline at end of file
diff --git a/rows/fixtures/result2.jsonl b/rows/fixtures/result2.jsonl
new file mode 100644
index 00000000..f3ca5c7d
--- /dev/null
+++ b/rows/fixtures/result2.jsonl
@@ -0,0 +1,3 @@
+{  "message_type": "START",  "query_id": "16FF2A0300ECA753",  "result_columns": [    {      "name": "int_col",      "type": "int null"    }  ]}
+{"message_type":"DATA",  "data": [    [      3    ],    [      null    ]  ]}
+{"message_type":"FINISHED_WITH_SUCCESS","statistics":{"elapsed":0.001797702,"rows_read":2,"bytes_read":293,"time_before_execution":0.001251613,"time_to_execute":0.000544098,"scanned_bytes_cache":2003,"scanned_bytes_storage":0}}
\ No newline at end of file
diff --git a/rows/in_memory_rows.go b/rows/in_memory_rows.go
index 8431c635..5f412c87 100644
--- a/rows/in_memory_rows.go
+++ b/rows/in_memory_rows.go
@@ -2,8 +2,13 @@ package rows
 
 import (
 	"database/sql/driver"
+	"encoding/json"
+	errorUtils "errors"
 	"io"
 
+	"github.com/firebolt-db/firebolt-go-sdk/client"
+	"github.com/firebolt-db/firebolt-go-sdk/logging"
+
 	"github.com/firebolt-db/firebolt-go-sdk/errors"
 	"github.com/firebolt-db/firebolt-go-sdk/types"
 )
@@ -68,6 +73,33 @@ func (r *InMemoryRows) NextResultSet() error {
 	return nil
 }
 
-func (r *InMemoryRows) AppendResponse(response types.QueryResponse) {
-	r.queryResponses = append(r.queryResponses, response)
+// AppendResponse appends the response to the InMemoryRows, parsing the response content
+// and checking for errors in the response body
+func (r *InMemoryRows) ProcessAndAppendResponse(response *client.Response) error {
+	// Check for error in the Response body, despite the status code 200
+	errorResponse := struct {
+		Errors []types.ErrorDetails `json:"errors"`
+	}{}
+	content, err := response.Content()
+	if err != nil {
+		return errors.ConstructNestedError("error during reading response content", err)
+	}
+	if err := json.Unmarshal(content, &errorResponse); err == nil {
+		if errorResponse.Errors != nil {
+			return errors.NewStructuredError(errorResponse.Errors)
+		}
+	}
+
+	// Unmarshal the response content
+	var queryResponse types.QueryResponse
+	// Response could be empty, which doesn't mean it is an error
+	if len(content) != 0 {
+		if err = json.Unmarshal(content, &queryResponse); err != nil {
+			return errors.ConstructNestedError("wrong response", errorUtils.New(string(content)))
+		}
+		logging.Infolog.Printf("Query was successful")
+	}
+
+	r.queryResponses = append(r.queryResponses, queryResponse)
+	return nil
 }
diff --git a/rows/in_memory_rows_test.go b/rows/in_memory_rows_test.go
new file mode 100644
index 00000000..148a4f0f
--- /dev/null
+++ b/rows/in_memory_rows_test.go
@@ -0,0 +1,136 @@
+package rows
+
+import (
+	"database/sql/driver"
+	"encoding/json"
+	"testing"
+
+	"github.com/firebolt-db/firebolt-go-sdk/types"
+)
+
+func mockRows(isMultiStatement bool) driver.RowsNextResultSet {
+	resultJson := []string{
+		`{
+        "query":{"query_id":"16FF2A0300ECA753"},
+        "meta":[
+        	{"name":"int_col","type":"int null"},
+        	{"name":"bigint_col","type":"long"},
+        	{"name":"float_col","type":"float"},
+        	{"name":"double_col","type":"double"},
+        	{"name":"text_col","type":"text"},
+        	{"name":"date_col","type":"date"},
+        	{"name":"timestamp_col","type":"timestamp"},
+        	{"name":"pgdate_col","type":"pgdate"},
+        	{"name":"timestampntz_col","type":"timestampntz"},
+        	{"name":"timestamptz_col","type":"timestamptz"},
+        	{"name":"legacy_bool_col","type":"int"},
+        	{"name":"array_col","type":"array(int)"},
+        	{"name":"nested_array_col","type":"array(array(text))"},
+        	{"name":"new_bool_col","type":"boolean"},
+        	{"name":"decimal_col","type":"Decimal(38, 30) null"},
+        	{"name":"decimal_array_col","type":"array(Decimal(38, 30) null)"},
+        	{"name":"bytea_col","type":"bytea null"},
+        	{"name":"geography_col","type":"geography null"},
+            {"name":"struct_col","type":"struct(a int, s struct(a array(int), b text))"}
+        ],
+        "data":[
+        	[null,1,0.312321,123213.321321,"text", "2080-12-31","1989-04-15 01:02:03","0002-01-01","1989-04-15 01:02:03.123456","1989-04-15 02:02:03.123456+00",1,[1,2,3],[[]],true, 123.12345678, [123.12345678], "\\x616263313233", "0101000020E6100000FEFFFFFFFFFFEF3F000000000000F03F", {"a":1,"s":{"a":[1,2,3],"b":"text"}}],
+        	[2,"37237366456",0.312321,123213.321321,"text","1970-01-01","1970-01-01 00:00:00","0001-01-01","1989-04-15 01:02:03.123457","1989-04-15 01:02:03.1234+05:30",1,[1,2,3],[[]],true, -123.12345678, [-123.12345678, 0.0], "\\x6162630A0AE3858D20E3858E5C", null, {"a": 2, "s": null}],
+        	[3,null,"inf",123213.321321,"text","1970-01-01","1970-01-01 00:00:00","0001-01-01","1989-04-15 01:02:03.123458","1989-04-15 01:02:03+01",1,[5,2,3,2],[["TEST","TEST1"],["TEST3"]],false, 0.0, [0.0], null, null, null],
+        	[2,1,"-inf",123213.321321,"text","1970-01-01","1970-01-01 00:00:00","0001-01-01","1989-04-15 01:02:03.123457","1111-01-05 17:04:42.123456+05:53:28",1,[1,2,3],[[]],false, 123456781234567812345678.123456781234567812345678, [123456781234567812345678.12345678123456781234567812345678], null, null, null],
+    	    [2,1,"-nan",123213.321321,"text","1970-01-01","1970-01-01 00:00:00","0001-01-01","1989-04-15 01:02:03.123457","1989-04-15 02:02:03.123456-01",1,[1,2,3],[[]],null, null, [null], null, null, null, null]
+        ],
+        "rows":5,
+        "statistics":{
+        	"elapsed":0.001797702,
+        	"rows_read":3,
+        	"bytes_read":293,
+        	"time_before_execution":0.001251613,
+        	"time_to_execute":0.000544098,
+        	"scanned_bytes_cache":2003,
+            "scanned_bytes_storage":0
+        }
+    }`,
+
+		`{
+        "query":{"query_id":"16FF2A0300ECA753"},
+        "meta":[{"name":"int_col","type":"int null"}],
+        "data":[[3], [null]],
+        "rows":2,
+        "statistics":{
+        	"elapsed":0.001797702,
+        	"rows_read":2,
+        	"bytes_read":293,
+        	"time_before_execution":0.001251613,
+        	"time_to_execute":0.000544098,
+        	"scanned_bytes_cache":2003,
+            "scanned_bytes_storage":0
+        }
+    }`,
+	}
+
+	var responses []types.QueryResponse
+	for i := 0; i < 2; i += 1 {
+		if i != 0 && !isMultiStatement {
+			break
+		}
+		var response types.QueryResponse
+		if err := json.Unmarshal([]byte(resultJson[i]), &response); err != nil {
+			panic(err)
+		} else {
+			responses = append(responses, response)
+		}
+	}
+
+	return &InMemoryRows{responses, 0, 0}
+}
+
+func mockRowsSingleValue(value interface{}, columnType string) driver.RowsNextResultSet {
+	response := types.QueryResponse{
+		Query:      map[string]string{"query_id": "16FF2A0300ECA753"},
+		Meta:       []types.Column{{Name: "single_col", Type: columnType}},
+		Data:       [][]interface{}{{value}},
+		Rows:       1,
+		Errors:     []types.ErrorDetails{},
+		Statistics: map[string]interface{}{},
+	}
+
+	return &InMemoryRows{[]types.QueryResponse{response}, 0, 0}
+}
+
+// testRowsColumns checks, that correct column names are returned
+func TestInMemoryRowsColumns(t *testing.T) {
+	testRowsColumns(t, mockRows)
+}
+
+// testRowsClose checks Close method, and inability to use rows afterward
+func TestInMemoryRowsClose(t *testing.T) {
+	testRowsClose(t, mockRows)
+
+}
+
+// testRowsNext check Next method
+func TestInMemoryRowsNext(t *testing.T) {
+	testRowsNext(t, mockRows)
+}
+
+// testRowsNextSet check rows with multiple statements
+func TestInMemoryRowsNextSet(t *testing.T) {
+	testRowsNextSet(t, mockRows)
+}
+
+func TestInMemoryRowsNextStructError(t *testing.T) {
+	testRowsNextStructError(t, mockRows)
+}
+
+func TestInMemoryRowsNextStructWithNestedSpaces(t *testing.T) {
+	testRowsNextStructWithNestedSpaces(t, mockRows)
+}
+
+func TestInMemoryRowsQuotedLong(t *testing.T) {
+	testRowsQuotedLong(t, mockRowsSingleValue)
+}
+
+func TestInMemoryRowsDecimalType(t *testing.T) {
+	testRowsDecimalType(t, mockRowsSingleValue)
+}
diff --git a/rows/parser.go b/rows/parser.go
index 42ed3f73..8840f4fa 100644
--- a/rows/parser.go
+++ b/rows/parser.go
@@ -33,12 +33,17 @@ const (
 	byteaType   = "bytea"
 
 	geographyType = "geography"
+
+	// Alternative names for the same types
+	integerType         = "integer"
+	bigIntType          = "bigint"
+	doublePrecisionType = "double precision"
 )
 
 // checkTypeValue checks that val type could be changed to columnType
 func checkTypeValue(columnType string, val interface{}) error {
 	switch columnType {
-	case intType, longType, floatType, doubleType:
+	case intType, integerType, longType, bigIntType, floatType, doubleType, doublePrecisionType:
 		if _, ok := val.(float64); !ok {
 			if columnType == floatType || columnType == doubleType {
 				for _, v := range []string{"inf", "-inf", "nan", "-nan"} {
@@ -194,9 +199,9 @@ func parseSingleValue(columnType string, val interface{}) (driver.Value, error)
 	}
 
 	switch columnType {
-	case intType:
+	case intType, integerType:
 		return int32(val.(float64)), nil
-	case longType:
+	case longType, bigIntType:
 		// long values as passed as strings by system engine
 		if unpacked, ok := val.(float64); ok {
 			return int64(unpacked), nil
@@ -205,7 +210,7 @@ func parseSingleValue(columnType string, val interface{}) (driver.Value, error)
 	case floatType:
 		v, err := parseFloatValue(val)
 		return float32(v), err
-	case doubleType:
+	case doubleType, doublePrecisionType:
 		return parseFloatValue(val)
 	case textType, geographyType:
 		return val.(string), nil
@@ -253,6 +258,7 @@ func parseValue(columnType string, val interface{}) (driver.Value, error) {
 		nullableSuffix = " null"
 		arrayPrefix    = "array("
 		decimalPrefix  = "Decimal("
+		numericPrefix  = "numeric("
 		structPrefix   = "struct("
 		suffix         = ")"
 	)
@@ -270,7 +276,7 @@ func parseValue(columnType string, val interface{}) (driver.Value, error) {
 			res[i], _ = parseValue(columnType[len(arrayPrefix):len(columnType)-len(suffix)], s.Index(i).Interface())
 		}
 		return res, nil
-	} else if strings.HasPrefix(columnType, decimalPrefix) && strings.HasSuffix(columnType, suffix) {
+	} else if (strings.HasPrefix(columnType, decimalPrefix) || strings.HasPrefix(columnType, numericPrefix)) && strings.HasSuffix(columnType, suffix) {
 		return parseDecimalValue(val)
 	} else if strings.HasPrefix(columnType, structPrefix) && strings.HasSuffix(columnType, suffix) {
 		return parseStruct(columnType[len(structPrefix):len(columnType)-len(suffix)], val)
diff --git a/rows/stream_rows.go b/rows/stream_rows.go
index c83c148e..51c2502e 100644
--- a/rows/stream_rows.go
+++ b/rows/stream_rows.go
@@ -1,37 +1,193 @@
 package rows
 
-import "database/sql/driver"
+import (
+	"bufio"
+	"bytes"
+	"database/sql/driver"
+	"encoding/json"
+	"fmt"
+	"io"
+
+	"github.com/firebolt-db/firebolt-go-sdk/client"
+	errorUtils "github.com/firebolt-db/firebolt-go-sdk/errors"
+	"github.com/firebolt-db/firebolt-go-sdk/types"
+)
 
 type StreamRows struct {
-	// TODO: add fields
+	responses         []*client.Response
+	resultSetPosition int
+	// current row
+	columns          []types.Column
+	rowReader        *bufio.Reader
+	dataBuffer       [][]interface{}
+	dataBufferCursor int
+	consumedResponse bool
+}
+
+func (r *StreamRows) readJsonLine() (types.JSONLinesRecord, error) {
+	reader := r.reader()
+
+	var record types.JSONLinesRecord
+	rawJsonLine, err := reader.ReadBytes('\n')
+	if err == io.EOF || rawJsonLine == nil {
+		return types.JSONLinesRecord{}, io.EOF
+	}
+	if err != nil {
+		return record, errorUtils.ConstructNestedError("Error reading JSON line:", err)
+	}
+	err = json.Unmarshal(rawJsonLine, &record)
+	if err != nil {
+		return record, errorUtils.ConstructNestedError("JSON parse error:", err)
+	}
+	return record, nil
+}
+
+func (r *StreamRows) reader() *bufio.Reader {
+	if r.rowReader == nil {
+		body := r.responses[r.resultSetPosition].Body()
+		if body != nil {
+			r.rowReader = bufio.NewReader(body)
+		} else {
+			r.rowReader = bufio.NewReader(io.NopCloser(bytes.NewReader([]byte{})))
+		}
+	}
+	return r.rowReader
 }
 
 // Columns returns a list of Meta names in response
 func (r *StreamRows) Columns() []string {
-	// TODO: implement this method
-	return nil
+	numColumns := len(r.columns)
+	result := make([]string, 0, numColumns)
+
+	for _, column := range r.columns {
+		result = append(result, column.Name)
+	}
+
+	return result
 }
 
 // Close makes the rows unusable
 func (r *StreamRows) Close() error {
-	// TODO: implement this method
+	for i := r.resultSetPosition; i < len(r.responses); i++ {
+		err := r.responses[i].Body().Close()
+		if err != nil {
+			return errorUtils.ConstructNestedError("Error closing response body:", err)
+		}
+	}
+	r.resultSetPosition = len(r.responses)
+	r.dataBuffer = nil
+	r.dataBufferCursor = 0
+	r.consumedResponse = true
+	return nil
+}
+
+func (r *StreamRows) bufferHasMoreData() bool {
+	return r.dataBuffer != nil && r.dataBufferCursor < len(r.dataBuffer)
+}
+
+func (r *StreamRows) populateDataBuffer() error {
+	nextRecord, err := r.readJsonLine()
+	if err == io.EOF {
+		r.consumedResponse = true
+		r.dataBuffer = [][]interface{}{}
+		r.dataBufferCursor = 0
+		return nil
+	}
+	if err != nil {
+		return errorUtils.ConstructNestedError("Error reading JSON line:", err)
+	}
+	if nextRecord.MessageType == types.MessageTypeError {
+		errors := make([]types.ErrorDetails, 0)
+		if nextRecord.Errors != nil {
+			errors = *nextRecord.Errors
+		}
+		r.consumedResponse = true
+		return errorUtils.NewStructuredError(errors)
+	} else if nextRecord.MessageType == types.MessageTypeSuccess {
+		r.consumedResponse = true
+		return io.EOF
+	} else {
+		if nextRecord.MessageType != types.MessageTypeData {
+			return fmt.Errorf("unexpected message type returned from the server %s", nextRecord.MessageType)
+		}
+		r.dataBuffer = *nextRecord.Data
+		r.dataBufferCursor = 0
+	}
 	return nil
 }
 
 // Next fetches the values of the next row, returns io.EOF if it was the end
 func (r *StreamRows) Next(dest []driver.Value) error {
-	// TODO: implement this method
+	if r.consumedResponse {
+		return io.EOF
+	}
+	if !r.bufferHasMoreData() {
+		if err := r.populateDataBuffer(); err != nil {
+			r.consumedResponse = true
+			return err
+		}
+	}
+	// We populated the buffer, but it's still empty
+	if r.dataBufferCursor == len(r.dataBuffer) {
+		return io.EOF
+	}
+
+	for i, column := range r.columns {
+		var err error
+		if dest[i], err = parseValue(column.Type, r.dataBuffer[r.dataBufferCursor][i]); err != nil {
+			return errorUtils.ConstructNestedError("error during fetching Next result", err)
+		}
+	}
+	r.dataBufferCursor++
 	return nil
 }
 
 // HasNextResultSet reports whether there is another result set available
 func (r *StreamRows) HasNextResultSet() bool {
-	// TODO: implement this method
-	return false
+	return r.resultSetPosition < len(r.responses)-1
+}
+
+func (r *StreamRows) fetchColumns() error {
+	if startRecord, err := r.readJsonLine(); err == io.EOF {
+		r.columns = []types.Column{}
+		return nil
+	} else if err != nil {
+		return errorUtils.ConstructNestedError("Error reading JSON line:", err)
+	} else if startRecord.MessageType != types.MessageTypeStart {
+		return fmt.Errorf("unexpected first message type returned from the server %s", startRecord.MessageType)
+	} else if startRecord.ResultColumns == nil {
+		return fmt.Errorf("no columns metadata returned from the server")
+	} else {
+		r.columns = *startRecord.ResultColumns
+	}
+	return nil
 }
 
 // NextResultSet advances to the next result set, if it is available, otherwise returns io.EOF
 func (r *StreamRows) NextResultSet() error {
-	// TODO: implement this method
+	err := r.responses[r.resultSetPosition].Body().Close()
+	if err != nil {
+		return errorUtils.ConstructNestedError("Error closing response body:", err)
+	}
+
+	if r.resultSetPosition+1 == len(r.responses) {
+		return io.EOF
+	}
+
+	r.resultSetPosition++
+	r.rowReader = nil
+	r.dataBuffer = nil
+	r.dataBufferCursor = 0
+	r.consumedResponse = false
+
+	return r.fetchColumns()
+}
+
+// AppendResponse appends a response to the list of row streams
+func (r *StreamRows) ProcessAndAppendResponse(response *client.Response) error {
+	r.responses = append(r.responses, response)
+	if r.columns == nil {
+		return r.fetchColumns()
+	}
 	return nil
 }
diff --git a/rows/stream_rows_test.go b/rows/stream_rows_test.go
new file mode 100644
index 00000000..f69ee1fd
--- /dev/null
+++ b/rows/stream_rows_test.go
@@ -0,0 +1,147 @@
+package rows
+
+import (
+	"bytes"
+	"database/sql/driver"
+	"encoding/json"
+	"io"
+	"log"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/firebolt-db/firebolt-go-sdk/client"
+	"github.com/firebolt-db/firebolt-go-sdk/types"
+	"github.com/firebolt-db/firebolt-go-sdk/utils"
+)
+
+const queryId = "16FF2A0300ECA753"
+
+func mockStreamRows(isMultiStatement bool) driver.RowsNextResultSet {
+
+	rows := &StreamRows{}
+	var responseFiles = []string{"fixtures/result1.jsonl", "fixtures/result2.jsonl"}
+	for i := 0; i < 2; i += 1 {
+		resultJson, err := os.ReadFile(responseFiles[i])
+		if err != nil {
+			log.Fatalf("Error reading file: %v", err)
+		}
+		if i != 0 && !isMultiStatement {
+			break
+		}
+		reader := io.NopCloser(bytes.NewReader(resultJson))
+		must(rows.ProcessAndAppendResponse(client.MakeResponse(reader, 200, nil, nil)))
+	}
+
+	return rows
+}
+
+func must(err error) {
+	if err != nil {
+		log.Fatalf("Error: %v", err)
+	}
+}
+
+func stringPrt(s string) *string {
+	return &s
+}
+
+func mockStreamRowsSingleValue(value interface{}, columnType string) driver.RowsNextResultSet {
+	response := []types.JSONLinesRecord{
+		{
+			MessageType:   types.MessageTypeStart,
+			ResultColumns: &[]types.Column{{Name: "single_col", Type: columnType}},
+			QueryID:       stringPrt(queryId),
+		},
+		{
+			MessageType: types.MessageTypeData,
+			Data:        &[][]interface{}{{value}},
+		},
+		{
+			MessageType: types.MessageTypeSuccess,
+			Statistics:  nil,
+		},
+	}
+
+	rows := &StreamRows{}
+	responseBody := strings.Builder{}
+	for _, record := range response {
+		jsonData, err := json.Marshal(record)
+		if err != nil {
+			log.Fatalf("Error marshaling JSON: %v", err)
+		}
+		responseBody.WriteString(string(jsonData) + "\n")
+	}
+	reader := io.NopCloser(bytes.NewReader([]byte(responseBody.String())))
+	must(rows.ProcessAndAppendResponse(client.MakeResponse(reader, 200, nil, nil)))
+	// Convert them to json lines
+
+	return rows
+}
+
+// testRowsColumns checks, that correct column names are returned
+func TestStreamRowsColumns(t *testing.T) {
+	testRowsColumns(t, mockStreamRows)
+}
+
+// testRowsClose checks Close method, and inability to use rows afterward
+func TestStreamRowsClose(t *testing.T) {
+	testRowsClose(t, mockStreamRows)
+
+}
+
+// testRowsNext check Next method
+func TestStreamRowsNext(t *testing.T) {
+	testRowsNext(t, mockStreamRows)
+}
+
+// testRowsNextSet check rows with multiple statements
+func TestStreamRowsNextSet(t *testing.T) {
+	testRowsNextSet(t, mockStreamRows)
+}
+
+func TestStreamRowsNextStructError(t *testing.T) {
+	testRowsNextStructError(t, mockStreamRows)
+}
+
+func TestStreamRowsNextStructWithNestedSpaces(t *testing.T) {
+	testRowsNextStructWithNestedSpaces(t, mockStreamRows)
+}
+
+func TestStreamRowsQuotedLong(t *testing.T) {
+	testRowsQuotedLong(t, mockStreamRowsSingleValue)
+}
+
+func TestStreamRowsDecimalType(t *testing.T) {
+	testRowsDecimalType(t, mockStreamRowsSingleValue)
+}
+
+func TestStreamRowsError(t *testing.T) {
+	rows := &StreamRows{}
+	responseFile := "fixtures/error.jsonl"
+	resultJson, err := os.ReadFile(responseFile)
+	if err != nil {
+		log.Fatalf("Error reading file: %v", err)
+	}
+	reader := io.NopCloser(bytes.NewReader(resultJson))
+	must(rows.ProcessAndAppendResponse(client.MakeResponse(reader, 200, nil, nil)))
+
+	dest := make([]driver.Value, 1)
+
+	// Check that the error is returned on third row
+	for i := 0; i < 2; i += 1 {
+		err = rows.Next(dest)
+		if err != nil {
+			t.Errorf("Unexpected error: %v", err)
+		}
+	}
+	err = rows.Next(dest)
+	if err == nil {
+		t.Errorf("Expected error, got nil")
+	}
+
+	// rows are closed now
+	utils.AssertEqual(rows.Next(dest), io.EOF, t, "Expected io.EOF")
+	utils.AssertEqual(rows.HasNextResultSet(), false, t, "Expected false for HasNextResultSet")
+	utils.AssertEqual(rows.NextResultSet(), io.EOF, t, "Expected io.EOF for NextResultSet")
+}
diff --git a/types/response.go b/types/json_compact.go
similarity index 100%
rename from types/response.go
rename to types/json_compact.go
diff --git a/types/json_compact_lines.go b/types/json_compact_lines.go
new file mode 100644
index 00000000..1f11a746
--- /dev/null
+++ b/types/json_compact_lines.go
@@ -0,0 +1,22 @@
+package types
+
+type RecordMessageType string
+
+const (
+	MessageTypeStart   RecordMessageType = "START"
+	MessageTypeData    RecordMessageType = "DATA"
+	MessageTypeSuccess RecordMessageType = "FINISHED_WITH_SUCCESS"
+	MessageTypeError   RecordMessageType = "FINISH_WITH_ERROR"
+)
+
+// JSONLinesRecord is a struct that represents any of the possible JSONLines records
+type JSONLinesRecord struct {
+	MessageType   RecordMessageType `json:"message_type"`
+	ResultColumns *[]Column         `json:"result_columns,omitempty"`
+	QueryID       *string           `json:"query_id,omitempty"`
+	QueryLabel    *string           `json:"query_label,omitempty"`
+	RequestID     *string           `json:"request_id,omitempty"`
+	Data          *[][]interface{}  `json:"data,omitempty"`
+	Errors        *[]ErrorDetails   `json:"errors,omitempty"`
+	Statistics    *interface{}      `json:"statistics,omitempty"`
+}
diff --git a/utils/test_utils.go b/utils/test_utils.go
index c90db853..e6781ee9 100644
--- a/utils/test_utils.go
+++ b/utils/test_utils.go
@@ -2,12 +2,17 @@ package utils
 
 import (
 	"bytes"
+	"context"
 	"database/sql/driver"
+	"runtime"
 	"runtime/debug"
 	"strconv"
+	"strings"
 	"testing"
 	"time"
 
+	contextUtils "github.com/firebolt-db/firebolt-go-sdk/context"
+
 	"github.com/shopspring/decimal"
 )
 
@@ -89,3 +94,20 @@ func GetAuthResponse(expiry int) []byte {
 }`
 	return []byte(response)
 }
+
+func getCallerFunctionName() string {
+	caller, _, _, ok := runtime.Caller(2)
+	if !ok {
+		panic("Failed to get caller function name")
+	}
+	strs := strings.Split(runtime.FuncForPC(caller).Name(), ".")
+	return strs[len(strs)-1]
+}
+
+// RunInMemoryAndStream runs a test case with both in memory result and streamed result
+func RunInMemoryAndStream(t *testing.T, testCase func(t *testing.T, ctx context.Context)) {
+	ctx := context.Background()
+	testName := getCallerFunctionName()
+	t.Run(testName+"InMemory", func(t *testing.T) { testCase(t, ctx) })
+	t.Run(testName+"Streaming", func(t *testing.T) { testCase(t, contextUtils.WithStreaming(ctx)) })
+}


Reviews:
user: login: bogdantruta-firebolt
body: LGTM
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T13:43:35Z
user: login: ptiurin
body: Also, let's add an example to the readme docs on how to enable streaming and read data.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T16:56:50Z
user: login: stepansergeevitch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T09:27:59Z
user: login: stepansergeevitch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T09:31:39Z
user: login: stepansergeevitch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T09:34:48Z
user: login: stepansergeevitch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T09:47:57Z
user: login: ptiurin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T10:25:52Z
user: login: stepansergeevitch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T09:08:36Z
user: login: ptiurin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T09:28:07Z
user: login: stepansergeevitch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T14:17:09Z
user: login: ptiurin
body: Final comment, otherwise lgtm.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T14:19:51Z

Review comments:
diff_hunk: @@ -145,18 +141,25 @@ func (c *BaseClient) processResponseHeaders(headers http.Header, control Connect
 	return nil
 }
 
-// request fetches an access token from the cache or re-authenticate when the access token is not available in the cache
+func (c *BaseClient) getOutputFormat(ctx context.Context) string {
+	if contextUtils.IsStreaming(ctx) && c.IsNewVersion() {
+		return jsonLinesOutputFormat
+	}
+	return jsonOutputFormat
+}
path: client/client_base.go
user: login: ptiurin
body: Do we need `c.IsNewVersion()` here at all, since in v0 client we have `jsonOutputFormat` hardcoded in `getQueryParams`?
created_at: 2025-03-03T15:38:39Z
author_association: CONTRIBUTOR
path: connection_integration_test.go
user: login: ptiurin
body: Looks like this test doesn't have changes apart from moving up a bit? If that's the case, let's revert this to avoid unnecessary diffs.
created_at: 2025-03-03T15:42:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -406,3 +436,39 @@ func TestLongQuery(t *testing.T) {
 		t.Errorf("Long query didn't finish in 10 minutes")
 	}
 }
+
+func TestStreamMultipleDataBlocks(t *testing.T) {
+	conn, err := sql.Open("firebolt", dsnMock)
+	if err != nil {
+		t.Errorf(OPEN_CONNECTION_ERROR_MSG)
+		t.FailNow()
+	}
+
+	query := "SELECT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' FROM generate_series(0, 100000)"
+
+	rows, err := conn.QueryContext(contextUtils.WithStreaming(context.Background()), query)
+
+	if err != nil {
+		t.Errorf(STATEMENT_ERROR_MSG, err)
+		t.FailNow()
+	}
+
+	cnt := 100001
+	var dest string
+
+	for i := 0; i < cnt; i++ {
+		if !rows.Next() {
+			t.Errorf("Next() call returned false with error: %v", rows.Err())
+			t.FailNow()
+		}
+		err = rows.Scan(&dest)
+		if err != nil {
+			t.Errorf(SCAN_STATEMENT_ERROR_MSG, err)
+		}
+	}
path: connection_common_integration_test.go
user: login: ptiurin
body: a small enhancement to improve this test would be to check that we've read the correct data. e.g. `dest == aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`
created_at: 2025-03-03T15:47:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,8 +5,11 @@ package fireboltgosdk
 
 import (
 	"context"
+	"database/sql/driver"
 	"testing"
 
+	rows2 "github.com/firebolt-db/firebolt-go-sdk/rows"
path: driver_options_integration_test.go
user: login: ptiurin
body: Perhaps `rowsImport` or `fireboltRows`?
created_at: 2025-03-03T15:49:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,37 +1,193 @@
 package rows
 
-import "database/sql/driver"
+import (
+	"bufio"
+	"bytes"
+	"database/sql/driver"
+	"encoding/json"
+	"fmt"
+	"io"
+
+	"github.com/firebolt-db/firebolt-go-sdk/client"
+	errorUtils "github.com/firebolt-db/firebolt-go-sdk/errors"
+	"github.com/firebolt-db/firebolt-go-sdk/types"
+)
 
 type StreamRows struct {
-	// TODO: add fields
+	responses         []*client.Response
+	resultSetPosition int
+	// current row
+	columns          []types.Column
+	rowReader        *bufio.Reader
+	dataBuffer       [][]interface{}
+	dataBufferCursor int
+	consumedResponse bool
+}
+
+func (r *StreamRows) readJsonLine() (types.JSONLinesRecord, error) {
+	reader := r.reader()
+
+	var record types.JSONLinesRecord
+	rawJsonLine, err := reader.ReadBytes('\n')
+	if err == io.EOF || rawJsonLine == nil {
+		return types.JSONLinesRecord{}, io.EOF
+	}
+	if err != nil {
+		return record, errorUtils.ConstructNestedError("Error reading JSON line:", err)
+	}
+	err = json.Unmarshal(rawJsonLine, &record)
+	if err != nil {
+		return record, errorUtils.ConstructNestedError("JSON parse error:", err)
+	}
+	return record, nil
+}
+
+func (r *StreamRows) reader() *bufio.Reader {
+	if r.rowReader == nil {
+		body := r.responses[r.resultSetPosition].Body()
+		if body != nil {
+			r.rowReader = bufio.NewReader(body)
+		} else {
+			r.rowReader = bufio.NewReader(io.NopCloser(bytes.NewReader([]byte{})))
+		}
+	}
+	return r.rowReader
 }
 
 // Columns returns a list of Meta names in response
 func (r *StreamRows) Columns() []string {
-	// TODO: implement this method
-	return nil
+	numColumns := len(r.columns)
+	result := make([]string, 0, numColumns)
+
+	for _, column := range r.columns {
+		result = append(result, column.Name)
+	}
+
+	return result
 }
 
 // Close makes the rows unusable
 func (r *StreamRows) Close() error {
-	// TODO: implement this method
+	for i := r.resultSetPosition; i < len(r.responses); i++ {
+		err := r.responses[i].Body().Close()
+		if err != nil {
+			return errorUtils.ConstructNestedError("Error closing response body:", err)
+		}
+	}
+	r.resultSetPosition = len(r.responses)
+	r.dataBuffer = nil
+	r.dataBufferCursor = 0
+	r.consumedResponse = true
+	return nil
+}
+
+func (r *StreamRows) bufferHasMoreData() bool {
+	return r.dataBuffer != nil && r.dataBufferCursor < len(r.dataBuffer)
+}
+
+func (r *StreamRows) populateDataBuffer() error {
+	nextRecord, err := r.readJsonLine()
+	if err == io.EOF {
+		r.consumedResponse = true
+		r.dataBuffer = [][]interface{}{}
+		r.dataBufferCursor = 0
+		return nil
+	}
+	if err != nil {
+		return errorUtils.ConstructNestedError("Error reading JSON line:", err)
+	}
+	if nextRecord.MessageType == types.MessageTypeError {
+		errors := make([]types.ErrorDetails, 0)
+		if nextRecord.Errors != nil {
+			errors = *nextRecord.Errors
+		}
+		r.consumedResponse = true
+		return errorUtils.NewStructuredError(errors)
+	} else if nextRecord.MessageType == types.MessageTypeSuccess {
+		r.consumedResponse = true
+		return io.EOF
+	} else {
+		if nextRecord.MessageType != types.MessageTypeData {
+			return fmt.Errorf("unexpected message type returned from the server %s", nextRecord.MessageType)
+		}
+		r.dataBuffer = *nextRecord.Data
+		r.dataBufferCursor = 0
+	}
 	return nil
 }
 
 // Next fetches the values of the next row, returns io.EOF if it was the end
 func (r *StreamRows) Next(dest []driver.Value) error {
-	// TODO: implement this method
+	if r.consumedResponse {
+		return io.EOF
+	}
+	if !r.bufferHasMoreData() {
+		if err := r.populateDataBuffer(); err != nil {
+			r.consumedResponse = true
+			return err
+		}
+	}
+	// We populated the buffer, but it's still empty
+	if r.dataBufferCursor == len(r.dataBuffer) {
+		return io.EOF
+	}
+
+	for i, column := range r.columns {
+		var err error
+		//log.Printf("Rows.Next: %s, %v", column.Type, r.queryResponses.Data[r.cursorPosition][i])
path: rows/stream_rows.go
user: login: ptiurin
body: let's remove the prints
created_at: 2025-03-03T16:15:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,28 +76,28 @@ func (c *fireboltConnection) queryContextInternal(ctx context.Context, query str
 		return nil, fmt.Errorf("multistatement is not allowed")
 	}
 
-	var rows rows.InMemoryRows
+	var rowsInst = c.makeRows(ctx)
+
 	for _, query := range queries {
 		if isSetStatement, err := processSetStatement(ctx, c, query); isSetStatement {
-			if err == nil {
-				rows.AppendResponse(types.QueryResponse{})
-				continue
-			} else {
-				return &rows, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			if err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			} else if err = rowsInst.AppendResponse(client.MakeResponse(nil, 200, nil, nil)); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
path: connection.go
user: login: ptiurin
body: nit: not error during query execution, but error appending or processing response? I was trying to make sense on what is actually happening here for some time.
created_at: 2025-03-03T16:42:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,6 +25,7 @@ func MakeClientV0(settings *types.FireboltSettings, apiEndpoint string) (*Client
 			ClientSecret: settings.ClientSecret,
 			ApiEndpoint:  apiEndpoint,
 			UserAgent:    ConstructUserAgentString(),
+			newVersion:   settings.NewVersion,
path: client/client_v0.go
user: login: ptiurin
body: I don't feel too strongly about it, but what do you think about storing settings as a part of the client? This way when NewVersion is discontinued it's easier to remove and also add any additional settings without the need to explicitly propagate them to the base class.
created_at: 2025-03-03T16:53:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -145,18 +141,25 @@ func (c *BaseClient) processResponseHeaders(headers http.Header, control Connect
 	return nil
 }
 
-// request fetches an access token from the cache or re-authenticate when the access token is not available in the cache
+func (c *BaseClient) getOutputFormat(ctx context.Context) string {
+	if contextUtils.IsStreaming(ctx) && c.IsNewVersion() {
+		return jsonLinesOutputFormat
+	}
+	return jsonOutputFormat
+}
path: client/client_base.go
user: login: stepansergeevitch
body: Good point, we can remove it
created_at: 2025-03-04T09:27:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,28 +76,28 @@ func (c *fireboltConnection) queryContextInternal(ctx context.Context, query str
 		return nil, fmt.Errorf("multistatement is not allowed")
 	}
 
-	var rows rows.InMemoryRows
+	var rowsInst = c.makeRows(ctx)
+
 	for _, query := range queries {
 		if isSetStatement, err := processSetStatement(ctx, c, query); isSetStatement {
-			if err == nil {
-				rows.AppendResponse(types.QueryResponse{})
-				continue
-			} else {
-				return &rows, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			if err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			} else if err = rowsInst.AppendResponse(client.MakeResponse(nil, 200, nil, nil)); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
path: connection.go
user: login: stepansergeevitch
body: Not sure I fully understand your comment. The error can happen in both query request and appending the response to rows. 
It's because inMemory read the response in the AppendResponse call, and can find a structured error there. While streaming rows just save the response stream and read it on demand
Maybe you can show the example of how do you want this changed?
created_at: 2025-03-04T09:31:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,6 +25,7 @@ func MakeClientV0(settings *types.FireboltSettings, apiEndpoint string) (*Client
 			ClientSecret: settings.ClientSecret,
 			ApiEndpoint:  apiEndpoint,
 			UserAgent:    ConstructUserAgentString(),
+			newVersion:   settings.NewVersion,
path: client/client_v0.go
user: login: stepansergeevitch
body: I would rather not do this. The reason is I believe we should keep the client as 'thin' as possible. This way we always know what exactly does a client depend on and if this dependency is gone we can safely remove the field. Let me know WDYT
created_at: 2025-03-04T09:34:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,8 +5,11 @@ package fireboltgosdk
 
 import (
 	"context"
+	"database/sql/driver"
 	"testing"
 
+	rows2 "github.com/firebolt-db/firebolt-go-sdk/rows"
path: driver_options_integration_test.go
user: login: stepansergeevitch
body: Not sure why it does it. It should be just `rows`. Renaming 
created_at: 2025-03-04T09:47:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,28 +76,28 @@ func (c *fireboltConnection) queryContextInternal(ctx context.Context, query str
 		return nil, fmt.Errorf("multistatement is not allowed")
 	}
 
-	var rows rows.InMemoryRows
+	var rowsInst = c.makeRows(ctx)
+
 	for _, query := range queries {
 		if isSetStatement, err := processSetStatement(ctx, c, query); isSetStatement {
-			if err == nil {
-				rows.AppendResponse(types.QueryResponse{})
-				continue
-			} else {
-				return &rows, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			if err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			} else if err = rowsInst.AppendResponse(client.MakeResponse(nil, 200, nil, nil)); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
path: connection.go
user: login: ptiurin
body: Ok, I see. AppendResponse also parses the contents. This is what confused me. Perhaps we can rename `AppendResponse` into `ProcessAndAppend`(Response) or `ValidateAndAppend` for clarity?
created_at: 2025-03-04T10:25:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,28 +76,28 @@ func (c *fireboltConnection) queryContextInternal(ctx context.Context, query str
 		return nil, fmt.Errorf("multistatement is not allowed")
 	}
 
-	var rows rows.InMemoryRows
+	var rowsInst = c.makeRows(ctx)
+
 	for _, query := range queries {
 		if isSetStatement, err := processSetStatement(ctx, c, query); isSetStatement {
-			if err == nil {
-				rows.AppendResponse(types.QueryResponse{})
-				continue
-			} else {
-				return &rows, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			if err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			} else if err = rowsInst.AppendResponse(client.MakeResponse(nil, 200, nil, nil)); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
path: connection.go
user: login: stepansergeevitch
body: The thing is that parsing is only done for InMemory rows, StreamRows simply append the stream, so mentioning validation can make it confusing. I've added  comment for each function that would hopefully clarify it
created_at: 2025-03-05T09:08:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,28 +76,28 @@ func (c *fireboltConnection) queryContextInternal(ctx context.Context, query str
 		return nil, fmt.Errorf("multistatement is not allowed")
 	}
 
-	var rows rows.InMemoryRows
+	var rowsInst = c.makeRows(ctx)
+
 	for _, query := range queries {
 		if isSetStatement, err := processSetStatement(ctx, c, query); isSetStatement {
-			if err == nil {
-				rows.AppendResponse(types.QueryResponse{})
-				continue
-			} else {
-				return &rows, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			if err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			} else if err = rowsInst.AppendResponse(client.MakeResponse(nil, 200, nil, nil)); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
path: connection.go
user: login: ptiurin
body: I feel like it's a design problem then. When reading through the code it's not obvious that append response method would throw execution errors. Unless we remember how this works (and in a few weeks who knows if we do) runtime errors might come as a surprise. Let's try to make it more bullet-proof.
created_at: 2025-03-05T09:28:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,28 +76,28 @@ func (c *fireboltConnection) queryContextInternal(ctx context.Context, query str
 		return nil, fmt.Errorf("multistatement is not allowed")
 	}
 
-	var rows rows.InMemoryRows
+	var rowsInst = c.makeRows(ctx)
+
 	for _, query := range queries {
 		if isSetStatement, err := processSetStatement(ctx, c, query); isSetStatement {
-			if err == nil {
-				rows.AppendResponse(types.QueryResponse{})
-				continue
-			} else {
-				return &rows, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			if err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("statement recognized as an invalid set statement", err)
+			} else if err = rowsInst.AppendResponse(client.MakeResponse(nil, 200, nil, nil)); err != nil {
+				return rowsInst, errorUtils.ConstructNestedError("error during query execution", err)
path: connection.go
user: login: stepansergeevitch
body: Agreed on the new name `ProcessAndAppendResponse`
created_at: 2025-03-05T14:17:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +14,19 @@ Firebolt GO driver is an implementation of `database/sql/driver`.
 go get github.com/firebolt-db/firebolt-go-sdk
 ```
 
-### Example
+### DSN (Data source name)
+All information for the connection should be specified using the DSN string. The firebolt dsn string has the following format:
+```
+firebolt://[/database]?account_name=account_name&client_id=client_id&client_secret=client_secret[&engine=engine]
+```
+
+- **client_id** - credentials client id.
+- **client_secret** - credentials client secret.
+- **account_name** - the name of Firebolt account to log in to.
+- **database** - (optional) the name of the database to connect to.
+- **engine** - (optional) the name of the engine to run SQL on.
+
path: README.md
user: login: ptiurin
body: Let's stress that client id and secret are coming from service account and link the docs. We've already had someone stumbling on it.
created_at: 2025-03-05T14:19:37Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-05T14:53:11Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=firebolt-db_firebolt-go-sdk&pullRequest=130) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=firebolt-db_firebolt-go-sdk&pullRequest=130&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=firebolt-db_firebolt-go-sdk&pullRequest=130&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=firebolt-db_firebolt-go-sdk&pullRequest=130&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=firebolt-db_firebolt-go-sdk&pullRequest=130&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=firebolt-db_firebolt-go-sdk&pullRequest=130&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=firebolt-db_firebolt-go-sdk&pullRequest=130)


author_association: NONE