diff --git a/exec_test.go b/exec_test.go index bc8f2fb..33dff6c 100644 --- a/exec_test.go +++ b/exec_test.go @@ -179,14 +179,14 @@ CREATE TABLE table ( defer rows.Close() type queryRow struct { Value interface{} - FieldB []map[string]interface{} + FieldB map[string]interface{} FieldY string } var results []*queryRow for rows.Next() { var ( value interface{} - fieldB []map[string]interface{} + fieldB map[string]interface{} fieldY string ) if err := rows.Scan(&value, &fieldB, &fieldY); err != nil { diff --git a/internal/encoder.go b/internal/encoder.go index de43699..4b17096 100644 --- a/internal/encoder.go +++ b/internal/encoder.go @@ -472,18 +472,12 @@ func CastValue(t types.Type, v Value) (Value, error) { return nil, err } typ := t.AsStruct() - anonymousStruct := true - for _, key := range s.keys { - if key != "" { - anonymousStruct = false - } - } - if anonymousStruct { - return s, nil - } ret := &StructValue{m: s.m} for i := 0; i < typ.NumFields(); i++ { key := typ.Field(i).Name() + if key == "" { + key = fmt.Sprintf("_field_%d", i+1) + } value, exists := s.m[key] if !exists { ret.keys = append(ret.keys, key) diff --git a/internal/function.go b/internal/function.go index 3d9d101..8fc89ef 100644 --- a/internal/function.go +++ b/internal/function.go @@ -158,6 +158,12 @@ func STRUCT_FIELD(v Value, idx int) (Value, error) { if err != nil { return nil, err } + if sv == nil { + return nil, nil + } + if idx < 0 || idx >= len(sv.values) { + return nil, fmt.Errorf("struct field index %d out of range (struct has %d fields)", idx, len(sv.values)) + } return sv.values[idx], nil } @@ -403,6 +409,9 @@ func MAKE_STRUCT(args ...Value) (Value, error) { if err != nil { return nil, err } + if k == "" { + k = fmt.Sprintf("_field_%d", i+1) + } keys[i] = k values[i] = value fieldMap[k] = value diff --git a/internal/rows.go b/internal/rows.go index 80944d9..5ad0a96 100644 --- a/internal/rows.go +++ b/internal/rows.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "reflect" - "time" "github.com/goccy/go-json" "github.com/goccy/go-zetasql/types" @@ -232,74 +231,86 @@ func (r *Rows) assignInterfaceValue(src Value, dst reflect.Value, typ *Type) err } dst.Set(reflect.ValueOf(f64)) case types.BYTES: - s, err := src.ToString() + s, err := src.ToApiString() if err != nil { return err } dst.Set(reflect.ValueOf(s)) case types.STRING: - s, err := src.ToString() + s, err := src.ToApiString() if err != nil { return err } dst.Set(reflect.ValueOf(s)) case types.NUMERIC: - s, err := src.ToString() + s, err := src.ToApiString() if err != nil { return err } dst.Set(reflect.ValueOf(s)) case types.BIG_NUMERIC: - s, err := src.ToString() + s, err := src.ToApiString() if err != nil { return err } dst.Set(reflect.ValueOf(s)) case types.DATE: - date, err := src.ToJSON() + date, err := src.ToApiString() if err != nil { return err } dst.Set(reflect.ValueOf(date)) case types.DATETIME: - datetime, err := src.ToJSON() + datetime, err := src.ToApiString() if err != nil { return err } dst.Set(reflect.ValueOf(datetime)) case types.TIME: - time, err := src.ToJSON() + time, err := src.ToApiString() if err != nil { return err } dst.Set(reflect.ValueOf(time)) case types.TIMESTAMP: - t, err := src.ToTime() + t, err := src.ToApiString() if err != nil { return err } - unixmicro := t.UnixMicro() - sec := unixmicro / int64(time.Millisecond) - nsec := unixmicro - sec*int64(time.Millisecond) - dst.Set(reflect.ValueOf(fmt.Sprintf("%d.%d", sec, nsec))) + dst.Set(reflect.ValueOf(t)) case types.INTERVAL: - s, err := src.ToString() + s, err := src.ToApiString() if err != nil { return err } dst.Set(reflect.ValueOf(s)) case types.JSON: - json, err := src.ToJSON() + j, err := src.ToApiString() if err != nil { return err } - dst.Set(reflect.ValueOf(json)) + dst.Set(reflect.ValueOf(j)) case types.STRUCT: s, err := src.ToStruct() if err != nil { return err } - dst.Set(reflect.ValueOf(s.Interface())) + m := make(map[string]interface{}, len(s.keys)) + for i, key := range s.keys { + var child interface{} + if s.values[i] != nil { + fieldType := typ.FieldTypes[i].Type + if err := r.assignInterfaceValue( + s.values[i], + reflect.ValueOf(&child).Elem(), + fieldType, + ); err != nil { + return err + } + } + m[key] = child + } + dst.Set(reflect.ValueOf(m)) case types.ARRAY: array, err := src.ToArray() if err != nil { diff --git a/internal/value.go b/internal/value.go index 457362c..f8760ff 100644 --- a/internal/value.go +++ b/internal/value.go @@ -26,6 +26,7 @@ type Value interface { GTE(Value) (bool, error) LT(Value) (bool, error) LTE(Value) (bool, error) + ToApiString() (string, error) ToInt64() (int64, error) ToString() (string, error) ToBytes() ([]byte, error) @@ -125,6 +126,10 @@ func (iv IntValue) ToString() (string, error) { return fmt.Sprint(iv), nil } +func (iv IntValue) ToApiString() (string, error) { + return iv.ToString() +} + func (iv IntValue) ToBytes() ([]byte, error) { return []byte(fmt.Sprint(iv)), nil } @@ -256,6 +261,10 @@ func (sv StringValue) ToString() (string, error) { return string(sv), nil } +func (sv StringValue) ToApiString() (string, error) { + return sv.ToString() +} + func (sv StringValue) ToBytes() ([]byte, error) { return []byte(string(sv)), nil } @@ -397,6 +406,10 @@ func (bv BytesValue) ToInt64() (int64, error) { } func (bv BytesValue) ToString() (string, error) { + return string([]byte(bv)[:]), nil +} + +func (bv BytesValue) ToApiString() (string, error) { return base64.StdEncoding.EncodeToString([]byte(bv)), nil } @@ -579,6 +592,10 @@ func (fv FloatValue) ToString() (string, error) { return fmt.Sprint(fv), nil } +func (fv FloatValue) ToApiString() (string, error) { + return fv.ToString() +} + func (fv FloatValue) ToBytes() ([]byte, error) { return []byte(fmt.Sprint(fv)), nil } @@ -748,6 +765,10 @@ func (nv *NumericValue) ToString() (string, error) { return nv.toString(), nil } +func (nv *NumericValue) ToApiString() (string, error) { + return nv.toString(), nil +} + func (nv *NumericValue) ToBytes() ([]byte, error) { return []byte(nv.toString()), nil } @@ -848,6 +869,10 @@ func (bv BoolValue) ToString() (string, error) { return fmt.Sprint(bv), nil } +func (bv BoolValue) ToApiString() (string, error) { + return bv.ToString() +} + func (bv BoolValue) ToBytes() ([]byte, error) { return []byte(fmt.Sprint(bv)), nil } @@ -943,6 +968,10 @@ func (jv JsonValue) ToString() (string, error) { return string(jv), nil } +func (jv JsonValue) ToApiString() (string, error) { + return jv.ToString() +} + func (jv JsonValue) ToBytes() ([]byte, error) { return []byte(string(jv)), nil } @@ -1174,6 +1203,10 @@ func (av *ArrayValue) ToString() (string, error) { return fmt.Sprintf("[%s]", strings.Join(elems, ",")), nil } +func (av *ArrayValue) ToApiString() (string, error) { + return "", fmt.Errorf("arrays do not have string-based API representations %v", av) +} + func (av *ArrayValue) ToBytes() ([]byte, error) { v, err := av.ToString() if err != nil { @@ -1384,6 +1417,10 @@ func (sv *StructValue) ToString() (string, error) { return fmt.Sprintf("{%s}", strings.Join(fields, ",")), nil } +func (sv *StructValue) ToApiString() (string, error) { + return "", fmt.Errorf("Structs do not have string-based API representations %v", "") +} + func (sv *StructValue) ToBytes() ([]byte, error) { v, err := sv.ToString() if err != nil { @@ -1571,6 +1608,8 @@ func (d DateValue) ToString() (string, error) { return time.Time(d).Format("2006-01-02"), nil } +func (d DateValue) ToApiString() (string, error) { return d.ToString() } + func (d DateValue) ToBytes() ([]byte, error) { v, err := d.ToString() if err != nil { @@ -1596,7 +1635,8 @@ func (d DateValue) ToStruct() (*StructValue, error) { } func (d DateValue) ToJSON() (string, error) { - return d.ToString() + val, err := d.ToString() + return strconv.Quote(val), err } func (d DateValue) ToTime() (time.Time, error) { @@ -1623,7 +1663,8 @@ func (d DateValue) Interface() interface{} { } const ( - datetimeFormat = "2006-01-02T15:04:05.999999" + datetimeDefaultFormat = "2006-01-02T15:04:05.999999" + datetimeAsStringFormat = "2006-01-02 15:04:05.999999" ) type DatetimeValue time.Time @@ -1720,7 +1761,11 @@ func (d DatetimeValue) ToInt64() (int64, error) { } func (d DatetimeValue) ToString() (string, error) { - return time.Time(d).Format(datetimeFormat), nil + return time.Time(d).Format(datetimeAsStringFormat), nil +} + +func (d DatetimeValue) ToApiString() (string, error) { + return time.Time(d).Format(datetimeDefaultFormat), nil } func (d DatetimeValue) ToBytes() ([]byte, error) { @@ -1748,7 +1793,7 @@ func (d DatetimeValue) ToStruct() (*StructValue, error) { } func (d DatetimeValue) ToJSON() (string, error) { - return d.ToString() + return strconv.Quote(time.Time(d).Format(datetimeDefaultFormat)), nil } func (d DatetimeValue) ToTime() (time.Time, error) { @@ -1760,7 +1805,7 @@ func (d DatetimeValue) ToRat() (*big.Rat, error) { } func (d DatetimeValue) Format(verb rune) string { - formatted := time.Time(d).Format(datetimeFormat) + formatted := time.Time(d).Format(datetimeDefaultFormat) switch verb { case 't': return formatted @@ -1771,7 +1816,7 @@ func (d DatetimeValue) Format(verb rune) string { } func (d DatetimeValue) Interface() interface{} { - return time.Time(d).Format(datetimeFormat) + return time.Time(d).Format(datetimeDefaultFormat) } type TimeValue time.Time @@ -1840,6 +1885,10 @@ func (t TimeValue) ToString() (string, error) { return time.Time(t).Format("15:04:05.999999"), nil } +func (t TimeValue) ToApiString() (string, error) { + return t.ToString() +} + func (t TimeValue) ToBytes() ([]byte, error) { v, err := t.ToString() if err != nil { @@ -1865,7 +1914,11 @@ func (t TimeValue) ToStruct() (*StructValue, error) { } func (t TimeValue) ToJSON() (string, error) { - return t.ToString() + val, err := t.ToString() + if err != nil { + return "", err + } + return strconv.Quote(val), nil } func (t TimeValue) ToTime() (time.Time, error) { @@ -1893,6 +1946,10 @@ func (t TimeValue) Interface() interface{} { type TimestampValue time.Time +func (t TimestampValue) GetFormatString() string { + return "2006-01-02 15:04:05.999999-07" +} + func (t TimestampValue) AddValueWithPart(v time.Duration, part string) (Value, error) { switch part { case "MICROSECOND": @@ -2004,7 +2061,18 @@ func (t TimestampValue) ToInt64() (int64, error) { } func (t TimestampValue) ToString() (string, error) { - return time.Time(t).Format(time.RFC3339Nano), nil + return time.Time(t).Format("2006-01-02 15:04:05.999999-07"), nil +} + +func (t TimestampValue) ToApiString() (string, error) { + ti, err := t.ToTime() + if err != nil { + return "", err + } + unixmicro := ti.UnixMicro() + sec := unixmicro / int64(time.Millisecond) + nsec := unixmicro - sec*int64(time.Millisecond) + return fmt.Sprintf("%d.%d", sec, nsec), nil } func (t TimestampValue) ToBytes() ([]byte, error) { @@ -2032,7 +2100,7 @@ func (t TimestampValue) ToStruct() (*StructValue, error) { } func (t TimestampValue) ToJSON() (string, error) { - return t.ToString() + return strconv.Quote(time.Time(t).UTC().Format("2006-01-02T15:04:05.999999Z")), nil } func (t TimestampValue) ToTime() (time.Time, error) { @@ -2110,6 +2178,10 @@ func (iv *IntervalValue) ToString() (string, error) { return iv.String(), nil } +func (iv *IntervalValue) ToApiString() (string, error) { + return iv.ToString() +} + func (iv *IntervalValue) ToBytes() ([]byte, error) { s, err := iv.ToString() if err != nil { @@ -2258,6 +2330,14 @@ func (v *SafeValue) ToString() (string, error) { return ret, nil } +func (v *SafeValue) ToApiString() (string, error) { + ret, err := v.value.ToApiString() + if err != nil { + return "", nil + } + return ret, nil +} + func (v *SafeValue) ToBytes() ([]byte, error) { ret, err := v.value.ToBytes() if err != nil { @@ -2364,7 +2444,7 @@ func parseDate(date string) (time.Time, error) { } func parseDatetime(datetime string) (time.Time, error) { - if t, err := time.Parse(datetimeFormat, datetime); err == nil { + if t, err := time.Parse(datetimeDefaultFormat, datetime); err == nil { return t, nil } return time.Parse("2006-01-02 15:04:05.999999", datetime) diff --git a/query_test.go b/query_test.go index 7ae0ed0..84c461e 100644 --- a/query_test.go +++ b/query_test.go @@ -1374,21 +1374,13 @@ FROM ( expectedRows: [][]interface{}{ { []interface{}{ - []map[string]interface{}{ - map[string]interface{}{ - "value": "pear", - }, - map[string]interface{}{ - "count": int64(3), - }, + map[string]interface{}{ + "value": "pear", + "count": int64(3), }, - []map[string]interface{}{ - map[string]interface{}{ - "value": "apple", - }, - map[string]interface{}{ - "count": int64(2), - }, + map[string]interface{}{ + "value": "apple", + "count": int64(2), }, }, }, @@ -1400,21 +1392,13 @@ FROM ( expectedRows: [][]interface{}{ { []interface{}{ - []map[string]interface{}{ - map[string]interface{}{ - "value": "pear", - }, - map[string]interface{}{ - "count": int64(3), - }, + map[string]interface{}{ + "value": "pear", + "count": int64(3), }, - []map[string]interface{}{ - map[string]interface{}{ - "value": nil, - }, - map[string]interface{}{ - "count": int64(2), - }, + map[string]interface{}{ + "value": nil, + "count": int64(2), }, }, }, @@ -1433,21 +1417,13 @@ SELECT APPROX_TOP_SUM(x, weight, 2) FROM UNNEST([ expectedRows: [][]interface{}{ { []interface{}{ - []map[string]interface{}{ - map[string]interface{}{ - "value": "pear", - }, - map[string]interface{}{ - "sum": int64(6), - }, + map[string]interface{}{ + "value": "pear", + "sum": int64(6), }, - []map[string]interface{}{ - map[string]interface{}{ - "value": "banana", - }, - map[string]interface{}{ - "sum": int64(5), - }, + map[string]interface{}{ + "value": "banana", + "sum": int64(5), }, }, }, @@ -1459,21 +1435,13 @@ SELECT APPROX_TOP_SUM(x, weight, 2) FROM UNNEST([ expectedRows: [][]interface{}{ { []interface{}{ - []map[string]interface{}{ - map[string]interface{}{ - "value": "pear", - }, - map[string]interface{}{ - "sum": int64(0), - }, + map[string]interface{}{ + "value": "pear", + "sum": int64(0), }, - []map[string]interface{}{ - map[string]interface{}{ - "value": "apple", - }, - map[string]interface{}{ - "sum": nil, - }, + map[string]interface{}{ + "value": "apple", + "sum": nil, }, }, }, @@ -1485,21 +1453,13 @@ SELECT APPROX_TOP_SUM(x, weight, 2) FROM UNNEST([ expectedRows: [][]interface{}{ { []interface{}{ - []map[string]interface{}{ - map[string]interface{}{ - "value": nil, - }, - map[string]interface{}{ - "sum": int64(2), - }, + map[string]interface{}{ + "value": nil, + "sum": int64(2), }, - []map[string]interface{}{ - map[string]interface{}{ - "value": "apple", - }, - map[string]interface{}{ - "sum": int64(0), - }, + map[string]interface{}{ + "value": "apple", + "sum": int64(0), }, }, }, @@ -1511,21 +1471,13 @@ SELECT APPROX_TOP_SUM(x, weight, 2) FROM UNNEST([ expectedRows: [][]interface{}{ { []interface{}{ - []map[string]interface{}{ - map[string]interface{}{ - "value": "apple", - }, - map[string]interface{}{ - "sum": int64(0), - }, + map[string]interface{}{ + "value": "apple", + "sum": int64(0), }, - []map[string]interface{}{ - map[string]interface{}{ - "value": nil, - }, - map[string]interface{}{ - "sum": nil, - }, + map[string]interface{}{ + "value": nil, + "sum": nil, }, }, }, @@ -2227,6 +2179,17 @@ SELECT * FROM Employees`, {"Jose", int64(2), "2013-03-17"}, }, }, + { + name: "date to json", + query: `SELECT TO_JSON_STRING(DATE(2024, 1, 1))`, + expectedRows: [][]interface{}{{`"2024-01-01"`}}, + }, + + { + name: "date struct to json", + query: `SELECT TO_JSON_STRING(STRUCT (DATE(2024, 1, 1) AS d))`, + expectedRows: [][]interface{}{{`{"d":"2024-01-01"}`}}, + }, { name: "window rank", query: ` @@ -2761,6 +2724,14 @@ FROM UNNEST([ }, // array functions + { + name: "array formatting", + query: `SELECT [0, 1, 1, 2, 3, 5] AS some_numbers;`, + expectedRows: [][]interface{}{ + {[]interface{}{int64(0), int64(1), int64(1), int64(2), int64(3), int64(5)}}, + }, + }, + { name: "make_array", query: `SELECT a, b FROM UNNEST([STRUCT(DATE(2022, 1, 1) AS a, 1 AS b)])`, @@ -2808,27 +2779,15 @@ INNER JOIN unnest(['lettuce']) in_stock_items ON in_stock_items = item;`, expectedRows: [][]interface{}{ { []interface{}{ - []map[string]interface{}{ - map[string]interface{}{ - "": float64(1), - }, - map[string]interface{}{ - "": float64(2), - }, - map[string]interface{}{ - "": float64(3), - }, + map[string]interface{}{ + "_field_1": int64(1), + "_field_2": int64(2), + "_field_3": int64(3), }, - []map[string]interface{}{ - map[string]interface{}{ - "": float64(4), - }, - map[string]interface{}{ - "": float64(5), - }, - map[string]interface{}{ - "": float64(6), - }, + map[string]interface{}{ + "_field_1": int64(4), + "_field_2": int64(5), + "_field_3": int64(6), }, }, }, @@ -2840,24 +2799,8 @@ INNER JOIN unnest(['lettuce']) in_stock_items ON in_stock_items = item;`, expectedRows: [][]interface{}{ { []interface{}{ - []map[string]interface{}{ - map[string]interface{}{ - "": []interface{}{ - float64(1), - float64(2), - float64(3), - }, - }, - }, - []map[string]interface{}{ - map[string]interface{}{ - "": []interface{}{ - float64(4), - float64(5), - float64(6), - }, - }, - }, + map[string]any{"_field_1": []any{int64(1), int64(2), int64(3)}}, + map[string]any{"_field_1": []any{int64(4), int64(5), int64(6)}}, }, }, }, @@ -2878,7 +2821,8 @@ SELECT ARRAY ( int64(1), }, }, - }, { + }, + { name: "array_concat function", query: `SELECT ARRAY_CONCAT([1, 2], [3, 4], [5, 6]) as count_to_six`, expectedRows: [][]interface{}{ @@ -4503,7 +4447,8 @@ WITH letters AS ( {[]interface{}{"b", "c", "d"}}, {[]interface{}{}}, }, - }, { + }, + { name: "split null delimiter", query: `SELECT SPLIT('abc', NULL), SPLIT(b'\xab\xcd\xef\xaa\xbb', NULL)`, expectedRows: [][]interface{}{{[]interface{}{}, []interface{}{}}}, @@ -4839,6 +4784,16 @@ SELECT date, EXTRACT(ISOYEAR FROM date), EXTRACT(YEAR FROM date), EXTRACT(MONTH }}, }, + { + name: "date_diff with month", + query: `SELECT DATE_DIFF(DATE '2018-01-01', DATE '2017-10-30', MONTH) AS months_diff`, + expectedRows: [][]interface{}{{int64(3)}}, + }, + { + name: "date_diff with day", + query: `SELECT DATE_DIFF(DATE '2021-06-06', DATE '2017-11-12', DAY) AS days_diff`, + expectedRows: [][]interface{}{{int64(1302)}}, + }, { name: "date_diff with month", query: `SELECT DATE_DIFF(DATE '2018-01-01', DATE '2017-10-30', MONTH) AS months_diff`, @@ -4899,6 +4854,16 @@ SELECT date, EXTRACT(ISOYEAR FROM date), EXTRACT(YEAR FROM date), EXTRACT(MONTH query: `SELECT FORMAT_DATE("%E4Y", DATE "2008-12-25")`, expectedRows: [][]interface{}{{"2008"}}, }, + { + name: "cast date as string", + query: `SELECT CAST(DATE("2022-08-01 06:47:51.123456-07:00") AS STRING)`, + expectedRows: [][]interface{}{{"2022-08-01"}}, + }, + { + name: "cast date as string", + query: `SELECT CAST(DATE("2022-08-01 06:47:51.123456-07:00") AS STRING)`, + expectedRows: [][]interface{}{{"2022-08-01"}}, + }, { name: "cast date as string", @@ -5167,6 +5132,16 @@ SELECT date, EXTRACT(ISOYEAR FROM date), EXTRACT(YEAR FROM date), EXTRACT(MONTH query: `SELECT FORMAT_DATETIME("%E4Y", DATETIME "2008-12-25 15:30:12.345678")`, expectedRows: [][]interface{}{{"2008"}}, }, + { + name: "datetime literal", + query: `SELECT DATETIME '2023-01-01 12:01:00'`, + expectedRows: [][]interface{}{{"2023-01-01T12:01:00"}}, + }, + { + name: "datetime to json", + query: `SELECT TO_JSON_STRING(STRUCT(DATETIME "2006-01-02T15:04:05.999999" AS DT))`, + expectedRows: [][]interface{}{{`{"DT":"2006-01-02T15:04:05.999999"}`}}, + }, { name: "cast datetime as string", query: `SELECT CAST(DATETIME(TIMESTAMP("2022-08-01 06:47:51.123456-07:00")) AS STRING)`, @@ -5284,6 +5259,11 @@ SELECT date, EXTRACT(ISOYEAR FROM date), EXTRACT(YEAR FROM date), EXTRACT(MONTH query: `SELECT CAST(TIME("2022-08-01 06:47:51.123456-04:00") AS STRING)`, expectedRows: [][]interface{}{{"10:47:51.123456"}}, }, + { + name: "time to json string", + query: `SELECT TO_JSON_STRING(TIME("2022-08-01 06:47:51.123456-04:00"))`, + expectedRows: [][]interface{}{{`"10:47:51.123456"`}}, + }, { name: "cast time with timezone as string", query: `SELECT CAST(TIME("2022-08-01 06:47:51.123456-04:00", "America/Los_Angeles") AS STRING)`, @@ -5373,6 +5353,17 @@ SELECT date, EXTRACT(ISOYEAR FROM date), EXTRACT(YEAR FROM date), EXTRACT(MONTH query: `SELECT TIMESTAMP(DATE "2008-12-25")`, expectedRows: [][]interface{}{{createTimestampFormatFromString("2008-12-25 00:00:00+00")}}, }, + { + name: "timestamp to json", + query: `SELECT TO_JSON_STRING(STRUCT(TIMESTAMP "2006-01-02T15:04:05.999999-07" AS TIMESTAMP))`, + expectedRows: [][]interface{}{{`{"TIMESTAMP":"2006-01-02T22:04:05.999999Z"}`}}, + }, + + { + name: "timestamp to string", + query: `WITH tmp AS ( SELECT TIMESTAMP "2006-01-02T15:04:05-07" AS TS ) SELECT ts, CAST(ts AS STRING) FROM tmp`, + expectedRows: [][]interface{}{{createTimestampFormatFromString(`2006-01-02 22:04:05+00`), `2006-01-02 22:04:05+00`}}, + }, { name: "timestamp_add", query: `SELECT TIMESTAMP_ADD(TIMESTAMP "2008-12-25 15:30:00+00", INTERVAL 10 MINUTE)`, @@ -5461,6 +5452,11 @@ SELECT date, EXTRACT(ISOYEAR FROM date), EXTRACT(YEAR FROM date), EXTRACT(MONTH query: `SELECT CAST(TIMESTAMP("2022-08-01 06:47:51.123456-07:00") AS STRING);`, expectedRows: [][]interface{}{{"2022-08-01 13:47:51.123456+00"}}, }, + { + name: "timestamp parses with T value", + query: `SELECT CAST(TIMESTAMP("2022-08-01T06:47:51.123456-07:00") AS STRING);`, + expectedRows: [][]interface{}{{"2022-08-01 13:47:51.123456+00"}}, + }, { name: "parse timestamp with %a %b %e %I:%M:%S %Y", query: `SELECT PARSE_TIMESTAMP("%a %b %e %I:%M:%S %Y", "Thu Dec 25 07:30:00 2008")`, @@ -5481,7 +5477,8 @@ SELECT date, EXTRACT(ISOYEAR FROM date), EXTRACT(YEAR FROM date), EXTRACT(MONTH query: `SELECT PARSE_TIMESTAMP("%k", " 9");`, expectedRows: [][]interface{}{{createTimestampFormatFromString("1970-01-01 09:00:00+00")}}, }, - {name: "parse_timestamp with %D", + { + name: "parse_timestamp with %D", query: `SELECT PARSE_TIMESTAMP("%D", "02/02/99");`, expectedRows: [][]interface{}{{createTimestampFormatFromString("1999-02-02 00:00:00+00")}}, }, @@ -5570,6 +5567,7 @@ FROM Input`, query: `SELECT DATE "2020-09-22" + val FROM UNNEST([INTERVAL 1 DAY,INTERVAL -1 DAY,INTERVAL 2 YEAR,CAST('1-2 3 18:1:55' AS INTERVAL)]) as val`, expectedRows: [][]interface{}{{"2020-09-23T00:00:00"}, {"2020-09-21T00:00:00"}, {"2022-09-22T00:00:00"}, {"2021-11-25T18:01:55"}}, }, + { name: "interval from sub operator", query: ` @@ -5635,6 +5633,13 @@ SELECT expectedRows: [][]interface{}{{"123.456", "123.456"}}, }, + // bytes formatting + { + name: "bytes_formatting", + query: `SELECT b"abc", CAST(b"abc" AS STRING)`, + expectedRows: [][]interface{}{{"YWJj", "abc"}}, + }, + // security functions { name: "session_user", @@ -6729,6 +6734,16 @@ SELECT id, name FROM T1 ORDER BY id`, {int64(2), "root_child"}, }, }, + { + name: "struct field integration", + query: `CREATE TABLE conductors (conductor STRUCT); +INSERT INTO conductors (conductor) VALUES (?); +SELECT conductor.length FROM conductors;`, + expectedRows: [][]interface{}{ + {nil}, + }, + args: []interface{}{map[string]float64{}}, + }, } { test := test t.Run(test.name, func(t *testing.T) {