Merge pull request #2939 from influxdb/jw-line-parse

Return error when parsing invalid field values for line protocol
pull/2947/head
Jason Wilder 2015-06-11 15:46:44 -06:00
commit 86a71a073f
2 changed files with 261 additions and 10 deletions

View File

@ -95,7 +95,7 @@ func ParsePointsWithPrecision(buf []byte, defaultTime time.Time, precision strin
pt, err := parsePoint(block, defaultTime, precision)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to parse '%s': %v", string(block), err)
}
points = append(points, pt)
@ -318,6 +318,38 @@ func scanFields(buf []byte, i int) (int, []byte, error) {
continue
}
// If we see an =, ensure that there is at least on char after it
if buf[i] == '=' && !quoted {
// check for "... value="
if i+1 >= len(buf) {
return i, buf[start:i], fmt.Errorf("missing field value")
}
// check for "... value=,value2=..."
if buf[i+1] == ',' || buf[i+1] == ' ' {
return i, buf[start:i], fmt.Errorf("missing field value")
}
if isNumeric(buf[i+1]) || buf[i+1] == '-' {
var err error
i, _, err = scanNumber(buf, i+1)
if err != nil {
return i, buf[start:i], err
} else {
continue
}
// If next byte is not a double-quote, the value must be a boolean
} else if buf[i+1] != '"' {
var err error
i, _, err = scanBoolean(buf, i+1)
if err != nil {
return i, buf[start:i], err
} else {
continue
}
}
}
// reached end of block?
if buf[i] == ' ' && !quoted {
break
@ -357,6 +389,121 @@ func scanTime(buf []byte, i int) (int, []byte, error) {
return i, buf[start:i], nil
}
func isNumeric(b byte) bool {
return (b >= '0' && b <= '9') || b == '.'
}
// scanNumber returns the end position within buf, start at i after
// scanning over buf for an integer, or float. It returns an
// error if a invalid number is scanned.
func scanNumber(buf []byte, i int) (int, []byte, error) {
start := i
// Is negative number?
if i < len(buf) && buf[i] == '-' {
i += 1
}
decimals := 0
for {
if i >= len(buf) {
break
}
if buf[i] == ',' || buf[i] == ' ' {
break
}
if buf[i] == '.' {
decimals += 1
}
// Can't have more than 1 decimal (e.g. 1.1.1 should fail)
if decimals > 1 {
return i, buf[start:i], fmt.Errorf("invalid number")
}
// `e` is valid for floats but not as the first char
if i > start && (buf[i] == 'e') {
i += 1
continue
}
// + and - are only valid at this point if they follow an e (scientific notation)
if (buf[i] == '+' || buf[i] == '-') && buf[i-1] == 'e' {
i += 1
continue
}
if !isNumeric(buf[i]) {
return i, buf[start:i], fmt.Errorf("invalid number")
}
i += 1
}
return i, buf[start:i], nil
}
// scanBoolean returns the end position within buf, start at i after
// scanning over buf for boolean. Valid values for a boolean are
// t, T, true, TRUE, f, F, false, FALSE. It returns an error if a invalid boolean
// is scanned.
func scanBoolean(buf []byte, i int) (int, []byte, error) {
start := i
if i < len(buf) && (buf[i] != 't' && buf[i] != 'f' && buf[i] != 'T' && buf[i] != 'F') {
return i, buf[start:i], fmt.Errorf("invalid boolean")
}
i += 1
for {
if i >= len(buf) {
break
}
if buf[i] == ',' || buf[i] == ' ' {
break
}
i += 1
}
// Single char bool (t, T, f, F) is ok
if i-start == 1 {
return i, buf[start:i], nil
}
// length must be 4 for true or TRUE
if (buf[start] == 't' || buf[start] == 'T') && i-start != 4 {
return i, buf[start:i], fmt.Errorf("invalid boolean")
}
// length must be 5 for false or FALSE
if (buf[start] == 'f' || buf[start] == 'F') && i-start != 5 {
return i, buf[start:i], fmt.Errorf("invalid boolean")
}
// Otherwise
valid := false
switch buf[start] {
case 't':
valid = bytes.Equal(buf[start:i], []byte("true"))
case 'f':
valid = bytes.Equal(buf[start:i], []byte("false"))
case 'T':
valid = bytes.Equal(buf[start:i], []byte("TRUE"))
case 'F':
valid = bytes.Equal(buf[start:i], []byte("FALSE"))
}
if !valid {
return i, buf[start:i], fmt.Errorf("invalid boolean")
}
return i, buf[start:i], nil
}
// skipWhitespace returns the end position within buf, starting at i after
// scanning over spaces in tags
func skipWhitespace(buf []byte, i int) int {
@ -740,16 +887,14 @@ func newFieldsFromBinary(buf []byte) Fields {
} else if (valueBuf[0] >= '0' && valueBuf[0] <= '9') || valueBuf[0] == '-' || valueBuf[0] == '.' {
value, err = parseNumber(valueBuf)
if err != nil {
fmt.Printf("unable to parse number value '%v': %v\n", string(valueBuf), err)
value = float64(0)
panic(fmt.Sprintf("unable to parse number value '%v': %v", string(valueBuf), err))
}
// Otherwise parse it as bool
} else {
value, err = strconv.ParseBool(string(valueBuf))
if err != nil {
fmt.Printf("unable to parse bool value '%v': %v\n", string(valueBuf), err)
value = false
panic(fmt.Sprintf("unable to parse bool value '%v': %v\n", string(valueBuf), err))
}
}
fields[string(unescape(name))] = value

View File

@ -159,6 +159,108 @@ func TestParsePointMissingTagValue(t *testing.T) {
}
}
func TestParsePointMissingFieldValue(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=`)
if err == nil {
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu")
}
_, err = ParsePointsString(`cpu,host=serverA,region=us-west value= 1000000000`)
if err == nil {
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu")
}
_, err = ParsePointsString(`cpu,host=serverA,region=us-west value=,value2=1`)
if err == nil {
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, "cpu")
}
}
func TestParsePointBadNumber(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=1a`)
if err == nil {
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,region=us-west value=1a`)
}
}
func TestParsePointNumberNonNumeric(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=.1a`)
if err == nil {
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,region=us-west value=.1a`)
}
}
func TestParsePointNegativeWrongPlace(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=0.-1`)
if err == nil {
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,region=us-west value=0.-1`)
}
}
func TestParsePointFloatMultipleDecimals(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=1.1.1`)
if err == nil {
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,region=us-west value=1.1.1`)
}
println(err.Error())
}
func TestParsePointInteger(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=1`)
if err != nil {
t.Errorf(`ParsePoints("%s") mismatch. got %v, exp nil`, `cpu,host=serverA,region=us-west value=1`, err)
}
}
func TestParsePointNegativeInteger(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=-1`)
if err != nil {
t.Errorf(`ParsePoints("%s") mismatch. got %v, exp nil`, `cpu,host=serverA,region=us-west value=-1`, err)
}
}
func TestParsePointNegativeFloat(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=-1.0`)
if err != nil {
t.Errorf(`ParsePoints("%s") mismatch. got %v, exp nil`, `cpu,host=serverA,region=us-west value=-1.0`, err)
}
}
func TestParsePointFloatNoLeadingDigit(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=.1`)
if err != nil {
t.Errorf(`ParsePoints("%s") mismatch. got %v, exp nil`, `cpu,host=serverA,region=us-west value=-1.0`, err)
}
}
func TestParsePointFloatScientific(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=1.0e4`)
if err != nil {
t.Errorf(`ParsePoints("%s") mismatch. got %v, exp nil`, `cpu,host=serverA,region=us-west value=1.0e4`, err)
}
}
func TestParsePointFloatScientificDecimal(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=1.0e-4`)
if err != nil {
t.Errorf(`ParsePoints("%s") mismatch. got %v, exp nil`, `cpu,host=serverA,region=us-west value=1.0e-4`, err)
}
}
func TestParsePointFloatNegativeScientific(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=-1.0e-4`)
if err != nil {
t.Errorf(`ParsePoints("%s") mismatch. got %v, exp nil`, `cpu,host=serverA,region=us-west value=-1.0e-4`, err)
}
}
func TestParsePointBooleanInvalid(t *testing.T) {
_, err := ParsePointsString(`cpu,host=serverA,region=us-west value=a`)
if err == nil {
t.Errorf(`ParsePoints("%s") mismatch. got nil, exp error`, `cpu,host=serverA,region=us-west value=a`)
}
}
func TestParsePointUnescape(t *testing.T) {
// commas in measuremnt name
test(t, `cpu\,main,regions=east\,west value=1.0`,
@ -384,7 +486,7 @@ func TestParsePointWithStringWithEquals(t *testing.T) {
}
func TestParsePointWithBoolField(t *testing.T) {
test(t, `cpu,host=serverA,region=us-east bool=true,boolTrue=t,false=false,falseVal=f 1000000000`,
test(t, `cpu,host=serverA,region=us-east true=true,t=t,T=T,TRUE=TRUE,false=false,f=f,F=F,FALSE=FALSE 1000000000`,
NewPoint(
"cpu",
Tags{
@ -392,10 +494,14 @@ func TestParsePointWithBoolField(t *testing.T) {
"region": "us-east",
},
Fields{
"bool": true,
"boolTrue": true,
"false": false,
"falseVal": false,
"t": true,
"T": true,
"true": true,
"TRUE": true,
"f": false,
"F": false,
"false": false,
"FALSE": false,
},
time.Unix(1, 0)),
)