influxdata / influxdb-client-go

InfluxDB 2 Go Client
MIT License
609 stars 116 forks source link

Query into Point structs #380

Open jpmeijers opened 1 year ago

jpmeijers commented 1 year ago

Proposal: I'd like to have symmetry between writes and reads. When writing data into Infludb one would use a Point struct, with strict Tags, Fields, Measurement and Time values. When querying the data I want to see the returned results in this same format.

Current behavior: The Point object when writing data is clear, and follows the basic concepts of Tags and Fields. When querying data the result is a pointer that contains a messy combination of tables and columns. Filtering these columns into Tags and Fields are tricky.

Desired behavior: A query should ideally return the same format struct as what is written to the database. In other words I should be able to take a result and write it directly back to the database, without any data being lost. Updating a field would then be a write, update of the field value, and then a write.

Alternatives considered: Describe other solutions or features you considered.

Use case: Why is this important (helps with prioritizing requests)?

jpmeijers commented 1 year ago
type ResultPoint struct {
    Tags  map[string]string
    Field string
    Time  time.Time
    Value interface{}
}

func QueryIntoPoints(query string) ([]ResultPoint, error) {
    var resultPoints []ResultPoint

    result, err := queryApi.Query(context.Background(), query)
    if err != nil {
        return nil, err
    }
    // check for an error
    if result.Err() != nil {
        return nil, result.Err()
    }

    var tagKeys []string
    ignoredColumns := []string{"result", "table"}

    // Iterate over query response
    for result.Next() {
        // Notice when group key has changed
        if result.TableChanged() {
            tagKeys = tagKeys[:0]
            for _, col := range result.TableMetadata().Columns() {
                if strings.HasPrefix(col.Name(), "_") {
                    // don't use
                } else if slices.Contains(ignoredColumns, col.Name()) {
                    // don't use
                } else {
                    tagKeys = append(tagKeys, col.Name())
                }
            }
        }

        // Create a point and store time and value
        resultPoint := ResultPoint{
            Tags:  make(map[string]string, 0),
            Time:  result.Record().Time(),
            Value: result.Record().Value(),
        }

        // Set tags
        for _, tagKey := range tagKeys {
            tagValue, ok := result.Record().ValueByKey(tagKey).(string)
            if ok {
                resultPoint.Tags[tagKey] = tagValue
            }
        }

        // Set field
        field, ok := result.Record().ValueByKey("_field").(string)
        if ok {
            resultPoint.Field = field
        }

        resultPoints = append(resultPoints, resultPoint)
    }

    return resultPoints, err
}

This gives me something that resembles a Point struct.

powersj commented 1 year ago

Hi,

When sending data to InfluxDB, it needs to be in line protocol format. The point helper class essentially is a wrapper around that format. When querying data in InfluxDB v2, flux is used to query and return data in a format that is more easily used for additional querying, filtering, and parsing.

Having a function that would take the flux tables that are returned and converting that data into line protocol might be possible, assuming that all the field and tag data is present. We would accept a PR that provides this functionality, but it is not something we would prioritize as part of our own future development.

Thanks