diff --git a/filter_to_quals_test.go b/filter_to_quals_test.go index ebd3e7b..375a013 100644 --- a/filter_to_quals_test.go +++ b/filter_to_quals_test.go @@ -1,9 +1,10 @@ package main import ( - "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" "log" "testing" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" ) func TestFilterStringToQual(t *testing.T) { @@ -178,3 +179,280 @@ func TestFilterStringToQual(t *testing.T) { } } + +func TestStringToQualValue(t *testing.T) { + tests := []struct { + name string + valueString string + columnType proto.ColumnType + want *proto.QualValue + wantErr bool + }{ + { + name: "Valid boolean true", + valueString: "true", + columnType: proto.ColumnType_BOOL, + want: &proto.QualValue{ + Value: &proto.QualValue_BoolValue{BoolValue: true}, + }, + }, + { + name: "Invalid boolean", + valueString: "notabool", + columnType: proto.ColumnType_BOOL, + wantErr: true, + }, + { + name: "Valid integer", + valueString: "123", + columnType: proto.ColumnType_INT, + want: &proto.QualValue{ + Value: &proto.QualValue_Int64Value{Int64Value: 123}, + }, + }, + { + name: "Invalid integer", + valueString: "not_a_number", + columnType: proto.ColumnType_INT, + wantErr: true, + }, + { + name: "Valid double", + valueString: "123.45", + columnType: proto.ColumnType_DOUBLE, + want: &proto.QualValue{ + Value: &proto.QualValue_DoubleValue{DoubleValue: 123.45}, + }, + }, + { + name: "Invalid double", + valueString: "not_a_double", + columnType: proto.ColumnType_DOUBLE, + wantErr: true, + }, + { + name: "Valid string", + valueString: "test string", + columnType: proto.ColumnType_STRING, + want: &proto.QualValue{ + Value: &proto.QualValue_StringValue{StringValue: "test string"}, + }, + }, + { + name: "Valid JSON", + valueString: `{"key": "value"}`, + columnType: proto.ColumnType_JSON, + want: &proto.QualValue{ + Value: &proto.QualValue_JsonbValue{JsonbValue: `{"key": "value"}`}, + }, + }, + { + name: "Valid timestamp RFC3339", + valueString: "2024-03-19T10:30:00Z", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp RFC3339 with nanoseconds", + valueString: "2024-03-19T10:30:00.123456789Z", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp RFC3339 with timezone offset", + valueString: "2024-03-19T10:30:00+02:00", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp ISO8601 basic", + valueString: "2024-03-19T10:30:05", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp with space separator", + valueString: "2024-03-19 10:30:05", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp date only", + valueString: "2024-03-19", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp RFC1123", + valueString: "Tue, 19 Mar 2024 10:30:00 GMT", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp RFC1123Z", + valueString: "Tue, 19 Mar 2024 10:30:00 +0000", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp RFC822", + valueString: "19 Mar 24 10:30 GMT", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid timestamp RFC822Z", + valueString: "19 Mar 24 10:30 +0000", + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Valid Unix timestamp seconds", + valueString: "1710842400", // 2024-03-19 10:00:00 UTC + columnType: proto.ColumnType_TIMESTAMP, + }, + { + name: "Invalid timestamp format", + valueString: "not_a_timestamp", + columnType: proto.ColumnType_TIMESTAMP, + wantErr: true, + }, + { + name: "Invalid timestamp partial date", + valueString: "2024-03", + columnType: proto.ColumnType_TIMESTAMP, + wantErr: true, + }, + { + name: "Invalid timestamp bad month", + valueString: "2024-13-19T10:30:00Z", + columnType: proto.ColumnType_TIMESTAMP, + wantErr: true, + }, + { + name: "Invalid timestamp bad day", + valueString: "2024-03-32T10:30:00Z", + columnType: proto.ColumnType_TIMESTAMP, + wantErr: true, + }, + { + name: "Invalid timestamp bad hour", + valueString: "2024-03-19T25:30:00Z", + columnType: proto.ColumnType_TIMESTAMP, + wantErr: true, + }, + // Same tests for DATETIME type + { + name: "Valid datetime RFC3339", + valueString: "2024-03-19T10:30:00Z", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime RFC3339 with nanoseconds", + valueString: "2024-03-19T10:30:00.123456789Z", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime RFC3339 with timezone offset", + valueString: "2024-03-19T10:30:00+02:00", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime ISO8601 basic", + valueString: "2024-03-19T10:30:05", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime with space separator", + valueString: "2024-03-19 10:30:05", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime date only", + valueString: "2024-03-19", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime RFC1123", + valueString: "Tue, 19 Mar 2024 10:30:00 GMT", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime RFC1123Z", + valueString: "Tue, 19 Mar 2024 10:30:00 +0000", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime RFC822", + valueString: "19 Mar 24 10:30 GMT", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid datetime RFC822Z", + valueString: "19 Mar 24 10:30 +0000", + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Valid Unix datetime seconds", + valueString: "1710842400", // 2024-03-19 10:00:00 UTC + columnType: proto.ColumnType_DATETIME, + }, + { + name: "Invalid datetime format", + valueString: "not_a_datetime", + columnType: proto.ColumnType_DATETIME, + wantErr: true, + }, + { + name: "Invalid datetime partial date", + valueString: "2024-03", + columnType: proto.ColumnType_DATETIME, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := stringToQualValue(tt.valueString, tt.columnType) + + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if got != nil { + t.Fatal("expected nil result, got", got) + } + return + } + + if err != nil { + t.Fatal(err) + } + + if tt.columnType == proto.ColumnType_TIMESTAMP { + if got == nil { + t.Fatal("got nil, want timestamp value") + } + if got.GetTimestampValue() == nil { + t.Fatal("got nil timestamp value") + } + } else if tt.want != nil { + // For non-timestamp values, compare the actual values + switch v := got.GetValue().(type) { + case *proto.QualValue_BoolValue: + if want := tt.want.GetBoolValue(); v.BoolValue != want { + t.Fatal("got", v.BoolValue, "want", want) + } + case *proto.QualValue_Int64Value: + if want := tt.want.GetInt64Value(); v.Int64Value != want { + t.Fatal("got", v.Int64Value, "want", want) + } + case *proto.QualValue_DoubleValue: + if want := tt.want.GetDoubleValue(); v.DoubleValue != want { + t.Fatal("got", v.DoubleValue, "want", want) + } + case *proto.QualValue_StringValue: + if want := tt.want.GetStringValue(); v.StringValue != want { + t.Fatal("got", v.StringValue, "want", want) + } + case *proto.QualValue_JsonbValue: + if want := tt.want.GetJsonbValue(); v.JsonbValue != want { + t.Fatal("got", v.JsonbValue, "want", want) + } + default: + t.Fatal("got unexpected type", v) + } + } + }) + } +} diff --git a/templates/main.go.tmpl b/templates/main.go.tmpl index 0debfac..4a005b4 100644 --- a/templates/main.go.tmpl +++ b/templates/main.go.tmpl @@ -503,12 +503,44 @@ func stringToQualValue(valueString string, columnType proto.ColumnType) (*proto. // todo parse case proto.ColumnType_DATETIME, proto.ColumnType_TIMESTAMP: - //t, err := time.Parse("Mon Jan 2 15:04:05 MST 2006", valueString) - //if err != nil{ - // return nil, err - //} - //result.Value = &proto.QualValue_TimestampValue{TimestampValue: t} - // todo parse + var t time.Time + var err error + // Try parsing as Unix timestamp (seconds since epoch) + if unixTime, err := strconv.ParseInt(valueString, 10, 64); err == nil { + t = time.Unix(unixTime, 0) + ts, err := ptypes.TimestampProto(t) + if err != nil { + return nil, fmt.Errorf("failed to convert Unix time to timestamp: %v", err) + } + result.Value = &proto.QualValue_TimestampValue{TimestampValue: ts} + return result, nil + } + // Try parsing with multiple common time formats + formats := []string{ + time.RFC3339, + time.RFC3339Nano, + "2006-01-02T15:04:05", + "2006-01-02 15:04:05", + "2006-01-02", + time.RFC1123, + time.RFC1123Z, + time.RFC822, + time.RFC822Z, + } + for _, format := range formats { + t, err = time.Parse(format, valueString) + if err == nil { + break + } + } + if err != nil { + return nil, fmt.Errorf("could not parse time value '%s' with any supported format", valueString) + } + ts, err := ptypes.TimestampProto(t) + if err != nil { + return nil, fmt.Errorf("failed to convert time to timestamp: %v", err) + } + result.Value = &proto.QualValue_TimestampValue{TimestampValue: ts} case proto.ColumnType_LTREE: result.Value = &proto.QualValue_LtreeValue{LtreeValue: valueString} }