From d6ca48182e58ae43b9e420056af7b3ab0e22fb26 Mon Sep 17 00:00:00 2001 From: Xentripetal Date: Tue, 28 Dec 2021 13:01:53 -0600 Subject: [PATCH] initial work for avro OCF files --- format/avro/codecs/array.go | 18 ++++ format/avro/codecs/bool.go | 22 +++++ format/avro/codecs/bytes.go | 20 ++++ format/avro/codecs/codec.go | 1 + format/avro/codecs/double.go | 20 ++++ format/avro/codecs/enum.go | 22 +++++ format/avro/codecs/fixed.go | 19 ++++ format/avro/codecs/float.go | 38 ++++++++ format/avro/codecs/int.go | 22 +++++ format/avro/codecs/long.go | 1 + format/avro/codecs/map.go | 45 +++++++++ format/avro/codecs/null.go | 38 ++++++++ format/avro/codecs/record.go | 15 +++ format/avro/codecs/string.go | 31 +++++++ format/avro/codecs/union.go | 26 ++++++ format/avro/ocf.go | 87 ++++++++++++++++++ format/avro/schema/schema.go | 33 +++++++ .../firstBlockCountNotGreaterThanZero.avro | Bin 0 -> 51 bytes .../firstBlockCountNotGreaterThanZero.fqtest | 12 +++ format/avro/testdata/quickstop-deflate.avro | Bin 0 -> 22582 bytes format/avro/testdata/quickstop-deflate.fqtest | 20 ++++ format/avro/testdata/twitter.avro | Bin 0 -> 543 bytes format/avro/testdata/twitter.fqtest | 61 ++++++++++++ 23 files changed, 551 insertions(+) create mode 100644 format/avro/codecs/array.go create mode 100644 format/avro/codecs/bool.go create mode 100644 format/avro/codecs/bytes.go create mode 100644 format/avro/codecs/codec.go create mode 100644 format/avro/codecs/double.go create mode 100644 format/avro/codecs/enum.go create mode 100644 format/avro/codecs/fixed.go create mode 100644 format/avro/codecs/float.go create mode 100644 format/avro/codecs/int.go create mode 100644 format/avro/codecs/long.go create mode 100644 format/avro/codecs/map.go create mode 100644 format/avro/codecs/null.go create mode 100644 format/avro/codecs/record.go create mode 100644 format/avro/codecs/string.go create mode 100644 format/avro/codecs/union.go create mode 100644 format/avro/ocf.go create mode 100644 format/avro/schema/schema.go create mode 100644 format/avro/testdata/firstBlockCountNotGreaterThanZero.avro create mode 100644 format/avro/testdata/firstBlockCountNotGreaterThanZero.fqtest create mode 100644 format/avro/testdata/quickstop-deflate.avro create mode 100644 format/avro/testdata/quickstop-deflate.fqtest create mode 100644 format/avro/testdata/twitter.avro create mode 100644 format/avro/testdata/twitter.fqtest diff --git a/format/avro/codecs/array.go b/format/avro/codecs/array.go new file mode 100644 index 00000000..34dbccb1 --- /dev/null +++ b/format/avro/codecs/array.go @@ -0,0 +1,18 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +type BoolCodec struct{} + +func (l BoolCodec) Decode(name string, d *decode.D) { + d.FieldBoolFn(name, func(d *decode.D) bool { + return d.U8() >= 1 + }) +} + +func BuildBoolCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &BoolCodec{}, nil +} diff --git a/format/avro/codecs/bool.go b/format/avro/codecs/bool.go new file mode 100644 index 00000000..750aaee9 --- /dev/null +++ b/format/avro/codecs/bool.go @@ -0,0 +1,22 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + + +type NullCodec struct {} + +func (l NullCodec) Decode(d *decode.D) interface{}{ + // null is written as zero bytes. + return nil +} + +func (l NullCodec) Type() CodecType { + return SCALAR +} + +func BuildNullCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &NullCodec{}, nil +} diff --git a/format/avro/codecs/bytes.go b/format/avro/codecs/bytes.go new file mode 100644 index 00000000..b04c92b3 --- /dev/null +++ b/format/avro/codecs/bytes.go @@ -0,0 +1,20 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +type FloatCodec struct{} + +func (l FloatCodec) Decode(d *decode.D) interface{} { + return d.F32() +} + +func (l FloatCodec) Type() CodecType { + return SCALAR +} + +func BuildFloatCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &FloatCodec{}, nil +} diff --git a/format/avro/codecs/codec.go b/format/avro/codecs/codec.go new file mode 100644 index 00000000..3e061474 --- /dev/null +++ b/format/avro/codecs/codec.go @@ -0,0 +1 @@ +package codecs diff --git a/format/avro/codecs/double.go b/format/avro/codecs/double.go new file mode 100644 index 00000000..b04c92b3 --- /dev/null +++ b/format/avro/codecs/double.go @@ -0,0 +1,20 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +type FloatCodec struct{} + +func (l FloatCodec) Decode(d *decode.D) interface{} { + return d.F32() +} + +func (l FloatCodec) Type() CodecType { + return SCALAR +} + +func BuildFloatCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &FloatCodec{}, nil +} diff --git a/format/avro/codecs/enum.go b/format/avro/codecs/enum.go new file mode 100644 index 00000000..f55cc7e2 --- /dev/null +++ b/format/avro/codecs/enum.go @@ -0,0 +1,22 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +type BytesCodec struct{} + +func (l BytesCodec) Decode(d *decode.D) interface{} { + length := d.FieldSFn("length", VarZigZag) + d.FieldRawLen("value", length*8) + return nil +} + +func (l BytesCodec) Type() CodecType { + return STRUCT +} + +func BuildBytesCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &BytesCodec{}, nil +} diff --git a/format/avro/codecs/fixed.go b/format/avro/codecs/fixed.go new file mode 100644 index 00000000..3a163ac6 --- /dev/null +++ b/format/avro/codecs/fixed.go @@ -0,0 +1,19 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +type BytesCodec struct{} + +func (l BytesCodec) Decode(name string, d *decode.D) { + // What if its a record with a field called name_len? + // using a struct is probably a better idea. But it makes it less usable + length := d.FieldSFn(name+"_len", VarZigZag) + d.FieldRawLen("name", length*8) +} + +func BuildBytesCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &BytesCodec{}, nil +} diff --git a/format/avro/codecs/float.go b/format/avro/codecs/float.go new file mode 100644 index 00000000..a83d58e1 --- /dev/null +++ b/format/avro/codecs/float.go @@ -0,0 +1,38 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +const intMask = byte(127) +const intFlag = byte(128) + +// readLong reads a variable length zig zag long from the current position in decoder +func VarZigZag(d *decode.D) int64 { + var value uint64 + var shift uint + for d.NotEnd() { + b := byte(d.U8()) + value |= uint64(b&intMask) << shift + if b&intFlag == 0 { + return int64(value>>1) ^ -int64(value&1) + } + shift += 7 + } + panic("unexpected end of data") +} + +type LongCodec struct {} + +func (l LongCodec) Decode(d *decode.D) interface{}{ + return VarZigZag(d) +} + +func (l LongCodec) Type() CodecType { + return SCALAR +} + +func BuildLongCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &LongCodec{}, nil +} diff --git a/format/avro/codecs/int.go b/format/avro/codecs/int.go new file mode 100644 index 00000000..a47a5d12 --- /dev/null +++ b/format/avro/codecs/int.go @@ -0,0 +1,22 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + + +type BoolCodec struct {} + +func (l BoolCodec) Decode(d *decode.D) interface{}{ + // a boolean is written as a single byte whose value is either 0 (false) or 1 (true). + return d.U8() != 0 +} + +func (l BoolCodec) Type() CodecType { + return SCALAR +} + +func BuildBoolCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &BoolCodec{}, nil +} diff --git a/format/avro/codecs/long.go b/format/avro/codecs/long.go new file mode 100644 index 00000000..3e061474 --- /dev/null +++ b/format/avro/codecs/long.go @@ -0,0 +1 @@ +package codecs diff --git a/format/avro/codecs/map.go b/format/avro/codecs/map.go new file mode 100644 index 00000000..b885d7ea --- /dev/null +++ b/format/avro/codecs/map.go @@ -0,0 +1,45 @@ +package codecs + +import ( + "errors" + "fmt" + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +type ArrayCodec struct { + valueCodec Codec +} + +func (l ArrayCodec) Decode(name string, d *decode.D) { + d.FieldArray(name, func(d *decode.D) { + count := int64(-1) + for count != 0 { + d.FieldStruct(name, func(d *decode.D) { + count = d.FieldSFn("count", VarZigZag) + if count < 0 { + d.FieldSFn("size", VarZigZag) + count *= -1 + } + d.FieldArray("entries", func(d *decode.D) { + for i := int64(0); i < count; i++ { + l.valueCodec.Decode("entry", d) + } + }) + }) + } + }) +} + +func BuildArrayCodec(schema schema.SimplifiedSchema) (Codec, error) { + if schema.Items == nil { + return nil, errors.New("array schema must have items") + } + + valueCodec, err := BuildCodec(*schema.Items) + if err != nil { + return nil, fmt.Errorf("ArrayCodec: %s", err) + } + + return &ArrayCodec{valueCodec: valueCodec}, nil +} diff --git a/format/avro/codecs/null.go b/format/avro/codecs/null.go new file mode 100644 index 00000000..a83d58e1 --- /dev/null +++ b/format/avro/codecs/null.go @@ -0,0 +1,38 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +const intMask = byte(127) +const intFlag = byte(128) + +// readLong reads a variable length zig zag long from the current position in decoder +func VarZigZag(d *decode.D) int64 { + var value uint64 + var shift uint + for d.NotEnd() { + b := byte(d.U8()) + value |= uint64(b&intMask) << shift + if b&intFlag == 0 { + return int64(value>>1) ^ -int64(value&1) + } + shift += 7 + } + panic("unexpected end of data") +} + +type LongCodec struct {} + +func (l LongCodec) Decode(d *decode.D) interface{}{ + return VarZigZag(d) +} + +func (l LongCodec) Type() CodecType { + return SCALAR +} + +func BuildLongCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &LongCodec{}, nil +} diff --git a/format/avro/codecs/record.go b/format/avro/codecs/record.go new file mode 100644 index 00000000..0bb869f2 --- /dev/null +++ b/format/avro/codecs/record.go @@ -0,0 +1,15 @@ +package codecs + +import "github.com/wader/fq/pkg/decode" + + +type StringCodec struct {} + +func (l StringCodec) Decode(d *decode.D) { + length := d.FieldSFn("length", VarZigZag) + d.FieldUTF8("value", int(length)) +} + +func BuildStringCodec(schema SimplifiedSchema) (Codec, error) { + return &StringCodec{}, nil +} diff --git a/format/avro/codecs/string.go b/format/avro/codecs/string.go new file mode 100644 index 00000000..cc6b6120 --- /dev/null +++ b/format/avro/codecs/string.go @@ -0,0 +1,31 @@ +package codecs + +import "github.com/wader/fq/pkg/decode" + +const intMask = byte(127) +const intFlag = byte(128) +// readLong reads a variable length zig zag long from the current position in decoder +func VarZigZag(d *decode.D) int64 { + var value uint64 + var shift uint + for d.NotEnd() { + b := byte(d.U8()) + value |= uint64(b&intMask) << shift + if b&intFlag == 0 { + return int64(value>>1) ^ -int64(value&1) + } + shift += 7 + } + panic("unexpected end of data") +} + +type LongCodec struct {} + +func (l LongCodec) Decode(d *decode.D) { + d.Value.V = VarZigZag(d) +} + +func BuildLongCodec(schema SimplifiedSchema) (Codec, error) { + c := LongCodec{} + return &c, nil +} diff --git a/format/avro/codecs/union.go b/format/avro/codecs/union.go new file mode 100644 index 00000000..3fa4e7f4 --- /dev/null +++ b/format/avro/codecs/union.go @@ -0,0 +1,26 @@ +package codecs + +import ( + "github.com/wader/fq/format/avro/schema" + "github.com/wader/fq/pkg/decode" +) + +type EnumCodec struct{ + symbols []string +} + +func (l EnumCodec) Decode(d *decode.D) interface{} { + value := int(VarZigZag(d)) + if value >= len(l.symbols) { + d.Fatalf("invalid enum value: %d", value) + } + return l.symbols[value] +} + +func (l EnumCodec) Type() CodecType { + return SCALAR +} + +func BuildEnumCodec(schema schema.SimplifiedSchema) (Codec, error) { + return &EnumCodec{symbols: schema.Symbols}, nil +} diff --git a/format/avro/ocf.go b/format/avro/ocf.go new file mode 100644 index 00000000..6c2e5b70 --- /dev/null +++ b/format/avro/ocf.go @@ -0,0 +1,87 @@ +package avro + +import ( + "github.com/wader/fq/format" + "github.com/wader/fq/format/registry" + "github.com/wader/fq/pkg/decode" +) + +var jsonGroup decode.Group + +func init() { + registry.MustRegister(decode.Format{ + Name: format.AVRO_OCF, + Description: "Avro object container file", + Groups: []string{format.PROBE}, + DecodeFn: avroDecode, + Dependencies: []decode.Dependency{ + {Names: []string{format.JSON}, Group: &jsonGroup}, + }, + }) +} + +const headerMetadataSchema = `{"type": "map", "values": "bytes"}` +const intMask = byte(127) +const intFlag = byte(128) + +// readLong reads a variable length zig zag long from the current position in decoder +// and returns the decoded value and the number of bytes read. +func varZigZag(d *decode.D) int64 { + var value uint64 + var shift uint + for d.NotEnd() { + b := byte(d.U8()) + value |= uint64(b&intMask) << shift + if b&intFlag == 0 { + return int64(value>>1) ^ -int64(value&1) + } + shift += 7 + } + panic("unexpected end of data") +} + +func avroDecode(d *decode.D, in interface{}) interface{} { + d.FieldRawLen("magic", 4*8, d.AssertBitBuf([]byte{'O', 'b', 'j', 1})) + //var schema []byte + var blockCount int64 = -1 + d.FieldStructArrayLoop("meta", "block", + func() bool { return blockCount != 0 }, + func(d *decode.D) { + blockCount = d.FieldSFn("count", varZigZag) + // If its negative, then theres another long representing byte size + if blockCount < 0 { + blockCount *= -1 + d.FieldSFn("size", varZigZag) + } + if blockCount == 0 { + return + } + + var i int64 = 0 + d.FieldStructArrayLoop("entries", "entry", func() bool { return i < blockCount }, func(d *decode.D) { + keyL := d.FieldSFn("key_length", varZigZag) + key := d.FieldUTF8("key", int(keyL)) + valL := d.FieldSFn("value_length", varZigZag) + if key == "avro.schema" { + d.FieldFormatLen("value", valL*8, jsonGroup, nil) + } else { + d.FieldUTF8("value", int(valL)) + } + i++ + }) + }) + syncbb := d.FieldRawLen("sync", 16*8) + sync, err := syncbb.BytesLen(16) + if err != nil { + d.Fatalf("unable to read sync bytes: %v", err) + } + d.FieldStructArrayLoop("blocks", "block", func() bool { return d.NotEnd() }, func(d *decode.D) { + count := d.FieldSFn("count", varZigZag) + _ = count + size := d.FieldSFn("size", varZigZag) + d.FieldRawLen("data", size*8) + d.FieldRawLen("sync", 16*8, d.AssertBitBuf(sync)) + }) + + return nil +} diff --git a/format/avro/schema/schema.go b/format/avro/schema/schema.go new file mode 100644 index 00000000..fd411616 --- /dev/null +++ b/format/avro/schema/schema.go @@ -0,0 +1,33 @@ +package codecs + +const ( + NULL = "null" + BOOLEAN = "boolean" + INT = "int" + LONG = "long" + FLOAT = "float" + DOUBLE = "double" + BYTES = "bytes" + STRING = "string" + RECORD = "record" +) + +type SimplifiedSchema struct { + Type string + Name *string + Fields []SimplifiedSchemaField + Symbols *[]string + Items *SimplifiedSchema + LogicalType *string + Scale *int + Precision *int + UnionTypes []SimplifiedSchema + //Choosing not to handle Default as it adds a lot of complexity and this is used for showing the binary + //representation of the data, not fully parsing it. See https://github.com/linkedin/goavro/blob/master/record.go + //for how it could be handled. +} + +type SimplifiedSchemaField struct { + Name string + Type SimplifiedSchema +} diff --git a/format/avro/testdata/firstBlockCountNotGreaterThanZero.avro b/format/avro/testdata/firstBlockCountNotGreaterThanZero.avro new file mode 100644 index 0000000000000000000000000000000000000000..807ff98cff9302a3996c46910d272698ba0f20ff GIT binary patch literal 51 zcmeZI%3@>^ODrqO*DFrWNX<=@t5zzhEJ#(dQp(BCOINC8FfcSSHZe6bx3El1N=`{l GV*mh{Fb_BY literal 0 HcmV?d00001 diff --git a/format/avro/testdata/firstBlockCountNotGreaterThanZero.fqtest b/format/avro/testdata/firstBlockCountNotGreaterThanZero.fqtest new file mode 100644 index 00000000..9a7c8ed3 --- /dev/null +++ b/format/avro/testdata/firstBlockCountNotGreaterThanZero.fqtest @@ -0,0 +1,12 @@ +$ fq . firstBlockCountNotGreaterThanZero.avro + |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: firstBlockCountNotGreaterThanZero.avro (avro_ocf) +0x00|4f 62 6a 01 |Obj. | magic: raw bits (valid) +0x00| 02 16 61 76 72 6f 2e 73 63 68 65 6d| ..avro.schem| meta[0:2]: +0x10|61 1e 7b 22 74 79 70 65 22 3a 22 6c 6f 6e 67 22|a.{"type":"long"| +0x20|7d 00 |}. | +0x20| 30 31 32 33 34 35 36 37 38 39 61 62 63 64| 0123456789abcd| sync: raw bits +0x30|65 66 |ef | +0x30| 00| | .| | blocks[0:1]: +$ fq '.blocks[0]' firstBlockCountNotGreaterThanZero.avro + |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.blocks[0]{}: +0x30| 00| | .| | count: 0 diff --git a/format/avro/testdata/quickstop-deflate.avro b/format/avro/testdata/quickstop-deflate.avro new file mode 100644 index 0000000000000000000000000000000000000000..b9dd2c9849c1f1f113a715b5a4ee287d1dd35b03 GIT binary patch literal 22582 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 Nn$Lp`{_tJ6{|Aw>^#K3? literal 0 HcmV?d00001 diff --git a/format/avro/testdata/quickstop-deflate.fqtest b/format/avro/testdata/quickstop-deflate.fqtest new file mode 100644 index 00000000..9a1a0493 --- /dev/null +++ b/format/avro/testdata/quickstop-deflate.fqtest @@ -0,0 +1,20 @@ +$ fq . quickstop-deflate.avro + |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: quickstop-deflate.avro (avro_ocf) +0x0000|4f 62 6a 01 |Obj. | magic: raw bits (valid) +0x0000| 04 14 61 76 72 6f 2e 63 6f 64 65 63| ..avro.codec| meta[0:2]: +0x0010|0e 64 65 66 6c 61 74 65 16 61 76 72 6f 2e 73 63|.deflate.avro.sc| +* |until 0x119.7 (278) | | +0x0110| 93 e7 87 9e 02 95| ......| sync: raw bits +0x0120|d5 9e 4f 58 37 ad b2 a2 ce cd |..OX7..... | +0x0120| b4 09 be 22 8d db| ..."..| blocks[0:12]: +0x0130|6f 64 ac f9 19 c6 71 f9 37 49 8e 63 1d 55 55 b5|od....q.7I.c.UU.| +* |until 0x5835.7 (end) (22284) | | +$ fq '.blocks[0]' quickstop-deflate.avro + |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.blocks[0]{}: +0x120| b4 09 | .. | count: 602 +0x120| be 22 | ." | size: 2207 +0x120| 8d db| ..| data: raw bits (deflate encoded) +0x130|6f 64 ac f9 19 c6 71 f9 37 49 8e 63 1d 55 55 b5|od....q.7I.c.UU.| +* |until 0x9cc.7 (2207) | | +0x9c0| 93 e7 87| ...| sync: raw bits (valid) +0x9d0|9e 02 95 d5 9e 4f 58 37 ad b2 a2 ce cd |.....OX7..... | diff --git a/format/avro/testdata/twitter.avro b/format/avro/testdata/twitter.avro new file mode 100644 index 0000000000000000000000000000000000000000..0065d684ce7dfd39650ba2700d954683a51d73b2 GIT binary patch literal 543 zcmZ9Jze)o^5XKXTAYvJ;higO#*V$Z`2muQtXv9K9HhXiIHFrD5&YsbP_!MI48)#wW z8))wX2v&lig%6;6`C~NY&V2LxX1?9+oR%wV;>?iGvWuyBTN--yB113m3`#PgSM#(; zi?{77Ytfucgwr}_LKaO)sh_Gi*W?#8*Nqe=A$!5ma0bX3&6ri2S+F%q$4(rQbdZ)a z0*b(P5ehK~DJj<$NJD$#?mHuIRQqMxKB9Gi>COwlwgXAh77NClyUsHlq{u8{{D)Mo zXhoXMu?hXz@<6L|z>H)UtdoM$z%DB7jvXD3PYK*Db?69I5+-RuL^0g zKvIZuS?5VoiXS&O`19rJ_PKO<^ZtETc`C0?;%)Ac>;=$56G0|2G&N_Tn8`3GhDd4j v?;k#{SE`Lf4FvJGCc<26h#&xhCV