From 7338bca793706e2dc0fc22c2b96c68f62420c8e4 Mon Sep 17 00:00:00 2001 From: Yury Dzerin Date: Mon, 8 Aug 2022 15:19:50 +0200 Subject: [PATCH 1/3] feat: add support of nil --- utils.go | 2 ++ utils_test.go | 1 + 2 files changed, 3 insertions(+) diff --git a/utils.go b/utils.go index 8682fd0..b0d5b0f 100644 --- a/utils.go +++ b/utils.go @@ -90,6 +90,8 @@ func formatValue(value driver.Value) (string, error) { } case time.Time: return fmt.Sprintf("'%s'", value.(time.Time).Format("2006-01-02 15:04:05")), nil + case nil: + return "NULL", nil default: return "", fmt.Errorf("not supported type: %v", v) } diff --git a/utils_test.go b/utils_test.go index 0856789..d08a99d 100644 --- a/utils_test.go +++ b/utils_test.go @@ -107,6 +107,7 @@ func TestFormatValue(t *testing.T) { runTestFormatValue(t, true, "1") runTestFormatValue(t, false, "0") runTestFormatValue(t, -10, "-10") + runTestFormatValue(t, nil, "NULL") runTestFormatValue(t, time.Date(2022, 01, 10, 1, 3, 2, 0, loc), "'2022-01-10 01:03:02'") // not passing, but should: runTestFormatValue(t, 1.1234567, "1.1234567") From 1bdace2a42374a03d5ccc0d2e800cd0d634381f8 Mon Sep 17 00:00:00 2001 From: Yury Dzerin Date: Wed, 10 Aug 2022 09:58:06 +0200 Subject: [PATCH 2/3] fix: add test for nullable type; make insert into work and test for it --- client.go | 5 +++++ connection_test.go | 22 ++++++++++++++++++++++ rows.go | 9 +++++++++ rows_test.go | 4 ++-- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 825f5f0..83a59a0 100644 --- a/client.go +++ b/client.go @@ -170,6 +170,11 @@ func (c *Client) Query(engineUrl, databaseName, query string, setStatements *map return ConstructNestedError("error during query request", err) } + if len(response) == 0 { + // response could be empty, which doesn't mean it is an error + return nil + } + if err = json.Unmarshal(response, &queryResponse); err != nil { return ConstructNestedError("wrong response", errors.New(string(response))) } diff --git a/connection_test.go b/connection_test.go index 36c0bea..1752916 100644 --- a/connection_test.go +++ b/connection_test.go @@ -64,6 +64,28 @@ func TestConnectionQueryWrong(t *testing.T) { } } +// TestConnectionInsertQuery checks simple Insert works +func TestConnectionInsertQuery(t *testing.T) { + markIntegrationTest(t) + conn := fireboltConnection{clientMock, databaseMock, engineUrlMock, map[string]string{}} + 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, nil); err != nil { + t.Errorf("statement returned an error: %v", err) + } + if _, err := conn.ExecContext(context.TODO(), "SET firebolt_dont_wait_for_upload_to_s3=1", nil); err != nil { + t.Errorf("statement returned an error: %v", err) + } + if _, err := conn.ExecContext(context.TODO(), insertSQL, nil); err != nil { + t.Errorf("statement returned an error: %v", err) + } + if _, err := conn.ExecContext(context.TODO(), deleteTableSQL, nil); err != nil { + t.Errorf("statement returned an error: %v", err) + } +} + // TestConnectionQuery checks simple SELECT query func TestConnectionQuery(t *testing.T) { markIntegrationTest(t) diff --git a/rows.go b/rows.go index 7d72e9c..2a89bbd 100644 --- a/rows.go +++ b/rows.go @@ -40,6 +40,7 @@ func (f *fireboltRows) Next(dest []driver.Value) error { for i, column := range f.response.Meta { var err error + //log.Printf("Rows.Next: %s, %v", column.Type, f.response.Data[f.cursorPosition][i]) if dest[i], err = parseValue(column.Type, f.response.Data[f.cursorPosition][i]); err != nil { return ConstructNestedError("error during fetching Next result", err) } @@ -90,6 +91,7 @@ func parseSingleValue(columnType string, val interface{}) (driver.Value, error) // uint8, uint32, uint64, int32, int64, float32, float64, string, Time or []driver.Value for arrays func parseValue(columnType string, val interface{}) (driver.Value, error) { const ( + nullablePrefix = "Nullable(" arrayPrefix = "Array(" dateTime64Prefix = "DateTime64(" decimalPrefix = "Decimal(" @@ -108,6 +110,13 @@ func parseValue(columnType string, val interface{}) (driver.Value, error) { return parseSingleValue("DateTime64", val) } else if strings.HasPrefix(columnType, decimalPrefix) && strings.HasSuffix(columnType, suffix) { return parseSingleValue("Float64", val) + } else if strings.HasPrefix(columnType, nullablePrefix) && strings.HasSuffix(columnType, suffix) { + if val == nil { + return nil, nil + } + + return parseSingleValue(columnType[len(nullablePrefix):len(columnType)-len(suffix)], val) } + return parseSingleValue(columnType, val) } diff --git a/rows_test.go b/rows_test.go index 084ce65..2320be4 100644 --- a/rows_test.go +++ b/rows_test.go @@ -16,7 +16,7 @@ func assert(val bool, t *testing.T, err string) { } func mockRows() driver.Rows { - resultJson := "{\"query\":{\"query_id\":\"16FF2A0300ECA753\"},\"meta\":[{\"name\":\"int_col\",\"type\":\"Int32\"},{\"name\":\"bigint_col\",\"type\":\"Int64\"},{\"name\":\"float_col\",\"type\":\"Float32\"},{\"name\":\"double_col\",\"type\":\"Float64\"},{\"name\":\"text_col\",\"type\":\"String\"},{\"name\":\"date_col\",\"type\":\"Date\"},{\"name\":\"timestamp_col\",\"type\":\"DateTime\"},{\"name\":\"bool_col\",\"type\":\"UInt8\"},{\"name\":\"array_col\",\"type\":\"Array(Int32)\"},{\"name\":\"nested_array_col\",\"type\":\"Array(Array(String))\"}],\"data\":[[2,1,0.312321,123213.321321,\"text\",\"2080-12-31\",\"1989-04-15 01:02:03\",1,[1,2,3],[[]]],[2,1,0.312321,123213.321321,\"text\",\"1970-01-01\",\"1970-01-01 00:00:00\",1,[1,2,3],[[]]],[3,5,0.312321,123213.321321,\"text\",\"1970-01-01\",\"1970-01-01 00:00:00\",1,[5,2,3,2],[[\"TEST\",\"TEST1\"],[\"TEST3\"]]]],\"rows\":3,\"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}}" + resultJson := "{\"query\":{\"query_id\":\"16FF2A0300ECA753\"},\"meta\":[{\"name\":\"int_col\",\"type\":\"Nullable(Int32)\"},{\"name\":\"bigint_col\",\"type\":\"Int64\"},{\"name\":\"float_col\",\"type\":\"Float32\"},{\"name\":\"double_col\",\"type\":\"Float64\"},{\"name\":\"text_col\",\"type\":\"String\"},{\"name\":\"date_col\",\"type\":\"Date\"},{\"name\":\"timestamp_col\",\"type\":\"DateTime\"},{\"name\":\"bool_col\",\"type\":\"UInt8\"},{\"name\":\"array_col\",\"type\":\"Array(Int32)\"},{\"name\":\"nested_array_col\",\"type\":\"Array(Array(String))\"}],\"data\":[[null,1,0.312321,123213.321321,\"text\",\"2080-12-31\",\"1989-04-15 01:02:03\",1,[1,2,3],[[]]],[2,1,0.312321,123213.321321,\"text\",\"1970-01-01\",\"1970-01-01 00:00:00\",1,[1,2,3],[[]]],[3,5,0.312321,123213.321321,\"text\",\"1970-01-01\",\"1970-01-01 00:00:00\",1,[5,2,3,2],[[\"TEST\",\"TEST1\"],[\"TEST3\"]]]],\"rows\":3,\"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}}" var response QueryResponse err := json.Unmarshal([]byte(resultJson), &response) if err != nil { @@ -56,7 +56,7 @@ func TestRowsNext(t *testing.T) { loc, _ := time.LoadLocation("UTC") assert(err == nil, t, "Next shouldn't return an error") - assert(dest[0] == int32(2), t, "results not equal for int32") + assert(dest[0] == nil, t, "results not equal for int32") assert(dest[1] == int64(1), t, "results not equal for int64") assert(dest[2] == float32(0.312321), t, "results not equal for float32") assert(dest[3] == float64(123213.321321), t, "results not equal for float64") From 82ca4567c2da1ca6e469e621f88db9ff862ca3f7 Mon Sep 17 00:00:00 2001 From: Yury Dzerin Date: Wed, 10 Aug 2022 13:40:27 +0200 Subject: [PATCH 3/3] fix: add timezones for prepared statements --- utils.go | 2 +- utils_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/utils.go b/utils.go index b0d5b0f..7065777 100644 --- a/utils.go +++ b/utils.go @@ -89,7 +89,7 @@ func formatValue(value driver.Value) (string, error) { return "0", nil } case time.Time: - return fmt.Sprintf("'%s'", value.(time.Time).Format("2006-01-02 15:04:05")), nil + return fmt.Sprintf("'%s'", value.(time.Time).Format("2006-01-02 15:04:05 -07:00")), nil case nil: return "NULL", nil default: diff --git a/utils_test.go b/utils_test.go index d08a99d..0e2599b 100644 --- a/utils_test.go +++ b/utils_test.go @@ -95,7 +95,7 @@ func runTestFormatValue(t *testing.T, value driver.Value, expected string) { } func TestFormatValue(t *testing.T) { - loc, _ := time.LoadLocation("UTC") + loc, _ := time.LoadLocation("Europe/Berlin") runTestFormatValue(t, "", "''") runTestFormatValue(t, "abcd", "'abcd'") @@ -108,7 +108,7 @@ func TestFormatValue(t *testing.T) { runTestFormatValue(t, false, "0") runTestFormatValue(t, -10, "-10") runTestFormatValue(t, nil, "NULL") - runTestFormatValue(t, time.Date(2022, 01, 10, 1, 3, 2, 0, loc), "'2022-01-10 01:03:02'") + runTestFormatValue(t, time.Date(2022, 01, 10, 1, 3, 2, 0, loc), "'2022-01-10 01:03:02 +01:00'") // not passing, but should: runTestFormatValue(t, 1.1234567, "1.1234567") // not passing, but should: runTestFormatValue(t, 1.123, "1.123")