Merge pull request #4841 from e-dard/points-refactor
Fix parsing issues in models.scanKeypull/4855/head
commit
d2c94fce16
|
@ -27,6 +27,7 @@ There are breaking changes in this release:
|
|||
- Scripts are now located in `/usr/lib/influxdb/scripts` (previously `/opt/influxdb`)
|
||||
|
||||
### Features
|
||||
- [#4841](https://github.com/influxdb/influxdb/pull/4841): Improve point parsing speed. Lint models pacakge.
|
||||
- [#4098](https://github.com/influxdb/influxdb/pull/4702): Support 'history' command at CLI
|
||||
- [#4098](https://github.com/influxdb/influxdb/issues/4098): Enable `golint` on the code base - uuid subpackage
|
||||
- [#4141](https://github.com/influxdb/influxdb/pull/4141): Control whether each query should be logged
|
||||
|
|
380
models/points.go
380
models/points.go
|
@ -112,12 +112,6 @@ const (
|
|||
minFloat64Digits = 27
|
||||
)
|
||||
|
||||
var ()
|
||||
|
||||
func ParsePointsString(buf string) ([]Point, error) {
|
||||
return ParsePoints([]byte(buf))
|
||||
}
|
||||
|
||||
// ParsePoints returns a slice of Points from a text representation of a point
|
||||
// with each point separated by newlines. If any points fail to parse, a non-nil error
|
||||
// will be returned in addition to the points that parsed successfully.
|
||||
|
@ -125,6 +119,14 @@ func ParsePoints(buf []byte) ([]Point, error) {
|
|||
return ParsePointsWithPrecision(buf, time.Now().UTC(), "n")
|
||||
}
|
||||
|
||||
// ParsePointsString is identical to ParsePoints but accepts a string
|
||||
// buffer.
|
||||
func ParsePointsString(buf string) ([]Point, error) {
|
||||
return ParsePoints([]byte(buf))
|
||||
}
|
||||
|
||||
// ParsePointsWithPrecision is similar to ParsePoints, but allows the
|
||||
// caller to provide a precision for time.
|
||||
func ParsePointsWithPrecision(buf []byte, defaultTime time.Time, precision string) ([]Point, error) {
|
||||
points := []Point{}
|
||||
var (
|
||||
|
@ -134,7 +136,7 @@ func ParsePointsWithPrecision(buf []byte, defaultTime time.Time, precision strin
|
|||
)
|
||||
for {
|
||||
pos, block = scanLine(buf, pos)
|
||||
pos += 1
|
||||
pos++
|
||||
|
||||
if len(block) == 0 {
|
||||
break
|
||||
|
@ -247,110 +249,18 @@ func scanKey(buf []byte, i int) (int, []byte, error) {
|
|||
// we need to know how many values in the buffer are in use.
|
||||
commas := 0
|
||||
|
||||
// tracks whether we've see an '='
|
||||
equals := 0
|
||||
|
||||
// loop over each byte in buf
|
||||
for {
|
||||
// reached the end of buf?
|
||||
if i >= len(buf) {
|
||||
if equals == 0 && commas > 0 {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag value")
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// 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 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 key")
|
||||
}
|
||||
|
||||
// Check for "cpu,\ =value"
|
||||
if buf[i-1] == ' ' && buf[i-2] != '\\' {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag key")
|
||||
}
|
||||
|
||||
i += 1
|
||||
equals += 1
|
||||
|
||||
// Check for "cpu,a=1,b= value=1" or "cpu,a=1,b=,c=foo value=1"
|
||||
if i < len(buf) && (buf[i] == ' ' || buf[i] == ',') {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag value")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// escaped character
|
||||
if buf[i] == '\\' {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
|
||||
// At a tag separator (comma), track it's location
|
||||
if buf[i] == ',' {
|
||||
if equals == 0 && commas > 0 {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag value")
|
||||
}
|
||||
i += 1
|
||||
|
||||
// grow our indices slice if we have too many tags
|
||||
if commas >= len(indices) {
|
||||
newIndics := make([]int, cap(indices)*2)
|
||||
copy(newIndics, indices)
|
||||
indices = newIndics
|
||||
}
|
||||
indices[commas] = i
|
||||
commas += 1
|
||||
|
||||
// Check for "cpu, value=1"
|
||||
if i < len(buf) && buf[i] == ' ' {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag key")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// reached end of the block? (next block would be fields)
|
||||
if buf[i] == ' ' {
|
||||
// check for "cpu,tag value=1"
|
||||
if equals == 0 && commas > 0 {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag value")
|
||||
}
|
||||
if equals > 0 && commas-1 != equals-1 {
|
||||
return i, buf[start:i], fmt.Errorf("missing tag value")
|
||||
}
|
||||
|
||||
// grow our indices slice if we have too many tags
|
||||
if commas >= len(indices) {
|
||||
newIndics := make([]int, cap(indices)*2)
|
||||
copy(newIndics, indices)
|
||||
indices = newIndics
|
||||
}
|
||||
|
||||
indices[commas] = i + 1
|
||||
break
|
||||
}
|
||||
|
||||
i += 1
|
||||
// First scan the Point's measurement.
|
||||
state, i, err := scanMeasurement(buf, i)
|
||||
if err != nil {
|
||||
return i, buf[start:i], err
|
||||
}
|
||||
|
||||
// check that all field sections had key and values (e.g. prevent "a=1,b"
|
||||
// We're using commas -1 because there should always be a comma after measurement
|
||||
if equals > 0 && commas-1 != equals-1 {
|
||||
return i, buf[start:i], fmt.Errorf("invalid tag format")
|
||||
}
|
||||
|
||||
// This check makes sure we actually received fields from the user. #3379
|
||||
// This will catch invalid syntax such as: `cpu,host=serverA,region=us-west`
|
||||
if i >= len(buf) {
|
||||
return i, buf[start:i], fmt.Errorf("missing fields")
|
||||
// Optionally scan tags if needed.
|
||||
if state == tagKeyState {
|
||||
i, commas, indices, err = scanTags(buf, i, indices)
|
||||
if err != nil {
|
||||
return i, buf[start:i], err
|
||||
}
|
||||
}
|
||||
|
||||
// Now we know where the key region is within buf, and the locations of tags, we
|
||||
|
@ -391,7 +301,7 @@ func scanKey(buf []byte, i int) (int, []byte, error) {
|
|||
pos := copy(b, measurement)
|
||||
for _, i := range indices {
|
||||
b[pos] = ','
|
||||
pos += 1
|
||||
pos++
|
||||
_, v := scanToSpaceOr(buf, i, ',')
|
||||
pos += copy(b[pos:], v)
|
||||
}
|
||||
|
@ -402,6 +312,152 @@ func scanKey(buf []byte, i int) (int, []byte, error) {
|
|||
return i, buf[start:i], nil
|
||||
}
|
||||
|
||||
// The following constants allow us to specify which state to move to
|
||||
// next, when scanning sections of a Point.
|
||||
const (
|
||||
tagKeyState = iota
|
||||
tagValueState
|
||||
fieldsState
|
||||
)
|
||||
|
||||
// scanMeasurement examines the measurement part of a Point, returning
|
||||
// the next state to move to, and the current location in the buffer.
|
||||
func scanMeasurement(buf []byte, i int) (int, int, error) {
|
||||
// Check first byte of measurement, anything except a comma is fine.
|
||||
// It can't be a space, since whitespace is stripped prior to this
|
||||
// function call.
|
||||
if buf[i] == ',' {
|
||||
return -1, i, fmt.Errorf("missing measurement")
|
||||
}
|
||||
|
||||
for {
|
||||
i++
|
||||
if i >= len(buf) {
|
||||
// cpu
|
||||
return -1, i, fmt.Errorf("missing fields")
|
||||
}
|
||||
|
||||
if buf[i-1] == '\\' {
|
||||
// Skip character (it's escaped).
|
||||
continue
|
||||
}
|
||||
|
||||
// Unescaped comma; move onto scanning the tags.
|
||||
if buf[i] == ',' {
|
||||
return tagKeyState, i + 1, nil
|
||||
}
|
||||
|
||||
// Unescaped space; move onto scanning the fields.
|
||||
if buf[i] == ' ' {
|
||||
// cpu value=1.0
|
||||
return fieldsState, i, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scanTags examines all the tags in a Point, keeping track of and
|
||||
// returning the updated indices slice, number of commas and location
|
||||
// in buf where to start examining the Point fields.
|
||||
func scanTags(buf []byte, i int, indices []int) (int, int, []int, error) {
|
||||
var (
|
||||
err error
|
||||
commas int
|
||||
state = tagKeyState
|
||||
)
|
||||
|
||||
for {
|
||||
switch state {
|
||||
case tagKeyState:
|
||||
// Grow our indices slice if we have too many tags.
|
||||
if commas >= len(indices) {
|
||||
newIndics := make([]int, cap(indices)*2)
|
||||
copy(newIndics, indices)
|
||||
indices = newIndics
|
||||
}
|
||||
indices[commas] = i
|
||||
commas++
|
||||
|
||||
i, err = scanTagsKey(buf, i)
|
||||
state = tagValueState // tag value always follows a tag key
|
||||
case tagValueState:
|
||||
state, i, err = scanTagsValue(buf, i)
|
||||
case fieldsState:
|
||||
indices[commas] = i + 1
|
||||
return i, commas, indices, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return i, commas, indices, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scanTagsKey scans each character in a tag key.
|
||||
func scanTagsKey(buf []byte, i int) (int, error) {
|
||||
// First character of the key.
|
||||
if i >= len(buf) || buf[i] == ' ' || buf[i] == ',' || buf[i] == '=' {
|
||||
// cpu,{'', ' ', ',', '='}
|
||||
return i, fmt.Errorf("missing tag key")
|
||||
}
|
||||
|
||||
// Examine each character in the tag key until we hit an unescaped
|
||||
// equals (the tag value), or we hit an error (i.e., unescaped
|
||||
// space or comma).
|
||||
for {
|
||||
i++
|
||||
|
||||
// Either we reached the end of the buffer or we hit an
|
||||
// unescaped comma or space.
|
||||
if i >= len(buf) ||
|
||||
((buf[i] == ' ' || buf[i] == ',') && buf[i-1] != '\\') {
|
||||
// cpu,tag{'', ' ', ','}
|
||||
return i, fmt.Errorf("missing tag value")
|
||||
}
|
||||
|
||||
if buf[i] == '=' && buf[i-1] != '\\' {
|
||||
// cpu,tag=
|
||||
return i + 1, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scanTagsValue scans each character in a tag value.
|
||||
func scanTagsValue(buf []byte, i int) (int, int, error) {
|
||||
// Tag value cannot be empty.
|
||||
if buf[i] == ',' || buf[i] == ' ' {
|
||||
// cpu,tag={',', ' '}
|
||||
return -1, i, fmt.Errorf("missing tag value")
|
||||
}
|
||||
|
||||
// Examine each character in the tag value until we hit an unescaped
|
||||
// comma (move onto next tag key), an unescaped space (move onto
|
||||
// fields), or we error out.
|
||||
for {
|
||||
i++
|
||||
if i >= len(buf) {
|
||||
// cpu,tag=value
|
||||
return -1, i, fmt.Errorf("missing fields")
|
||||
}
|
||||
|
||||
// An unescaped equals sign is an invalid tag value.
|
||||
if buf[i] == '=' && buf[i-1] != '\\' {
|
||||
// cpu,tag={'=', 'fo=o'}
|
||||
return -1, i, fmt.Errorf("invalid tag format")
|
||||
}
|
||||
|
||||
if buf[i] == ',' && buf[i-1] != '\\' {
|
||||
// cpu,tag=foo,
|
||||
return tagKeyState, i + 1, nil
|
||||
}
|
||||
|
||||
// cpu,tag=foo value=1.0
|
||||
// cpu, tag=foo\= value=1.0
|
||||
if buf[i] == ' ' && buf[i-1] != '\\' {
|
||||
return fieldsState, i, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func insertionSort(l, r int, buf []byte, indices []int) {
|
||||
for i := l + 1; i < r; i++ {
|
||||
for j := i; j > l && less(buf, indices, j, j-1); j-- {
|
||||
|
@ -462,13 +518,13 @@ func scanFields(buf []byte, i int) (int, []byte, error) {
|
|||
// If the value is quoted, scan until we get to the end quote
|
||||
if buf[i] == '"' {
|
||||
quoted = !quoted
|
||||
i += 1
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// If we see an =, ensure that there is at least on char before and after it
|
||||
if buf[i] == '=' && !quoted {
|
||||
equals += 1
|
||||
equals++
|
||||
|
||||
// check for "... =123" but allow "a\ =123"
|
||||
if buf[i-1] == ' ' && buf[i-2] != '\\' {
|
||||
|
@ -510,14 +566,14 @@ func scanFields(buf []byte, i int) (int, []byte, error) {
|
|||
}
|
||||
|
||||
if buf[i] == ',' && !quoted {
|
||||
commas += 1
|
||||
commas++
|
||||
}
|
||||
|
||||
// reached end of block?
|
||||
if buf[i] == ' ' && !quoted {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
|
||||
if quoted {
|
||||
|
@ -549,7 +605,7 @@ func scanTime(buf []byte, i int) (int, []byte, error) {
|
|||
if buf[i] < '0' || buf[i] > '9' {
|
||||
// Handle negative timestamps
|
||||
if i == start && buf[i] == '-' {
|
||||
i += 1
|
||||
i++
|
||||
continue
|
||||
}
|
||||
return i, buf[start:i], fmt.Errorf("bad timestamp")
|
||||
|
@ -559,7 +615,7 @@ func scanTime(buf []byte, i int) (int, []byte, error) {
|
|||
if buf[i] == '\n' {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
return i, buf[start:i], nil
|
||||
}
|
||||
|
@ -577,7 +633,7 @@ func scanNumber(buf []byte, i int) (int, error) {
|
|||
|
||||
// Is negative number?
|
||||
if i < len(buf) && buf[i] == '-' {
|
||||
i += 1
|
||||
i++
|
||||
// There must be more characters now, as just '-' is illegal.
|
||||
if i == len(buf) {
|
||||
return i, fmt.Errorf("invalid number")
|
||||
|
@ -601,12 +657,12 @@ func scanNumber(buf []byte, i int) (int, error) {
|
|||
|
||||
if buf[i] == 'i' && i > start && !isInt {
|
||||
isInt = true
|
||||
i += 1
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if buf[i] == '.' {
|
||||
decimals += 1
|
||||
decimals++
|
||||
}
|
||||
|
||||
// Can't have more than 1 decimal (e.g. 1.1.1 should fail)
|
||||
|
@ -617,13 +673,13 @@ func scanNumber(buf []byte, i int) (int, error) {
|
|||
// `e` is valid for floats but not as the first char
|
||||
if i > start && (buf[i] == 'e' || buf[i] == 'E') {
|
||||
scientific = true
|
||||
i += 1
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// + and - are only valid at this point if they follow an e (scientific notation)
|
||||
if (buf[i] == '+' || buf[i] == '-') && (buf[i-1] == 'e' || buf[i-1] == 'E') {
|
||||
i += 1
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -635,7 +691,7 @@ func scanNumber(buf []byte, i int) (int, error) {
|
|||
if !isNumeric(buf[i]) {
|
||||
return i, fmt.Errorf("invalid number")
|
||||
}
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
if isInt && (decimals > 0 || scientific) {
|
||||
return i, fmt.Errorf("invalid number")
|
||||
|
@ -680,7 +736,7 @@ func scanBoolean(buf []byte, i int) (int, []byte, error) {
|
|||
return i, buf[start:i], fmt.Errorf("invalid boolean")
|
||||
}
|
||||
|
||||
i += 1
|
||||
i++
|
||||
for {
|
||||
if i >= len(buf) {
|
||||
break
|
||||
|
@ -689,7 +745,7 @@ func scanBoolean(buf []byte, i int) (int, []byte, error) {
|
|||
if buf[i] == ',' || buf[i] == ' ' {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
|
||||
// Single char bool (t, T, f, F) is ok
|
||||
|
@ -758,7 +814,7 @@ func scanLine(buf []byte, i int) (int, []byte) {
|
|||
|
||||
// If we see a double quote, makes sure it is not escaped
|
||||
if fields && buf[i] == '"' && (i-1 > 0 && buf[i-1] != '\\') {
|
||||
i += 1
|
||||
i++
|
||||
quoted = !quoted
|
||||
continue
|
||||
}
|
||||
|
@ -767,7 +823,7 @@ func scanLine(buf []byte, i int) (int, []byte) {
|
|||
break
|
||||
}
|
||||
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
|
||||
return i, buf[start:i]
|
||||
|
@ -793,7 +849,7 @@ func scanTo(buf []byte, i int, stop byte) (int, []byte) {
|
|||
if buf[i] == stop {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
|
||||
return i, buf[start:i]
|
||||
|
@ -804,24 +860,26 @@ func scanTo(buf []byte, i int, stop byte) (int, []byte) {
|
|||
// spaces, they are skipped.
|
||||
func scanToSpaceOr(buf []byte, i int, stop byte) (int, []byte) {
|
||||
start := i
|
||||
for {
|
||||
// reached the end of buf?
|
||||
if i >= len(buf) {
|
||||
break
|
||||
}
|
||||
|
||||
if buf[i] == '\\' {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
// reached end of block?
|
||||
if buf[i] == stop || buf[i] == ' ' {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
if buf[i] == stop || buf[i] == ' ' {
|
||||
return i, buf[start:i]
|
||||
}
|
||||
|
||||
return i, buf[start:i]
|
||||
for {
|
||||
i++
|
||||
if buf[i-1] == '\\' {
|
||||
continue
|
||||
}
|
||||
|
||||
// reached the end of buf?
|
||||
if i >= len(buf) {
|
||||
return i, buf[start:i]
|
||||
}
|
||||
|
||||
// reached end of block?
|
||||
if buf[i] == stop || buf[i] == ' ' {
|
||||
return i, buf[start:i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scanTagValue(buf []byte, i int) (int, []byte) {
|
||||
|
@ -839,7 +897,7 @@ func scanTagValue(buf []byte, i int) (int, []byte) {
|
|||
if buf[i] == ',' {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
return i, buf[start:i]
|
||||
}
|
||||
|
@ -860,7 +918,7 @@ func scanFieldValue(buf []byte, i int) (int, []byte) {
|
|||
|
||||
// Quoted value? (e.g. string)
|
||||
if buf[i] == '"' {
|
||||
i += 1
|
||||
i++
|
||||
quoted = !quoted
|
||||
continue
|
||||
}
|
||||
|
@ -868,7 +926,7 @@ func scanFieldValue(buf []byte, i int) (int, []byte) {
|
|||
if buf[i] == ',' && !quoted {
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
return i, buf[start:i]
|
||||
}
|
||||
|
@ -914,18 +972,18 @@ func escapeStringField(in string) string {
|
|||
if in[i] == '\\' {
|
||||
out = append(out, '\\')
|
||||
out = append(out, '\\')
|
||||
i += 1
|
||||
i++
|
||||
continue
|
||||
}
|
||||
// escape double-quotes
|
||||
if in[i] == '"' {
|
||||
out = append(out, '\\')
|
||||
out = append(out, '"')
|
||||
i += 1
|
||||
i++
|
||||
continue
|
||||
}
|
||||
out = append(out, in[i])
|
||||
i += 1
|
||||
i++
|
||||
|
||||
}
|
||||
return string(out)
|
||||
|
@ -953,7 +1011,7 @@ func unescapeStringField(in string) string {
|
|||
continue
|
||||
}
|
||||
out = append(out, in[i])
|
||||
i += 1
|
||||
i++
|
||||
|
||||
}
|
||||
return string(out)
|
||||
|
@ -982,7 +1040,7 @@ func NewPoint(name string, tags Tags, fields Fields, time time.Time) (Point, err
|
|||
}, nil
|
||||
}
|
||||
|
||||
// NewPoint returns a new point with the given measurement name, tags, fields and timestamp. If
|
||||
// MustNewPoint returns a new point with the given measurement name, tags, fields and timestamp. If
|
||||
// an unsupported field value (NaN) is passed, this function panics.
|
||||
func MustNewPoint(name string, tags Tags, fields Fields, time time.Time) Point {
|
||||
pt, err := NewPoint(name, tags, fields, time)
|
||||
|
@ -1061,12 +1119,13 @@ func (p *point) Tags() Tags {
|
|||
|
||||
tags[string(unescapeTag(key))] = string(unescapeTag(value))
|
||||
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// MakeKey creates a key for a set of tags.
|
||||
func MakeKey(name []byte, tags Tags) []byte {
|
||||
// unescape the name and then re-escape it to avoid double escaping.
|
||||
// The key should always be stored in escaped form.
|
||||
|
@ -1175,8 +1234,11 @@ func (p *point) UnixNano() int64 {
|
|||
return p.Time().UnixNano()
|
||||
}
|
||||
|
||||
// Tags represents a mapping between a Point's tag names and their
|
||||
// values.
|
||||
type Tags map[string]string
|
||||
|
||||
// HashKey hashes all of a tag's keys.
|
||||
func (t Tags) HashKey() []byte {
|
||||
// Empty maps marshal to empty bytes.
|
||||
if len(t) == 0 {
|
||||
|
@ -1199,7 +1261,7 @@ func (t Tags) HashKey() []byte {
|
|||
i := 0
|
||||
for k, v := range escaped {
|
||||
keys[i] = k
|
||||
i += 1
|
||||
i++
|
||||
sz += len(k) + len(v)
|
||||
}
|
||||
keys = keys[:i]
|
||||
|
@ -1210,11 +1272,11 @@ func (t Tags) HashKey() []byte {
|
|||
idx := 0
|
||||
for _, k := range keys {
|
||||
buf[idx] = ','
|
||||
idx += 1
|
||||
idx++
|
||||
copy(buf[idx:idx+len(k)], k)
|
||||
idx += len(k)
|
||||
buf[idx] = '='
|
||||
idx += 1
|
||||
idx++
|
||||
v := escaped[k]
|
||||
copy(buf[idx:idx+len(v)], v)
|
||||
idx += len(v)
|
||||
|
@ -1222,6 +1284,8 @@ func (t Tags) HashKey() []byte {
|
|||
return b[:idx]
|
||||
}
|
||||
|
||||
// Fields represents a mapping between a Point's field names and their
|
||||
// values.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
func parseNumber(val []byte) (interface{}, error) {
|
||||
|
@ -1287,7 +1351,7 @@ func newFieldsFromBinary(buf []byte) Fields {
|
|||
}
|
||||
}
|
||||
fields[string(name)] = value
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
@ -1300,9 +1364,9 @@ func (p Fields) MarshalBinary() []byte {
|
|||
b := []byte{}
|
||||
keys := make([]string, len(p))
|
||||
i := 0
|
||||
for k, _ := range p {
|
||||
for k := range p {
|
||||
keys[i] = k
|
||||
i += 1
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
|
|
|
@ -163,41 +163,22 @@ func TestParsePointWhitespaceValue(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParsePointSingleEquals(t *testing.T) {
|
||||
pts, err := models.ParsePointsString("=")
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. expected error`, "=")
|
||||
}
|
||||
|
||||
if exp := 0; len(pts) != exp {
|
||||
t.Errorf(`ParsePoints("%s") len mismatch. got %v, exp %v`, "", len(pts), exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePointNoFields(t *testing.T) {
|
||||
_, err := models.ParsePointsString("cpu_load_short,host=server01,region=us-west")
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu_load_short,host=server01,region=us-west")
|
||||
expectedSuffix := "missing fields"
|
||||
examples := []string{
|
||||
"cpu_load_short,host=server01,region=us-west",
|
||||
"cpu",
|
||||
"cpu,host==",
|
||||
"=",
|
||||
}
|
||||
|
||||
_, err = models.ParsePointsString("cpu")
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu")
|
||||
}
|
||||
|
||||
_, err = models.ParsePointsString("cpu,")
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu,")
|
||||
}
|
||||
|
||||
_, err = models.ParsePointsString("cpu, value=1")
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu, value=1")
|
||||
}
|
||||
|
||||
_, err = models.ParsePointsString("cpu,,, value=1")
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu,,, value=1")
|
||||
for i, example := range examples {
|
||||
_, err := models.ParsePointsString(example)
|
||||
if err == nil {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got nil, exp error`, i, example)
|
||||
} else if !strings.HasSuffix(err.Error(), expectedSuffix) {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got %q, exp suffix %q`, i, example, err, expectedSuffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,51 +187,84 @@ func TestParsePointNoTimestamp(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParsePointMissingQuote(t *testing.T) {
|
||||
_, err := models.ParsePointsString(`cpu,host=serverA value="test`)
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu")
|
||||
expectedSuffix := "unbalanced quotes"
|
||||
examples := []string{
|
||||
`cpu,host=serverA value="test`,
|
||||
`cpu,host=serverA value="test""`,
|
||||
}
|
||||
|
||||
for i, example := range examples {
|
||||
_, err := models.ParsePointsString(example)
|
||||
if err == nil {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got nil, exp error`, i, example)
|
||||
} else if !strings.HasSuffix(err.Error(), expectedSuffix) {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got %q, exp suffix %q`, i, example, err, expectedSuffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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`)
|
||||
expectedSuffix := "missing tag key"
|
||||
examples := []string{
|
||||
`cpu, value=1`,
|
||||
`cpu,`,
|
||||
`cpu,,,`,
|
||||
`cpu,host=serverA,=us-east value=1i`,
|
||||
`cpu,host=serverAa\,,=us-east value=1i`,
|
||||
`cpu,host=serverA\,,=us-east value=1i`,
|
||||
`cpu, =serverA value=1i`,
|
||||
}
|
||||
|
||||
_, err = models.ParsePointsString(`cpu,host=serverAa\,,=us-east value=1i`)
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverAa\,,=us-east value=1i`)
|
||||
for i, example := range examples {
|
||||
_, err := models.ParsePointsString(example)
|
||||
if err == nil {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got nil, exp error`, i, example)
|
||||
} else if !strings.HasSuffix(err.Error(), expectedSuffix) {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got %q, exp suffix %q`, i, example, err, expectedSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
_, 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`)
|
||||
}
|
||||
|
||||
_, err = models.ParsePointsString(`cpu,host=serverA,\ =us-east value=1i`)
|
||||
_, err := models.ParsePointsString(`cpu,host=serverA,\ =us-east value=1i`)
|
||||
if err != nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got %v, exp nil`, `cpu,host=serverA,\ =us-east value=1i`, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePointMissingTagValue(t *testing.T) {
|
||||
_, err := models.ParsePointsString(`cpu,host value=1i`)
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host value=1i`)
|
||||
expectedSuffix := "missing tag value"
|
||||
examples := []string{
|
||||
`cpu,host`,
|
||||
`cpu,host,`,
|
||||
`cpu,host value=1i`,
|
||||
`cpu,host=serverA,region value=1i`,
|
||||
`cpu,host=serverA,region= value=1i`,
|
||||
`cpu,host=serverA,region=,zone=us-west value=1i`,
|
||||
}
|
||||
|
||||
_, err = models.ParsePointsString(`cpu,host=serverA,region value=1i`)
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,region value=1i`)
|
||||
for i, example := range examples {
|
||||
_, err := models.ParsePointsString(example)
|
||||
if err == nil {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got nil, exp error`, i, example)
|
||||
} else if !strings.HasSuffix(err.Error(), expectedSuffix) {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got %q, exp suffix %q`, i, example, err, expectedSuffix)
|
||||
}
|
||||
}
|
||||
_, err = models.ParsePointsString(`cpu,host=serverA,region= value=1i`)
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,region= value=1i`)
|
||||
}
|
||||
|
||||
func TestParsePointInvalidTagFormat(t *testing.T) {
|
||||
expectedSuffix := "invalid tag format"
|
||||
examples := []string{
|
||||
`cpu,host=f=o,`,
|
||||
`cpu,host=f\==o,`,
|
||||
}
|
||||
_, err = models.ParsePointsString(`cpu,host=serverA,region=,zone=us-west value=1i`)
|
||||
if err == nil {
|
||||
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,region=,zone=us-west value=1i`)
|
||||
|
||||
for i, example := range examples {
|
||||
_, err := models.ParsePointsString(example)
|
||||
if err == nil {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got nil, exp error`, i, example)
|
||||
} else if !strings.HasSuffix(err.Error(), expectedSuffix) {
|
||||
t.Errorf(`[Example %d] ParsePoints("%s") mismatch. got %q, exp suffix %q`, i, example, err, expectedSuffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -556,6 +570,30 @@ func TestParsePointUnescape(t *testing.T) {
|
|||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// equals in measurement name
|
||||
test(t, `cpu\=load,region=east value=1.0`,
|
||||
models.MustNewPoint(
|
||||
`cpu\=load`, // backslash is literal
|
||||
models.Tags{
|
||||
"region": "east",
|
||||
},
|
||||
models.Fields{
|
||||
"value": 1.0,
|
||||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// equals in measurement name
|
||||
test(t, `cpu=load,region=east value=1.0`,
|
||||
models.MustNewPoint(
|
||||
`cpu=load`, // literal equals is fine in measurement name
|
||||
models.Tags{
|
||||
"region": "east",
|
||||
},
|
||||
models.Fields{
|
||||
"value": 1.0,
|
||||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// commas in tag names
|
||||
test(t, `cpu,region\,zone=east value=1.0`,
|
||||
models.MustNewPoint("cpu",
|
||||
|
@ -567,11 +605,22 @@ func TestParsePointUnescape(t *testing.T) {
|
|||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// spaces in tag names
|
||||
// spaces in tag name
|
||||
test(t, `cpu,region\ zone=east value=1.0`,
|
||||
models.MustNewPoint("cpu",
|
||||
models.Tags{
|
||||
"region zone": "east", // comma in the tag key
|
||||
"region zone": "east", // space in the tag name
|
||||
},
|
||||
models.Fields{
|
||||
"value": 1.0,
|
||||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// space is tag name
|
||||
test(t, `cpu,\ =east value=1.0`,
|
||||
models.MustNewPoint("cpu",
|
||||
models.Tags{
|
||||
" ": "east", // tag name is single space
|
||||
},
|
||||
models.Fields{
|
||||
"value": 1.0,
|
||||
|
@ -657,6 +706,18 @@ func TestParsePointUnescape(t *testing.T) {
|
|||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// backslash literal followed by escaped characters
|
||||
test(t, `cpu,regions=\\,\,\=east value=1.0`,
|
||||
models.MustNewPoint(
|
||||
"cpu",
|
||||
models.Tags{
|
||||
"regions": `\,,=east`,
|
||||
},
|
||||
models.Fields{
|
||||
"value": 1.0,
|
||||
},
|
||||
time.Unix(0, 0)))
|
||||
|
||||
// field keys using escape char.
|
||||
test(t, `cpu \a=1i`,
|
||||
models.MustNewPoint(
|
||||
|
@ -1126,7 +1187,7 @@ func TestNewPointLargeNumberOfTags(t *testing.T) {
|
|||
}
|
||||
|
||||
if len(pt[0].Tags()) != 255 {
|
||||
t.Fatalf("ParsePoints() with max tags failed: %v", err)
|
||||
t.Fatalf("expected %d tags, got %d", 255, len(pt[0].Tags()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1166,7 +1227,7 @@ func TestParsePointKeyUnsorted(t *testing.T) {
|
|||
pt := pts[0]
|
||||
|
||||
if exp := "cpu,first=2,last=1"; string(pt.Key()) != exp {
|
||||
t.Errorf("ParsePoint key not sorted. got %v, exp %v", pt.Key(), exp)
|
||||
t.Errorf("ParsePoint key not sorted. got %v, exp %v", string(pt.Key()), exp)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ func (r *Row) tagsKeys() []string {
|
|||
return a
|
||||
}
|
||||
|
||||
// Rows represents a collection of rows. Rows implements sort.Interface.
|
||||
type Rows []*Row
|
||||
|
||||
func (p Rows) Len() int { return len(p) }
|
||||
|
|
Loading…
Reference in New Issue