Repository: influxdata/influxdb-client-go (go)

Issue:
Accessing FluxRecord fields that are not returned by result result panic (flux `count()`, `pivot()`)
When executing [FluxRecord.Time()](https://github.com/influxdata/influxdb-client-go/blob/b73a2151a2b7ea9dda44b00bbb09564ef6c925b3/api/query/table.go#L155)  for query that does aggregation  Go panics ` interface conversion: interface {} is nil, not time.Time`

This is because these results do not have `_time` field in result and `nil` can not be cast to `time.Time`

https://github.com/influxdata/influxdb-client-go/blob/b73a2151a2b7ea9dda44b00bbb09564ef6c925b3/api/query/table.go#L156

There are other methods that could panic in same way `Start`/`Stop`/`Value` etc.   For example Flux function `pivot()` does not return `_value` column

I think allowing those methods to panic is little bit too much. Adding second field indicating if value exists - similarly as getting value from map  `val, ok := mymap[key]`  would have maybe less surprising results and not that punishing API. 

Example query:
```
from(bucket:"data/autogen")
  |> range(start: 2021-03-22T16:05:20+02:00)
  |> filter(fn:(r) => r._measurement == "position" and (r._field =="latitude") and (r.type == "main"))
  |> count()
``` 

Result:
```
#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,string,string,string,long
#group,false,false,true,true,true,true,true,false
#default,_result,,,,,,,
,result,table,_start,_stop,_field,_measurement,type,_value
,,0,2021-03-22T14:05:20Z,2021-04-12T13:14:34.558380213Z,latitude,position,main,25997
```

Pull request:
fix: reliably retrieving values (#246)
Closes #246

## Proposed Changes

Reliably reading values from the map.

## Checklist

<!-- Checkboxes below this note can be erased if not applicable to your Pull Request. -->

- [X] CHANGELOG.md updated
- [X] Rebased/mergeable
- [X] A test has been added if appropriate
- [X] Tests pass
- [X] Commit messages are in [semantic format](https://seesparkbox.com/foundry/semantic_commit_messages)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e18bed..b380876 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,8 +10,8 @@
  - Improved documentation about concurrency
 1. [#251](https://github.com/influxdata/influxdb-client-go/pull/251) Fixed Readme.md formatting
  
-### Bugfixes
-  
+### Bug fixes
+1. [#252](https://github.com/influxdata/influxdb-client-go/pull/252) Fixed panic when getting not present standard Flux columns  
 
 ## 2.2.3 [2021-04-01]
 ### Bug fixes
diff --git a/api/query/table.go b/api/query/table.go
index 68776dc..fc30fa5 100644
--- a/api/query/table.go
+++ b/api/query/table.go
@@ -151,19 +151,22 @@ func (r *FluxRecord) Table() int {
 	return r.table
 }
 
-// Start returns the inclusive lower time bound of all records in the current table
+// Start returns the inclusive lower time bound of all records in the current table.
+// Returns empty time.Time if there is no column "_start".
 func (r *FluxRecord) Start() time.Time {
-	return r.ValueByKey("_start").(time.Time)
+	return timeValue(r.values, "_start")
 }
 
-// Stop returns the exclusive upper time bound of all records in the current table
+// Stop returns the exclusive upper time bound of all records in the current table.
+// Returns empty time.Time if there is no column "_stop".
 func (r *FluxRecord) Stop() time.Time {
-	return r.ValueByKey("_stop").(time.Time)
+	return timeValue(r.values, "_stop")
 }
 
-// Time returns the time of the record
+// Time returns the time of the record.
+// Returns empty time.Time if there is no column "_time".
 func (r *FluxRecord) Time() time.Time {
-	return r.ValueByKey("_time").(time.Time)
+	return timeValue(r.values, "_time")
 }
 
 // Value returns the default _value column value or nil if not present
@@ -171,14 +174,16 @@ func (r *FluxRecord) Value() interface{} {
 	return r.ValueByKey("_value")
 }
 
-// Field returns the field name
+// Field returns the field name.
+// Returns empty string if there is no column "_field".
 func (r *FluxRecord) Field() string {
-	return r.ValueByKey("_field").(string)
+	return stringValue(r.values, "_field")
 }
 
 // Measurement returns the measurement name of the record
+// Returns empty string if there is no column "_measurement".
 func (r *FluxRecord) Measurement() string {
-	return r.ValueByKey("_measurement").(string)
+	return stringValue(r.values, "_measurement")
 }
 
 // Values returns map of the values where key is the column name
@@ -204,3 +209,25 @@ func (r *FluxRecord) String() string {
 	}
 	return buffer.String()
 }
+
+// timeValue returns time.Time value from values map according to the key
+// Empty time.Time value is returned if key is not found
+func timeValue(values map[string]interface{}, key string) time.Time {
+	if val, ok := values[key]; ok {
+		if t, ok := val.(time.Time); ok {
+			return t
+		}
+	}
+	return time.Time{}
+}
+
+// timeValue returns string value from values map according to the key
+// Empty string is returned if key is not found
+func stringValue(values map[string]interface{}, key string) string {
+	if val, ok := values[key]; ok {
+		if s, ok := val.(string); ok {
+			return s
+		}
+	}
+	return ""
+}


Test patch:
diff --git a/api/query/table_test.go b/api/query/table_test.go
index 112467a..f8a34b4 100644
--- a/api/query/table_test.go
+++ b/api/query/table_test.go
@@ -29,41 +29,41 @@ func TestTable(t *testing.T) {
 	table.AddColumn(&FluxColumn{dataType: "string", defaultValue: "", name: "_field", group: true, index: 4})
 	require.Len(t, table.columns, 5)
 
-	assert.Equal(t, table.Position(), 1)
+	assert.Equal(t, 1, table.Position())
 	require.NotNil(t, table.Column(0))
-	assert.Equal(t, table.Column(0).DefaultValue(), "_result")
-	assert.Equal(t, table.Column(0).DataType(), "string")
-	assert.Equal(t, table.Column(0).Name(), "result")
-	assert.Equal(t, table.Column(0).Index(), 0)
-	assert.Equal(t, table.Column(0).IsGroup(), false)
+	assert.Equal(t, "_result", table.Column(0).DefaultValue())
+	assert.Equal(t, "string", table.Column(0).DataType())
+	assert.Equal(t, "result", table.Column(0).Name())
+	assert.Equal(t, 0, table.Column(0).Index())
+	assert.Equal(t, false, table.Column(0).IsGroup())
 
 	require.NotNil(t, table.Column(1))
-	assert.Equal(t, table.Column(1).DefaultValue(), "10")
-	assert.Equal(t, table.Column(1).DataType(), "long")
-	assert.Equal(t, table.Column(1).Name(), "_table")
-	assert.Equal(t, table.Column(1).Index(), 1)
-	assert.Equal(t, table.Column(1).IsGroup(), false)
+	assert.Equal(t, "10", table.Column(1).DefaultValue())
+	assert.Equal(t, "long", table.Column(1).DataType())
+	assert.Equal(t, "_table", table.Column(1).Name())
+	assert.Equal(t, 1, table.Column(1).Index())
+	assert.Equal(t, false, table.Column(1).IsGroup())
 
 	require.NotNil(t, table.Column(2))
-	assert.Equal(t, table.Column(2).DefaultValue(), "")
-	assert.Equal(t, table.Column(2).DataType(), "dateTime:RFC3339")
-	assert.Equal(t, table.Column(2).Name(), "_start")
-	assert.Equal(t, table.Column(2).Index(), 2)
-	assert.Equal(t, table.Column(2).IsGroup(), true)
+	assert.Equal(t, "", table.Column(2).DefaultValue())
+	assert.Equal(t, "dateTime:RFC3339", table.Column(2).DataType())
+	assert.Equal(t, "_start", table.Column(2).Name())
+	assert.Equal(t, 2, table.Column(2).Index())
+	assert.Equal(t, true, table.Column(2).IsGroup())
 
 	require.NotNil(t, table.Column(3))
-	assert.Equal(t, table.Column(3).DefaultValue(), "1.1")
-	assert.Equal(t, table.Column(3).DataType(), "double")
-	assert.Equal(t, table.Column(3).Name(), "_value")
-	assert.Equal(t, table.Column(3).Index(), 3)
-	assert.Equal(t, table.Column(3).IsGroup(), false)
+	assert.Equal(t, "1.1", table.Column(3).DefaultValue())
+	assert.Equal(t, "double", table.Column(3).DataType())
+	assert.Equal(t, "_value", table.Column(3).Name())
+	assert.Equal(t, 3, table.Column(3).Index())
+	assert.Equal(t, false, table.Column(3).IsGroup())
 
 	require.NotNil(t, table.Column(4))
-	assert.Equal(t, table.Column(4).DefaultValue(), "")
-	assert.Equal(t, table.Column(4).DataType(), "string")
-	assert.Equal(t, table.Column(4).Name(), "_field")
-	assert.Equal(t, table.Column(4).Index(), 4)
-	assert.Equal(t, table.Column(4).IsGroup(), true)
+	assert.Equal(t, "", table.Column(4).DefaultValue())
+	assert.Equal(t, "string", table.Column(4).DataType())
+	assert.Equal(t, "_field", table.Column(4).Name())
+	assert.Equal(t, 4, table.Column(4).Index())
+	assert.Equal(t, true, table.Column(4).IsGroup())
 }
 
 func TestRecord(t *testing.T) {
@@ -82,11 +82,32 @@ func TestRecord(t *testing.T) {
 		},
 	}
 	require.Len(t, record.values, 10)
-	assert.Equal(t, record.Start(), mustParseTime("2020-02-17T22:19:49.747562847Z"))
-	assert.Equal(t, record.Stop(), mustParseTime("2020-02-18T22:19:49.747562847Z"))
-	assert.Equal(t, record.Time(), mustParseTime("2020-02-18T10:34:08.135814545Z"))
-	assert.Equal(t, record.Field(), "f")
-	assert.Equal(t, record.Value(), 1.4)
-	assert.Equal(t, record.Measurement(), "test")
-	assert.Equal(t, record.Table(), 2)
+	assert.Equal(t, mustParseTime("2020-02-17T22:19:49.747562847Z"), record.Start())
+	assert.Equal(t, mustParseTime("2020-02-18T22:19:49.747562847Z"), record.Stop())
+	assert.Equal(t, mustParseTime("2020-02-18T10:34:08.135814545Z"), record.Time())
+	assert.Equal(t, "f", record.Field())
+	assert.Equal(t, 1.4, record.Value())
+	assert.Equal(t, "test", record.Measurement())
+	assert.Equal(t, 2, record.Table())
+
+	agRec := &FluxRecord{table: 0,
+		values: map[string]interface{}{
+			"result": "_result",
+			"room":   "bathroom",
+			"sensor": "SHT",
+			"temp":   24.3,
+			"hum":    42,
+		},
+	}
+	require.Len(t, agRec.values, 5)
+	assert.Equal(t, time.Time{}, agRec.Start())
+	assert.Equal(t, time.Time{}, agRec.Stop())
+	assert.Equal(t, time.Time{}, agRec.Time())
+	assert.Equal(t, "", agRec.Field())
+	assert.Nil(t, agRec.Value())
+	assert.Equal(t, "", agRec.Measurement())
+	assert.Equal(t, 0, agRec.Table())
+	assert.Equal(t, 24.3, agRec.ValueByKey("temp"))
+	assert.Equal(t, 42, agRec.ValueByKey("hum"))
+	assert.Nil(t, agRec.ValueByKey("notexist"))
 }