Merge pull request #4436 from influxdb/tag-names-to-keys
WIP tag name --> tag key, field name --> field keypull/4454/head
commit
82f104a8b1
|
@ -1,4 +1,4 @@
|
|||
The top level name is called a measurement. These names can contain any characters. Then there are field names, field values, tag keys and tag values, which can also contain any characters. However, if the measurement, field, or tag contains any character other than [A-Z,a-z,0-9,_], or if it starts with a digit, it must be double-quoted. Therefore anywhere a measurement name, field name, field value, tag name, or tag value appears it should be wrapped in double quotes.
|
||||
The top level name is called a measurement. These names can contain any characters. Then there are field names, field values, tag keys and tag values, which can also contain any characters. However, if the measurement, field, or tag contains any character other than [A-Z,a-z,0-9,_], or if it starts with a digit, it must be double-quoted. Therefore anywhere a measurement name, field key, or tag key appears it should be wrapped in double quotes.
|
||||
|
||||
# Databases & retention policies
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ type WritePointsRequest struct {
|
|||
Points []models.Point
|
||||
}
|
||||
|
||||
// AddPoint adds a point to the WritePointRequest with field name 'value'
|
||||
// AddPoint adds a point to the WritePointRequest with field key 'value'
|
||||
func (w *WritePointsRequest) AddPoint(name string, value interface{}, timestamp time.Time, tags map[string]string) {
|
||||
w.Points = append(w.Points, models.NewPoint(
|
||||
name, tags, map[string]interface{}{"value": value}, timestamp,
|
||||
|
|
|
@ -141,7 +141,7 @@ func (c *Config) applyEnvOverrides(prefix string, spec reflect.Value) error {
|
|||
configName := typeOfSpec.Field(i).Tag.Get("toml")
|
||||
// Replace hyphens with underscores to avoid issues with shells
|
||||
configName = strings.Replace(configName, "-", "_", -1)
|
||||
fieldName := typeOfSpec.Field(i).Name
|
||||
fieldKey := typeOfSpec.Field(i).Name
|
||||
|
||||
// Skip any fields that we cannot set
|
||||
if f.CanSet() || f.Kind() == reflect.Slice {
|
||||
|
@ -188,14 +188,14 @@ func (c *Config) applyEnvOverrides(prefix string, spec reflect.Value) error {
|
|||
if f.Type().Name() == "Duration" {
|
||||
dur, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldName, f.Type().String(), value)
|
||||
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldKey, f.Type().String(), value)
|
||||
}
|
||||
intValue = dur.Nanoseconds()
|
||||
} else {
|
||||
var err error
|
||||
intValue, err = strconv.ParseInt(value, 0, f.Type().Bits())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldName, f.Type().String(), value)
|
||||
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldKey, f.Type().String(), value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,14 +203,14 @@ func (c *Config) applyEnvOverrides(prefix string, spec reflect.Value) error {
|
|||
case reflect.Bool:
|
||||
boolValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldName, f.Type().String(), value)
|
||||
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldKey, f.Type().String(), value)
|
||||
|
||||
}
|
||||
f.SetBool(boolValue)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
floatValue, err := strconv.ParseFloat(value, f.Type().Bits())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldName, f.Type().String(), value)
|
||||
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldKey, f.Type().String(), value)
|
||||
|
||||
}
|
||||
f.SetFloat(floatValue)
|
||||
|
|
|
@ -3432,7 +3432,7 @@ func TestServer_Query_WildcardExpansion(t *testing.T) {
|
|||
exp: `{"results":[{"series":[{"name":"wildcard","columns":["time","c","h","region","value"],"values":[["2000-01-01T00:00:00Z",80,"A","us-east",10],["2000-01-01T00:00:10Z",90,"B","us-east",20],["2000-01-01T00:00:20Z",70,"B","us-west",30],["2000-01-01T00:00:30Z",60,"A","us-east",40]]}]}]}`,
|
||||
},
|
||||
&Query{
|
||||
name: "duplicate tag and field name, always favor field over tag",
|
||||
name: "duplicate tag and field key, always favor field over tag",
|
||||
command: `SELECT * FROM dupnames`,
|
||||
params: url.Values{"db": []string{"db0"}},
|
||||
exp: `{"results":[{"series":[{"name":"dupnames","columns":["time","day","region","value"],"values":[["2000-01-01T00:00:00Z",3,"us-east",10],["2000-01-01T00:00:10Z",2,"us-east",20],["2000-01-01T00:00:20Z",1,"us-west",30]]}]}]}`,
|
||||
|
|
|
@ -54,7 +54,7 @@ digit = "0" … "9" .
|
|||
|
||||
## Identifiers
|
||||
|
||||
Identifiers are tokens which refer to database names, retention policy names, user names, measurement names, tag keys, and field names.
|
||||
Identifiers are tokens which refer to database names, retention policy names, user names, measurement names, tag keys, and field keys.
|
||||
|
||||
The rules:
|
||||
|
||||
|
@ -652,7 +652,7 @@ privilege = "ALL" [ "PRIVILEGES" ] | "READ" | "WRITE" .
|
|||
|
||||
series_id = int_lit .
|
||||
|
||||
sort_field = field_name [ ASC | DESC ] .
|
||||
sort_field = field_key [ ASC | DESC ] .
|
||||
|
||||
sort_fields = sort_field { "," sort_field } .
|
||||
|
||||
|
|
|
@ -248,21 +248,21 @@ func scanKey(buf []byte, i int) (int, []byte, error) {
|
|||
break
|
||||
}
|
||||
|
||||
// equals is special in the tags section. It must be escaped if part of a tag name or value.
|
||||
// equals is special in the tags section. It must be escaped if part of a tag key or value.
|
||||
// It does not need to be escaped if part of the measurement.
|
||||
if buf[i] == '=' && commas > 0 {
|
||||
if i-1 < 0 || i-2 < 0 {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag name")
|
||||
return i, buf[start:i], fmt.Errorf("missing tag key")
|
||||
}
|
||||
|
||||
// Check for "cpu,=value" but allow "cpu,a\,=value"
|
||||
if buf[i-1] == ',' && buf[i-2] != '\\' {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag name")
|
||||
return i, buf[start:i], fmt.Errorf("missing tag key")
|
||||
}
|
||||
|
||||
// Check for "cpu,\ =value"
|
||||
if buf[i-1] == ' ' && buf[i-2] != '\\' {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag name")
|
||||
return i, buf[start:i], fmt.Errorf("missing tag key")
|
||||
}
|
||||
|
||||
i += 1
|
||||
|
@ -459,12 +459,12 @@ func scanFields(buf []byte, i int) (int, []byte, error) {
|
|||
|
||||
// check for "... =123" but allow "a\ =123"
|
||||
if buf[i-1] == ' ' && buf[i-2] != '\\' {
|
||||
return i, buf[start:i], fmt.Errorf("missing field name")
|
||||
return i, buf[start:i], fmt.Errorf("missing field key")
|
||||
}
|
||||
|
||||
// check for "...a=123,=456" but allow "a=123,a\,=456"
|
||||
if buf[i-1] == ',' && buf[i-2] != '\\' {
|
||||
return i, buf[start:i], fmt.Errorf("missing field name")
|
||||
return i, buf[start:i], fmt.Errorf("missing field key")
|
||||
}
|
||||
|
||||
// check for "... value="
|
||||
|
|
|
@ -211,7 +211,7 @@ func TestParsePointMissingQuote(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParsePointMissingTagName(t *testing.T) {
|
||||
func TestParsePointMissingTagKey(t *testing.T) {
|
||||
_, err := models.ParsePointsString(`cpu,host=serverA,=us-east value=1i`)
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,=us-east value=1i`)
|
||||
|
@ -560,7 +560,7 @@ func TestParsePointUnescape(t *testing.T) {
|
|||
test(t, `cpu,region\,zone=east value=1.0`,
|
||||
models.NewPoint("cpu",
|
||||
models.Tags{
|
||||
"region,zone": "east", // comma in the tag name
|
||||
"region,zone": "east", // comma in the tag key
|
||||
},
|
||||
models.Fields{
|
||||
"value": 1.0,
|
||||
|
@ -571,7 +571,7 @@ func TestParsePointUnescape(t *testing.T) {
|
|||
test(t, `cpu,region\ zone=east value=1.0`,
|
||||
models.NewPoint("cpu",
|
||||
models.Tags{
|
||||
"region zone": "east", // comma in the tag name
|
||||
"region zone": "east", // comma in the tag key
|
||||
},
|
||||
models.Fields{
|
||||
"value": 1.0,
|
||||
|
@ -600,25 +600,25 @@ func TestParsePointUnescape(t *testing.T) {
|
|||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// commas in field names
|
||||
// commas in field keys
|
||||
test(t, `cpu,regions=east value\,ms=1.0`,
|
||||
models.NewPoint("cpu",
|
||||
models.Tags{
|
||||
"regions": "east",
|
||||
},
|
||||
models.Fields{
|
||||
"value,ms": 1.0, // comma in the field name
|
||||
"value,ms": 1.0, // comma in the field keys
|
||||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// spaces in field names
|
||||
// spaces in field keys
|
||||
test(t, `cpu,regions=east value\ ms=1.0`,
|
||||
models.NewPoint("cpu",
|
||||
models.Tags{
|
||||
"regions": "east",
|
||||
},
|
||||
models.Fields{
|
||||
"value ms": 1.0, // comma in the field name
|
||||
"value ms": 1.0, // comma in the field keys
|
||||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
|
@ -657,7 +657,7 @@ func TestParsePointUnescape(t *testing.T) {
|
|||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// field name using escape char.
|
||||
// field keys using escape char.
|
||||
test(t, `cpu \a=1i`,
|
||||
models.NewPoint(
|
||||
"cpu",
|
||||
|
|
|
@ -14,7 +14,7 @@ To extract tags from metrics, one or more templates must be configured to parse
|
|||
|
||||
## Templates
|
||||
|
||||
Templates allow matching parts of a metric name to be used as tag names in the stored metric. They have a similar format to graphite metric names. The values in between the separators are used as the tag name. The location of the tag name that matches the same position as the graphite metric section is used as the value. If there is no value, the graphite portion is skipped.
|
||||
Templates allow matching parts of a metric name to be used as tag keys in the stored metric. They have a similar format to graphite metric names. The values in between the separators are used as the tag keys. The location of the tag key that matches the same position as the graphite metric section is used as the value. If there is no value, the graphite portion is skipped.
|
||||
|
||||
The special value _measurement_ is used to define the measurement name. It can have a trailing `*` to indicate that the remainder of the metric should be used. If a _measurement_ is not specified, the full metric name is used.
|
||||
|
||||
|
@ -50,7 +50,7 @@ Additional tags can be added to a metric that don't exist on the received metric
|
|||
|
||||
### Fields
|
||||
|
||||
A field name can be specified by using the keyword _field_. By default if no _field_ keyword is specified then the metric will be written to a field named _value_.
|
||||
A field key can be specified by using the keyword _field_. By default if no _field_ keyword is specified then the metric will be written to a field named _value_.
|
||||
|
||||
When using the current default engine _BZ1_, it's recommended to use a single field per value for performance reasons.
|
||||
|
||||
|
@ -158,7 +158,7 @@ If you need to add the same set of tags to all metrics, you can define them glob
|
|||
# filter + template + extra tag
|
||||
"stats.* .host.measurement* region=us-west,agent=sensu",
|
||||
|
||||
# filter + template with field name
|
||||
# filter + template with field key
|
||||
"stats.* .host.measurement.field",
|
||||
|
||||
# default template. Ignore the first graphite component "servers"
|
||||
|
|
|
@ -136,7 +136,7 @@ func (db *DatabaseIndex) measurementsByExpr(expr influxql.Expr) (Measurements, e
|
|||
case influxql.EQ, influxql.NEQ, influxql.EQREGEX, influxql.NEQREGEX:
|
||||
tag, ok := e.LHS.(*influxql.VarRef)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("left side of '%s' must be a tag name", e.Op.String())
|
||||
return nil, fmt.Errorf("left side of '%s' must be a tag key", e.Op.String())
|
||||
}
|
||||
|
||||
tf := &TagFilter{
|
||||
|
|
Loading…
Reference in New Issue