From ddea0bdf117793a94e6912cd360c6ca7262138b9 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 13 Sep 2019 14:18:35 -0700 Subject: [PATCH] Concurrent CSV parsing and reduce S3 select allocations (#8200) ``` CSV parsing, BEFORE: BenchmarkReaderBasic-12 2842 407533 ns/op 397860 B/op 957 allocs/op BenchmarkReaderReplace-12 2718 429914 ns/op 397844 B/op 957 allocs/op BenchmarkReaderReplaceTwo-12 2718 435556 ns/op 397855 B/op 957 allocs/op BenchmarkAggregateCount_100K-12 171 6798974 ns/op 16667102 B/op 308077 allocs/op BenchmarkAggregateCount_1M-12 19 65657411 ns/op 168057743 B/op 3146610 allocs/op BenchmarkSelectAll_10M-12 1 20882119900 ns/op 2758799896 B/op 41978762 allocs/op CSV parsing, AFTER: BenchmarkReaderBasic-12 3721 312549 ns/op 101920 B/op 338 allocs/op BenchmarkReaderReplace-12 3776 318810 ns/op 101993 B/op 340 allocs/op BenchmarkReaderReplaceTwo-12 3610 330967 ns/op 102012 B/op 341 allocs/op BenchmarkAggregateCount_100K-12 295 4149588 ns/op 3553623 B/op 103261 allocs/op BenchmarkAggregateCount_1M-12 30 37746503 ns/op 33827931 B/op 1049435 allocs/op BenchmarkSelectAll_10M-12 1 17608495800 ns/op 1416504040 B/op 21007082 allocs/op ~ benchcmp old.txt new.txt benchmark old ns/op new ns/op delta BenchmarkReaderBasic-12 407533 312549 -23.31% BenchmarkReaderReplace-12 429914 318810 -25.84% BenchmarkReaderReplaceTwo-12 435556 330967 -24.01% BenchmarkAggregateCount_100K-12 6798974 4149588 -38.97% BenchmarkAggregateCount_1M-12 65657411 37746503 -42.51% BenchmarkSelectAll_10M-12 20882119900 17608495800 -15.68% benchmark old allocs new allocs delta BenchmarkReaderBasic-12 957 338 -64.68% BenchmarkReaderReplace-12 957 340 -64.47% BenchmarkReaderReplaceTwo-12 957 341 -64.37% BenchmarkAggregateCount_100K-12 308077 103261 -66.48% BenchmarkAggregateCount_1M-12 3146610 1049435 -66.65% BenchmarkSelectAll_10M-12 41978762 21007082 -49.96% benchmark old bytes new bytes delta BenchmarkReaderBasic-12 397860 101920 -74.38% BenchmarkReaderReplace-12 397844 101993 -74.36% BenchmarkReaderReplaceTwo-12 397855 102012 -74.36% BenchmarkAggregateCount_100K-12 16667102 3553623 -78.68% BenchmarkAggregateCount_1M-12 168057743 33827931 -79.87% BenchmarkSelectAll_10M-12 2758799896 1416504040 -48.66% ``` ``` BenchmarkReaderHuge/97K-12 2200 540840 ns/op 184.32 MB/s 1604450 B/op 687 allocs/op BenchmarkReaderHuge/194K-12 1522 752257 ns/op 265.04 MB/s 2143135 B/op 1335 allocs/op BenchmarkReaderHuge/389K-12 1190 947858 ns/op 420.69 MB/s 3221831 B/op 2630 allocs/op BenchmarkReaderHuge/778K-12 806 1472486 ns/op 541.61 MB/s 5201856 B/op 5187 allocs/op BenchmarkReaderHuge/1557K-12 426 2575269 ns/op 619.36 MB/s 9101330 B/op 10233 allocs/op BenchmarkReaderHuge/3115K-12 286 4034656 ns/op 790.66 MB/s 12397968 B/op 16099 allocs/op BenchmarkReaderHuge/6230K-12 172 6830563 ns/op 934.05 MB/s 16008416 B/op 26844 allocs/op BenchmarkReaderHuge/12461K-12 100 11409467 ns/op 1118.39 MB/s 22655163 B/op 48107 allocs/op BenchmarkReaderHuge/24922K-12 66 19780395 ns/op 1290.19 MB/s 35158559 B/op 90216 allocs/op BenchmarkReaderHuge/49844K-12 34 37282559 ns/op 1369.03 MB/s 60528624 B/op 174497 allocs/op ``` --- docs/select/README.md | 1 + go.mod | 1 + pkg/s3select/csv/reader.go | 341 +++++++---- pkg/s3select/csv/reader_test.go | 561 ++++++++++++++++++- pkg/s3select/csv/record.go | 2 +- pkg/s3select/csv/recordtransform.go | 93 +++ pkg/s3select/csv/testdata/testdata.zip | Bin 0 -> 114303 bytes pkg/s3select/json/reader.go | 24 +- pkg/s3select/json/reader_test.go | 42 +- pkg/s3select/json/{data => testdata}/10.json | 0 pkg/s3select/json/{data => testdata}/11.json | 0 pkg/s3select/json/{data => testdata}/12.json | 0 pkg/s3select/json/{data => testdata}/2.json | 0 pkg/s3select/json/{data => testdata}/3.json | 0 pkg/s3select/json/{data => testdata}/4.json | 0 pkg/s3select/json/{data => testdata}/5.json | 0 pkg/s3select/json/{data => testdata}/6.json | 0 pkg/s3select/json/{data => testdata}/7.json | 0 pkg/s3select/json/{data => testdata}/8.json | 0 pkg/s3select/json/{data => testdata}/9.json | 0 pkg/s3select/parquet/reader.go | 15 +- pkg/s3select/select.go | 9 +- pkg/s3select/select_test.go | 141 ++--- 23 files changed, 1041 insertions(+), 189 deletions(-) create mode 100644 pkg/s3select/csv/recordtransform.go create mode 100644 pkg/s3select/csv/testdata/testdata.zip rename pkg/s3select/json/{data => testdata}/10.json (100%) rename pkg/s3select/json/{data => testdata}/11.json (100%) rename pkg/s3select/json/{data => testdata}/12.json (100%) rename pkg/s3select/json/{data => testdata}/2.json (100%) rename pkg/s3select/json/{data => testdata}/3.json (100%) rename pkg/s3select/json/{data => testdata}/4.json (100%) rename pkg/s3select/json/{data => testdata}/5.json (100%) rename pkg/s3select/json/{data => testdata}/6.json (100%) rename pkg/s3select/json/{data => testdata}/7.json (100%) rename pkg/s3select/json/{data => testdata}/8.json (100%) rename pkg/s3select/json/{data => testdata}/9.json (100%) diff --git a/docs/select/README.md b/docs/select/README.md index bc5504262..6f0f2e41f 100644 --- a/docs/select/README.md +++ b/docs/select/README.md @@ -106,3 +106,4 @@ For a more detailed SELECT SQL reference, please see [here](https://docs.aws.ama - Large numbers (outside of the signed 64-bit range) are not yet supported. - The Date [functions](https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-glacier-select-sql-reference-date.html) `DATE_ADD`, `DATE_DIFF`, `EXTRACT` and `UTCNOW` along with type conversion using `CAST` to the `TIMESTAMP` data type are currently supported. - AWS S3's [reserved keywords](https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-glacier-select-sql-reference-keyword-list.html) list is not yet respected. +- CSV input fields (even quoted) cannot contain newlines even if `RecordDelimiter` is something else. diff --git a/go.mod b/go.mod index dc82d7821..421cf6ab3 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/hashicorp/vault v1.1.0 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf github.com/json-iterator/go v1.1.7 + github.com/klauspost/compress v1.5.0 github.com/klauspost/pgzip v1.2.1 github.com/klauspost/readahead v1.3.0 github.com/klauspost/reedsolomon v1.9.1 diff --git a/pkg/s3select/csv/reader.go b/pkg/s3select/csv/reader.go index e14f85851..bdf24d922 100644 --- a/pkg/s3select/csv/reader.go +++ b/pkg/s3select/csv/reader.go @@ -17,89 +17,66 @@ package csv import ( + "bufio" "bytes" "encoding/csv" "fmt" "io" + "runtime" + "sync" "github.com/minio/minio/pkg/s3select/sql" ) -type recordReader struct { - reader io.Reader - recordDelimiter []byte - oneByte []byte - useOneByte bool -} - -func (rr *recordReader) Read(p []byte) (n int, err error) { - if rr.useOneByte { - p[0] = rr.oneByte[0] - rr.useOneByte = false - n, err = rr.reader.Read(p[1:]) - n++ - } else { - n, err = rr.reader.Read(p) - } - - if err != nil { - return 0, err - } - - if string(rr.recordDelimiter) == "\n" { - return n, nil - } - - for { - i := bytes.Index(p, rr.recordDelimiter) - if i < 0 { - break - } - - p[i] = '\n' - if len(rr.recordDelimiter) > 1 { - p = append(p[:i+1], p[i+len(rr.recordDelimiter):]...) - n-- - } - } - - if len(rr.recordDelimiter) == 1 || p[n-1] != rr.recordDelimiter[0] { - return n, nil - } - - if _, err = rr.reader.Read(rr.oneByte); err != nil { - return 0, err - } - - if rr.oneByte[0] == rr.recordDelimiter[1] { - p[n-1] = '\n' - return n, nil - } - - rr.useOneByte = true - return n, nil -} - // Reader - CSV record reader for S3Select. type Reader struct { args *ReaderArgs - readCloser io.ReadCloser - csvReader *csv.Reader - columnNames []string - nameIndexMap map[string]int64 + readCloser io.ReadCloser // raw input + buf *bufio.Reader // input to the splitter + columnNames []string // names of columns + nameIndexMap map[string]int64 // name to column index + current [][]string // current block of results to be returned + recordsRead int // number of records read in current slice + input chan *queueItem // input for workers + queue chan *queueItem // output from workers in order + err error // global error state, only touched by Reader.Read + bufferPool sync.Pool // pool of []byte objects for input + csvDstPool sync.Pool // pool of [][]string used for output + close chan struct{} // used for shutting down the splitter before end of stream + readerWg sync.WaitGroup // used to keep track of async reader. +} + +// queueItem is an item in the queue. +type queueItem struct { + input []byte // raw input sent to the worker + dst chan [][]string // result of block decode + err error // any error encountered will be set here } // Read - reads single record. -func (r *Reader) Read() (sql.Record, error) { - csvRecord, err := r.csvReader.Read() - if err != nil { - if err != io.EOF { - return nil, errCSVParsingError(err) +// Once Read is called the previous record should no longer be referenced. +func (r *Reader) Read(dst sql.Record) (sql.Record, error) { + // If we have have any records left, return these before any error. + for len(r.current) <= r.recordsRead { + if r.err != nil { + return nil, r.err } - - return nil, err + // Move to next block + item, ok := <-r.queue + if !ok { + r.err = io.EOF + return nil, r.err + } + //lint:ignore SA6002 Using pointer would allocate more since we would have to copy slice header before taking a pointer. + r.csvDstPool.Put(r.current) + r.current = <-item.dst + r.err = item.err + r.recordsRead = 0 } + csvRecord := r.current[r.recordsRead] + r.recordsRead++ + // If no column names are set, use _(index) if r.columnNames == nil { r.columnNames = make([]string, len(csvRecord)) for i := range csvRecord { @@ -107,67 +84,225 @@ func (r *Reader) Read() (sql.Record, error) { } } + // If no index max, add that. if r.nameIndexMap == nil { r.nameIndexMap = make(map[string]int64) for i := range r.columnNames { r.nameIndexMap[r.columnNames[i]] = int64(i) } } + dstRec, ok := dst.(*Record) + if !ok { + dstRec = &Record{} + } + dstRec.columnNames = r.columnNames + dstRec.csvRecord = csvRecord + dstRec.nameIndexMap = r.nameIndexMap - return &Record{ - columnNames: r.columnNames, - csvRecord: csvRecord, - nameIndexMap: r.nameIndexMap, - }, nil + return dstRec, nil } -// Close - closes underlaying reader. +// Close - closes underlying reader. func (r *Reader) Close() error { + if r.close != nil { + close(r.close) + r.close = nil + r.readerWg.Wait() + } + r.recordsRead = len(r.current) + if r.err == nil { + r.err = io.EOF + } return r.readCloser.Close() } +// nextSplit will attempt to skip a number of bytes and +// return the buffer until the next newline occurs. +// The last block will be sent along with an io.EOF. +func (r *Reader) nextSplit(skip int, dst []byte) ([]byte, error) { + if cap(dst) < skip { + dst = make([]byte, 0, skip+1024) + } + dst = dst[:skip] + if skip > 0 { + n, err := io.ReadFull(r.buf, dst) + if err != nil && err != io.ErrUnexpectedEOF { + // If an EOF happens after reading some but not all the bytes, + // ReadFull returns ErrUnexpectedEOF. + return dst[:n], err + } + dst = dst[:n] + if err == io.ErrUnexpectedEOF { + return dst, io.EOF + } + } + // Read until next line. + in, err := r.buf.ReadBytes('\n') + dst = append(dst, in...) + return dst, err +} + +// csvSplitSize is the size of each block. +// Blocks will read this much and find the first following newline. +// 128KB appears to be a very reasonable default. +const csvSplitSize = 128 << 10 + +// startReaders will read the header if needed and spin up a parser +// and a number of workers based on GOMAXPROCS. +// If an error is returned no goroutines have been started and r.err will have been set. +func (r *Reader) startReaders(in io.Reader, newReader func(io.Reader) *csv.Reader) error { + if r.args.FileHeaderInfo != none { + // Read column names + // Get one line. + b, err := r.nextSplit(0, nil) + if err != nil { + r.err = err + return err + } + reader := newReader(bytes.NewReader(b)) + record, err := reader.Read() + if err != nil { + r.err = err + if err != io.EOF { + r.err = errCSVParsingError(err) + return errCSVParsingError(err) + } + return err + } + + if r.args.FileHeaderInfo == use { + // Copy column names since records will be reused. + columns := append(make([]string, 0, len(record)), record...) + r.columnNames = columns + } + } + + r.bufferPool.New = func() interface{} { + return make([]byte, csvSplitSize+1024) + } + + // Create queue + r.queue = make(chan *queueItem, runtime.GOMAXPROCS(0)) + r.input = make(chan *queueItem, runtime.GOMAXPROCS(0)) + r.readerWg.Add(1) + + // Start splitter + go func() { + defer close(r.input) + defer close(r.queue) + defer r.readerWg.Done() + for { + next, err := r.nextSplit(csvSplitSize, r.bufferPool.Get().([]byte)) + q := queueItem{ + input: next, + dst: make(chan [][]string, 1), + err: err, + } + select { + case <-r.close: + return + case r.queue <- &q: + } + + select { + case <-r.close: + return + case r.input <- &q: + } + if err != nil { + // Exit on any error. + return + } + } + }() + + // Start parsers + for i := 0; i < runtime.GOMAXPROCS(0); i++ { + go func() { + for in := range r.input { + if len(in.input) == 0 { + in.dst <- nil + continue + } + dst, ok := r.csvDstPool.Get().([][]string) + if !ok { + dst = make([][]string, 0, 1000) + } + + cr := newReader(bytes.NewBuffer(in.input)) + all := dst[:0] + err := func() error { + // Read all records until EOF or another error. + for { + record, err := cr.Read() + if err == io.EOF { + return nil + } + if err != nil { + return errCSVParsingError(err) + } + var recDst []string + if len(dst) > len(all) { + recDst = dst[len(all)] + } + if cap(recDst) < len(record) { + recDst = make([]string, len(record)) + } + recDst = recDst[:len(record)] + copy(recDst, record) + all = append(all, recDst) + } + }() + if err != nil { + in.err = err + } + // We don't need the input any more. + //lint:ignore SA6002 Using pointer would allocate more since we would have to copy slice header before taking a pointer. + r.bufferPool.Put(in.input) + in.input = nil + in.dst <- all + } + }() + } + return nil + +} + // NewReader - creates new CSV reader using readCloser. func NewReader(readCloser io.ReadCloser, args *ReaderArgs) (*Reader, error) { if args == nil || args.IsEmpty() { panic(fmt.Errorf("empty args passed %v", args)) } - - csvReader := csv.NewReader(&recordReader{ - reader: readCloser, - recordDelimiter: []byte(args.RecordDelimiter), - oneByte: []byte{0}, - }) - csvReader.Comma = []rune(args.FieldDelimiter)[0] - csvReader.Comment = []rune(args.CommentCharacter)[0] - csvReader.FieldsPerRecord = -1 - // If LazyQuotes is true, a quote may appear in an unquoted field and a - // non-doubled quote may appear in a quoted field. - csvReader.LazyQuotes = true - // We do not trim leading space to keep consistent with s3. - csvReader.TrimLeadingSpace = false + csvIn := io.Reader(readCloser) + if args.RecordDelimiter != "\n" { + csvIn = &recordTransform{ + reader: readCloser, + recordDelimiter: []byte(args.RecordDelimiter), + oneByte: make([]byte, len(args.RecordDelimiter)-1), + } + } r := &Reader{ args: args, + buf: bufio.NewReaderSize(csvIn, csvSplitSize*2), readCloser: readCloser, - csvReader: csvReader, + close: make(chan struct{}), } - if args.FileHeaderInfo == none { - return r, nil + // Assume args are validated by ReaderArgs.UnmarshalXML() + newCsvReader := func(r io.Reader) *csv.Reader { + ret := csv.NewReader(r) + ret.Comma = []rune(args.FieldDelimiter)[0] + ret.Comment = []rune(args.CommentCharacter)[0] + ret.FieldsPerRecord = -1 + // If LazyQuotes is true, a quote may appear in an unquoted field and a + // non-doubled quote may appear in a quoted field. + ret.LazyQuotes = true + // We do not trim leading space to keep consistent with s3. + ret.TrimLeadingSpace = false + ret.ReuseRecord = true + return ret } - record, err := csvReader.Read() - if err != nil { - if err != io.EOF { - return nil, errCSVParsingError(err) - } - - return nil, err - } - - if args.FileHeaderInfo == use { - r.columnNames = record - } - - return r, nil + return r, r.startReaders(csvIn, newCsvReader) } diff --git a/pkg/s3select/csv/reader_test.go b/pkg/s3select/csv/reader_test.go index 81853e42f..81a39a5f9 100644 --- a/pkg/s3select/csv/reader_test.go +++ b/pkg/s3select/csv/reader_test.go @@ -18,11 +18,16 @@ package csv import ( "bytes" + "errors" + "fmt" "io" "io/ioutil" + "path/filepath" + "reflect" "strings" "testing" + "github.com/klauspost/compress/zip" "github.com/minio/minio/pkg/s3select/sql" ) @@ -49,12 +54,12 @@ func TestRead(t *testing.T) { QuoteCharacter: defaultQuoteCharacter, QuoteEscapeCharacter: defaultQuoteEscapeCharacter, CommentCharacter: defaultCommentCharacter, - AllowQuotedRecordDelimiter: true, + AllowQuotedRecordDelimiter: false, unmarshaled: true, }) for { - record, err = r.Read() + record, err = r.Read(record) if err != nil { break } @@ -72,3 +77,555 @@ func TestRead(t *testing.T) { } } } + +type tester interface { + Fatal(...interface{}) +} + +func openTestFile(t tester, file string) []byte { + f, err := ioutil.ReadFile(filepath.Join("testdata/testdata.zip")) + if err != nil { + t.Fatal(err) + } + z, err := zip.NewReader(bytes.NewReader(f), int64(len(f))) + if err != nil { + t.Fatal(err) + } + for _, f := range z.File { + if f.Name == file { + rc, err := f.Open() + if err != nil { + t.Fatal(err) + } + defer rc.Close() + b, err := ioutil.ReadAll(rc) + if err != nil { + t.Fatal(err) + } + return b + } + } + t.Fatal(file, "not found in testdata/testdata.zip") + return nil +} + +func TestReadExtended(t *testing.T) { + cases := []struct { + file string + recordDelimiter string + fieldDelimiter string + header bool + wantColumns []string + wantTenFields string + totalFields int + }{ + { + file: "nyc-taxi-data-100k.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + header: true, + wantColumns: []string{"trip_id", "vendor_id", "pickup_datetime", "dropoff_datetime", "store_and_fwd_flag", "rate_code_id", "pickup_longitude", "pickup_latitude", "dropoff_longitude", "dropoff_latitude", "passenger_count", "trip_distance", "fare_amount", "extra", "mta_tax", "tip_amount", "tolls_amount", "ehail_fee", "improvement_surcharge", "total_amount", "payment_type", "trip_type", "pickup", "dropoff", "cab_type", "precipitation", "snow_depth", "snowfall", "max_temp", "min_temp", "wind", "pickup_nyct2010_gid", "pickup_ctlabel", "pickup_borocode", "pickup_boroname", "pickup_ct2010", "pickup_boroct2010", "pickup_cdeligibil", "pickup_ntacode", "pickup_ntaname", "pickup_puma", "dropoff_nyct2010_gid", "dropoff_ctlabel", "dropoff_borocode", "dropoff_boroname", "dropoff_ct2010", "dropoff_boroct2010", "dropoff_cdeligibil", "dropoff_ntacode", "dropoff_ntaname", "dropoff_puma"}, + wantTenFields: `3389224,2,2014-03-26 00:26:15,2014-03-26 00:28:38,N,1,-73.950431823730469,40.792251586914063,-73.938949584960937,40.794425964355469,1,0.84,4.5,0.5,0.5,1,0,,,6.5,1,1,75,74,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1828,180,1,Manhattan,018000,1018000,E,MN34,East Harlem North,3804 +3389225,2,2014-03-31 09:42:15,2014-03-31 10:01:17,N,1,-73.950340270996094,40.792228698730469,-73.941970825195313,40.842235565185547,1,4.47,17.5,0,0.5,0,0,,,18,2,1,75,244,green,0.16,0.0,0.0,56,36,8.28,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,911,251,1,Manhattan,025100,1025100,E,MN36,Washington Heights South,3801 +3389226,2,2014-03-26 17:13:28,2014-03-26 17:19:07,N,1,-73.949493408203125,40.793506622314453,-73.943374633789063,40.786155700683594,1,0.82,5.5,1,0.5,0,0,,,7,1,1,75,75,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1387,164,1,Manhattan,016400,1016400,E,MN33,East Harlem South,3804 +3389227,2,2014-03-14 21:07:19,2014-03-14 21:11:41,N,1,-73.950538635253906,40.792228698730469,-73.940811157226563,40.809253692626953,1,1.40,6,0.5,0.5,0,0,,,7,2,1,75,42,green,0.00,0.0,0.0,46,22,5.59,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1184,208,1,Manhattan,020800,1020800,E,MN03,Central Harlem North-Polo Grounds,3803 +3389228,1,2014-03-28 13:52:56,2014-03-28 14:29:01,N,1,-73.950569152832031,40.792312622070313,-73.868507385253906,40.688491821289063,2,16.10,46,0,0.5,0,5.33,,,51.83,2,,75,63,green,0.04,0.0,0.0,62,37,5.37,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1544,1182.02,3,Brooklyn,118202,3118202,E,BK83,Cypress Hills-City Line,4008 +3389229,2,2014-03-07 09:46:32,2014-03-07 09:55:01,N,1,-73.952301025390625,40.789798736572266,-73.935806274414062,40.794448852539063,1,1.67,8,0,0.5,2,0,,,10.5,1,1,75,74,green,0.00,3.9,0.0,37,26,7.83,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1553,178,1,Manhattan,017800,1017800,E,MN34,East Harlem North,3804 +3389230,2,2014-03-17 18:23:05,2014-03-17 18:28:38,N,1,-73.952346801757813,40.789844512939453,-73.946319580078125,40.783851623535156,5,0.95,5.5,1,0.5,0.65,0,,,7.65,1,1,75,263,green,0.00,0.0,0.0,35,23,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,32,156.01,1,Manhattan,015601,1015601,I,MN32,Yorkville,3805 +3389231,1,2014-03-19 19:09:36,2014-03-19 19:12:20,N,1,-73.952377319335938,40.789779663085938,-73.947494506835938,40.796474456787109,1,0.50,4,1,0.5,1,0,,,6.5,1,,75,75,green,0.92,0.0,0.0,46,32,7.16,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1401,174.02,1,Manhattan,017402,1017402,E,MN33,East Harlem South,3804 +3389232,2,2014-03-20 19:06:28,2014-03-20 19:21:35,N,1,-73.952583312988281,40.789516448974609,-73.985870361328125,40.776973724365234,2,3.04,13,1,0.5,2.8,0,,,17.3,1,1,75,143,green,0.00,0.0,0.0,54,40,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1742,155,1,Manhattan,015500,1015500,I,MN14,Lincoln Square,3806 +3389233,2,2014-03-29 09:38:12,2014-03-29 09:44:16,N,1,-73.952728271484375,40.789501190185547,-73.950935363769531,40.775600433349609,1,1.10,6.5,0,0.5,1.3,0,,,8.3,1,1,75,263,green,1.81,0.0,0.0,59,43,10.74,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,2048,138,1,Manhattan,013800,1013800,I,MN32,Yorkville,3805 +`, + totalFields: 308*2 + 1, + }, { + file: "nyc-taxi-data-tabs-100k.csv", + recordDelimiter: "\n", + fieldDelimiter: "\t", + header: true, + wantColumns: []string{"trip_id", "vendor_id", "pickup_datetime", "dropoff_datetime", "store_and_fwd_flag", "rate_code_id", "pickup_longitude", "pickup_latitude", "dropoff_longitude", "dropoff_latitude", "passenger_count", "trip_distance", "fare_amount", "extra", "mta_tax", "tip_amount", "tolls_amount", "ehail_fee", "improvement_surcharge", "total_amount", "payment_type", "trip_type", "pickup", "dropoff", "cab_type", "precipitation", "snow_depth", "snowfall", "max_temp", "min_temp", "wind", "pickup_nyct2010_gid", "pickup_ctlabel", "pickup_borocode", "pickup_boroname", "pickup_ct2010", "pickup_boroct2010", "pickup_cdeligibil", "pickup_ntacode", "pickup_ntaname", "pickup_puma", "dropoff_nyct2010_gid", "dropoff_ctlabel", "dropoff_borocode", "dropoff_boroname", "dropoff_ct2010", "dropoff_boroct2010", "dropoff_cdeligibil", "dropoff_ntacode", "dropoff_ntaname", "dropoff_puma"}, + wantTenFields: `3389224,2,2014-03-26 00:26:15,2014-03-26 00:28:38,N,1,-73.950431823730469,40.792251586914063,-73.938949584960937,40.794425964355469,1,0.84,4.5,0.5,0.5,1,0,,,6.5,1,1,75,74,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1828,180,1,Manhattan,018000,1018000,E,MN34,East Harlem North,3804 +3389225,2,2014-03-31 09:42:15,2014-03-31 10:01:17,N,1,-73.950340270996094,40.792228698730469,-73.941970825195313,40.842235565185547,1,4.47,17.5,0,0.5,0,0,,,18,2,1,75,244,green,0.16,0.0,0.0,56,36,8.28,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,911,251,1,Manhattan,025100,1025100,E,MN36,Washington Heights South,3801 +3389226,2,2014-03-26 17:13:28,2014-03-26 17:19:07,N,1,-73.949493408203125,40.793506622314453,-73.943374633789063,40.786155700683594,1,0.82,5.5,1,0.5,0,0,,,7,1,1,75,75,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1387,164,1,Manhattan,016400,1016400,E,MN33,East Harlem South,3804 +3389227,2,2014-03-14 21:07:19,2014-03-14 21:11:41,N,1,-73.950538635253906,40.792228698730469,-73.940811157226563,40.809253692626953,1,1.40,6,0.5,0.5,0,0,,,7,2,1,75,42,green,0.00,0.0,0.0,46,22,5.59,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1184,208,1,Manhattan,020800,1020800,E,MN03,Central Harlem North-Polo Grounds,3803 +3389228,1,2014-03-28 13:52:56,2014-03-28 14:29:01,N,1,-73.950569152832031,40.792312622070313,-73.868507385253906,40.688491821289063,2,16.10,46,0,0.5,0,5.33,,,51.83,2,,75,63,green,0.04,0.0,0.0,62,37,5.37,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1544,1182.02,3,Brooklyn,118202,3118202,E,BK83,Cypress Hills-City Line,4008 +3389229,2,2014-03-07 09:46:32,2014-03-07 09:55:01,N,1,-73.952301025390625,40.789798736572266,-73.935806274414062,40.794448852539063,1,1.67,8,0,0.5,2,0,,,10.5,1,1,75,74,green,0.00,3.9,0.0,37,26,7.83,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1553,178,1,Manhattan,017800,1017800,E,MN34,East Harlem North,3804 +3389230,2,2014-03-17 18:23:05,2014-03-17 18:28:38,N,1,-73.952346801757813,40.789844512939453,-73.946319580078125,40.783851623535156,5,0.95,5.5,1,0.5,0.65,0,,,7.65,1,1,75,263,green,0.00,0.0,0.0,35,23,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,32,156.01,1,Manhattan,015601,1015601,I,MN32,Yorkville,3805 +3389231,1,2014-03-19 19:09:36,2014-03-19 19:12:20,N,1,-73.952377319335938,40.789779663085938,-73.947494506835938,40.796474456787109,1,0.50,4,1,0.5,1,0,,,6.5,1,,75,75,green,0.92,0.0,0.0,46,32,7.16,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1401,174.02,1,Manhattan,017402,1017402,E,MN33,East Harlem South,3804 +3389232,2,2014-03-20 19:06:28,2014-03-20 19:21:35,N,1,-73.952583312988281,40.789516448974609,-73.985870361328125,40.776973724365234,2,3.04,13,1,0.5,2.8,0,,,17.3,1,1,75,143,green,0.00,0.0,0.0,54,40,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1742,155,1,Manhattan,015500,1015500,I,MN14,Lincoln Square,3806 +3389233,2,2014-03-29 09:38:12,2014-03-29 09:44:16,N,1,-73.952728271484375,40.789501190185547,-73.950935363769531,40.775600433349609,1,1.10,6.5,0,0.5,1.3,0,,,8.3,1,1,75,263,green,1.81,0.0,0.0,59,43,10.74,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,2048,138,1,Manhattan,013800,1013800,I,MN32,Yorkville,3805 +`, + totalFields: 308*2 + 1, + }, { + file: "nyc-taxi-data-100k-single-delim.csv", + recordDelimiter: "^", + fieldDelimiter: ",", + header: true, + wantColumns: []string{"trip_id", "vendor_id", "pickup_datetime", "dropoff_datetime", "store_and_fwd_flag", "rate_code_id", "pickup_longitude", "pickup_latitude", "dropoff_longitude", "dropoff_latitude", "passenger_count", "trip_distance", "fare_amount", "extra", "mta_tax", "tip_amount", "tolls_amount", "ehail_fee", "improvement_surcharge", "total_amount", "payment_type", "trip_type", "pickup", "dropoff", "cab_type", "precipitation", "snow_depth", "snowfall", "max_temp", "min_temp", "wind", "pickup_nyct2010_gid", "pickup_ctlabel", "pickup_borocode", "pickup_boroname", "pickup_ct2010", "pickup_boroct2010", "pickup_cdeligibil", "pickup_ntacode", "pickup_ntaname", "pickup_puma", "dropoff_nyct2010_gid", "dropoff_ctlabel", "dropoff_borocode", "dropoff_boroname", "dropoff_ct2010", "dropoff_boroct2010", "dropoff_cdeligibil", "dropoff_ntacode", "dropoff_ntaname", "dropoff_puma"}, + wantTenFields: `3389224,2,2014-03-26 00:26:15,2014-03-26 00:28:38,N,1,-73.950431823730469,40.792251586914063,-73.938949584960937,40.794425964355469,1,0.84,4.5,0.5,0.5,1,0,,,6.5,1,1,75,74,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1828,180,1,Manhattan,018000,1018000,E,MN34,East Harlem North,3804 +3389225,2,2014-03-31 09:42:15,2014-03-31 10:01:17,N,1,-73.950340270996094,40.792228698730469,-73.941970825195313,40.842235565185547,1,4.47,17.5,0,0.5,0,0,,,18,2,1,75,244,green,0.16,0.0,0.0,56,36,8.28,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,911,251,1,Manhattan,025100,1025100,E,MN36,Washington Heights South,3801 +3389226,2,2014-03-26 17:13:28,2014-03-26 17:19:07,N,1,-73.949493408203125,40.793506622314453,-73.943374633789063,40.786155700683594,1,0.82,5.5,1,0.5,0,0,,,7,1,1,75,75,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1387,164,1,Manhattan,016400,1016400,E,MN33,East Harlem South,3804 +3389227,2,2014-03-14 21:07:19,2014-03-14 21:11:41,N,1,-73.950538635253906,40.792228698730469,-73.940811157226563,40.809253692626953,1,1.40,6,0.5,0.5,0,0,,,7,2,1,75,42,green,0.00,0.0,0.0,46,22,5.59,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1184,208,1,Manhattan,020800,1020800,E,MN03,Central Harlem North-Polo Grounds,3803 +3389228,1,2014-03-28 13:52:56,2014-03-28 14:29:01,N,1,-73.950569152832031,40.792312622070313,-73.868507385253906,40.688491821289063,2,16.10,46,0,0.5,0,5.33,,,51.83,2,,75,63,green,0.04,0.0,0.0,62,37,5.37,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1544,1182.02,3,Brooklyn,118202,3118202,E,BK83,Cypress Hills-City Line,4008 +3389229,2,2014-03-07 09:46:32,2014-03-07 09:55:01,N,1,-73.952301025390625,40.789798736572266,-73.935806274414062,40.794448852539063,1,1.67,8,0,0.5,2,0,,,10.5,1,1,75,74,green,0.00,3.9,0.0,37,26,7.83,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1553,178,1,Manhattan,017800,1017800,E,MN34,East Harlem North,3804 +3389230,2,2014-03-17 18:23:05,2014-03-17 18:28:38,N,1,-73.952346801757813,40.789844512939453,-73.946319580078125,40.783851623535156,5,0.95,5.5,1,0.5,0.65,0,,,7.65,1,1,75,263,green,0.00,0.0,0.0,35,23,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,32,156.01,1,Manhattan,015601,1015601,I,MN32,Yorkville,3805 +3389231,1,2014-03-19 19:09:36,2014-03-19 19:12:20,N,1,-73.952377319335938,40.789779663085938,-73.947494506835938,40.796474456787109,1,0.50,4,1,0.5,1,0,,,6.5,1,,75,75,green,0.92,0.0,0.0,46,32,7.16,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1401,174.02,1,Manhattan,017402,1017402,E,MN33,East Harlem South,3804 +3389232,2,2014-03-20 19:06:28,2014-03-20 19:21:35,N,1,-73.952583312988281,40.789516448974609,-73.985870361328125,40.776973724365234,2,3.04,13,1,0.5,2.8,0,,,17.3,1,1,75,143,green,0.00,0.0,0.0,54,40,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1742,155,1,Manhattan,015500,1015500,I,MN14,Lincoln Square,3806 +3389233,2,2014-03-29 09:38:12,2014-03-29 09:44:16,N,1,-73.952728271484375,40.789501190185547,-73.950935363769531,40.775600433349609,1,1.10,6.5,0,0.5,1.3,0,,,8.3,1,1,75,263,green,1.81,0.0,0.0,59,43,10.74,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,2048,138,1,Manhattan,013800,1013800,I,MN32,Yorkville,3805 +`, + totalFields: 308*2 + 1, + }, { + file: "nyc-taxi-data-100k-multi-delim.csv", + recordDelimiter: "^Y", + fieldDelimiter: ",", + header: true, + wantColumns: []string{"trip_id", "vendor_id", "pickup_datetime", "dropoff_datetime", "store_and_fwd_flag", "rate_code_id", "pickup_longitude", "pickup_latitude", "dropoff_longitude", "dropoff_latitude", "passenger_count", "trip_distance", "fare_amount", "extra", "mta_tax", "tip_amount", "tolls_amount", "ehail_fee", "improvement_surcharge", "total_amount", "payment_type", "trip_type", "pickup", "dropoff", "cab_type", "precipitation", "snow_depth", "snowfall", "max_temp", "min_temp", "wind", "pickup_nyct2010_gid", "pickup_ctlabel", "pickup_borocode", "pickup_boroname", "pickup_ct2010", "pickup_boroct2010", "pickup_cdeligibil", "pickup_ntacode", "pickup_ntaname", "pickup_puma", "dropoff_nyct2010_gid", "dropoff_ctlabel", "dropoff_borocode", "dropoff_boroname", "dropoff_ct2010", "dropoff_boroct2010", "dropoff_cdeligibil", "dropoff_ntacode", "dropoff_ntaname", "dropoff_puma"}, + wantTenFields: `3389224,2,2014-03-26 00:26:15,2014-03-26 00:28:38,N,1,-73.950431823730469,40.792251586914063,-73.938949584960937,40.794425964355469,1,0.84,4.5,0.5,0.5,1,0,,,6.5,1,1,75,74,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1828,180,1,Manhattan,018000,1018000,E,MN34,East Harlem North,3804 +3389225,2,2014-03-31 09:42:15,2014-03-31 10:01:17,N,1,-73.950340270996094,40.792228698730469,-73.941970825195313,40.842235565185547,1,4.47,17.5,0,0.5,0,0,,,18,2,1,75,244,green,0.16,0.0,0.0,56,36,8.28,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,911,251,1,Manhattan,025100,1025100,E,MN36,Washington Heights South,3801 +3389226,2,2014-03-26 17:13:28,2014-03-26 17:19:07,N,1,-73.949493408203125,40.793506622314453,-73.943374633789063,40.786155700683594,1,0.82,5.5,1,0.5,0,0,,,7,1,1,75,75,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1387,164,1,Manhattan,016400,1016400,E,MN33,East Harlem South,3804 +3389227,2,2014-03-14 21:07:19,2014-03-14 21:11:41,N,1,-73.950538635253906,40.792228698730469,-73.940811157226563,40.809253692626953,1,1.40,6,0.5,0.5,0,0,,,7,2,1,75,42,green,0.00,0.0,0.0,46,22,5.59,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1184,208,1,Manhattan,020800,1020800,E,MN03,Central Harlem North-Polo Grounds,3803 +3389228,1,2014-03-28 13:52:56,2014-03-28 14:29:01,N,1,-73.950569152832031,40.792312622070313,-73.868507385253906,40.688491821289063,2,16.10,46,0,0.5,0,5.33,,,51.83,2,,75,63,green,0.04,0.0,0.0,62,37,5.37,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1544,1182.02,3,Brooklyn,118202,3118202,E,BK83,Cypress Hills-City Line,4008 +3389229,2,2014-03-07 09:46:32,2014-03-07 09:55:01,N,1,-73.952301025390625,40.789798736572266,-73.935806274414062,40.794448852539063,1,1.67,8,0,0.5,2,0,,,10.5,1,1,75,74,green,0.00,3.9,0.0,37,26,7.83,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1553,178,1,Manhattan,017800,1017800,E,MN34,East Harlem North,3804 +3389230,2,2014-03-17 18:23:05,2014-03-17 18:28:38,N,1,-73.952346801757813,40.789844512939453,-73.946319580078125,40.783851623535156,5,0.95,5.5,1,0.5,0.65,0,,,7.65,1,1,75,263,green,0.00,0.0,0.0,35,23,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,32,156.01,1,Manhattan,015601,1015601,I,MN32,Yorkville,3805 +3389231,1,2014-03-19 19:09:36,2014-03-19 19:12:20,N,1,-73.952377319335938,40.789779663085938,-73.947494506835938,40.796474456787109,1,0.50,4,1,0.5,1,0,,,6.5,1,,75,75,green,0.92,0.0,0.0,46,32,7.16,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1401,174.02,1,Manhattan,017402,1017402,E,MN33,East Harlem South,3804 +3389232,2,2014-03-20 19:06:28,2014-03-20 19:21:35,N,1,-73.952583312988281,40.789516448974609,-73.985870361328125,40.776973724365234,2,3.04,13,1,0.5,2.8,0,,,17.3,1,1,75,143,green,0.00,0.0,0.0,54,40,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1742,155,1,Manhattan,015500,1015500,I,MN14,Lincoln Square,3806 +3389233,2,2014-03-29 09:38:12,2014-03-29 09:44:16,N,1,-73.952728271484375,40.789501190185547,-73.950935363769531,40.775600433349609,1,1.10,6.5,0,0.5,1.3,0,,,8.3,1,1,75,263,green,1.81,0.0,0.0,59,43,10.74,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,2048,138,1,Manhattan,013800,1013800,I,MN32,Yorkville,3805 +`, + totalFields: 308*2 + 1, + }, { + file: "nyc-taxi-data-noheader-100k.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + header: false, + wantColumns: []string{"_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8", "_9", "_10", "_11", "_12", "_13", "_14", "_15", "_16", "_17", "_18", "_19", "_20", "_21", "_22", "_23", "_24", "_25", "_26", "_27", "_28", "_29", "_30", "_31", "_32", "_33", "_34", "_35", "_36", "_37", "_38", "_39", "_40", "_41", "_42", "_43", "_44", "_45", "_46", "_47", "_48", "_49", "_50", "_51"}, + wantTenFields: `3389224,2,2014-03-26 00:26:15,2014-03-26 00:28:38,N,1,-73.950431823730469,40.792251586914063,-73.938949584960937,40.794425964355469,1,0.84,4.5,0.5,0.5,1,0,,,6.5,1,1,75,74,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1828,180,1,Manhattan,018000,1018000,E,MN34,East Harlem North,3804 +3389225,2,2014-03-31 09:42:15,2014-03-31 10:01:17,N,1,-73.950340270996094,40.792228698730469,-73.941970825195313,40.842235565185547,1,4.47,17.5,0,0.5,0,0,,,18,2,1,75,244,green,0.16,0.0,0.0,56,36,8.28,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,911,251,1,Manhattan,025100,1025100,E,MN36,Washington Heights South,3801 +3389226,2,2014-03-26 17:13:28,2014-03-26 17:19:07,N,1,-73.949493408203125,40.793506622314453,-73.943374633789063,40.786155700683594,1,0.82,5.5,1,0.5,0,0,,,7,1,1,75,75,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1387,164,1,Manhattan,016400,1016400,E,MN33,East Harlem South,3804 +3389227,2,2014-03-14 21:07:19,2014-03-14 21:11:41,N,1,-73.950538635253906,40.792228698730469,-73.940811157226563,40.809253692626953,1,1.40,6,0.5,0.5,0,0,,,7,2,1,75,42,green,0.00,0.0,0.0,46,22,5.59,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1184,208,1,Manhattan,020800,1020800,E,MN03,Central Harlem North-Polo Grounds,3803 +3389228,1,2014-03-28 13:52:56,2014-03-28 14:29:01,N,1,-73.950569152832031,40.792312622070313,-73.868507385253906,40.688491821289063,2,16.10,46,0,0.5,0,5.33,,,51.83,2,,75,63,green,0.04,0.0,0.0,62,37,5.37,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1544,1182.02,3,Brooklyn,118202,3118202,E,BK83,Cypress Hills-City Line,4008 +3389229,2,2014-03-07 09:46:32,2014-03-07 09:55:01,N,1,-73.952301025390625,40.789798736572266,-73.935806274414062,40.794448852539063,1,1.67,8,0,0.5,2,0,,,10.5,1,1,75,74,green,0.00,3.9,0.0,37,26,7.83,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1553,178,1,Manhattan,017800,1017800,E,MN34,East Harlem North,3804 +3389230,2,2014-03-17 18:23:05,2014-03-17 18:28:38,N,1,-73.952346801757813,40.789844512939453,-73.946319580078125,40.783851623535156,5,0.95,5.5,1,0.5,0.65,0,,,7.65,1,1,75,263,green,0.00,0.0,0.0,35,23,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,32,156.01,1,Manhattan,015601,1015601,I,MN32,Yorkville,3805 +3389231,1,2014-03-19 19:09:36,2014-03-19 19:12:20,N,1,-73.952377319335938,40.789779663085938,-73.947494506835938,40.796474456787109,1,0.50,4,1,0.5,1,0,,,6.5,1,,75,75,green,0.92,0.0,0.0,46,32,7.16,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1401,174.02,1,Manhattan,017402,1017402,E,MN33,East Harlem South,3804 +3389232,2,2014-03-20 19:06:28,2014-03-20 19:21:35,N,1,-73.952583312988281,40.789516448974609,-73.985870361328125,40.776973724365234,2,3.04,13,1,0.5,2.8,0,,,17.3,1,1,75,143,green,0.00,0.0,0.0,54,40,8.05,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1742,155,1,Manhattan,015500,1015500,I,MN14,Lincoln Square,3806 +3389233,2,2014-03-29 09:38:12,2014-03-29 09:44:16,N,1,-73.952728271484375,40.789501190185547,-73.950935363769531,40.775600433349609,1,1.10,6.5,0,0.5,1.3,0,,,8.3,1,1,75,263,green,1.81,0.0,0.0,59,43,10.74,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,2048,138,1,Manhattan,013800,1013800,I,MN32,Yorkville,3805 +`, + totalFields: 308 * 2, + }, + } + + for i, c := range cases { + t.Run(c.file, func(t *testing.T) { + + var err error + var record sql.Record + var result bytes.Buffer + input := openTestFile(t, c.file) + // Get above block size. + input = append(input, input...) + args := ReaderArgs{ + FileHeaderInfo: use, + RecordDelimiter: c.recordDelimiter, + FieldDelimiter: c.fieldDelimiter, + QuoteCharacter: defaultQuoteCharacter, + QuoteEscapeCharacter: defaultQuoteEscapeCharacter, + CommentCharacter: defaultCommentCharacter, + AllowQuotedRecordDelimiter: false, + unmarshaled: true, + } + if !c.header { + args.FileHeaderInfo = none + } + r, _ := NewReader(ioutil.NopCloser(bytes.NewReader(input)), &args) + fields := 0 + for { + record, err = r.Read(record) + if err != nil { + break + } + if fields < 10 { + // Write with fixed delimiters, newlines. + err := record.WriteCSV(&result, ',') + if err != nil { + t.Error(err) + } + } + fields++ + } + r.Close() + if err != io.EOF { + t.Fatalf("Case %d failed with %s", i, err) + } + if !reflect.DeepEqual(r.columnNames, c.wantColumns) { + t.Errorf("Case %d failed: expected %#v, got result %#v", i, c.wantColumns, r.columnNames) + } + if result.String() != c.wantTenFields { + t.Errorf("Case %d failed: expected %v, got result %v", i, c.wantTenFields, result.String()) + } + if fields != c.totalFields { + t.Errorf("Case %d failed: expected %v results %v", i, c.totalFields, fields) + } + }) + } +} + +type errReader struct { + err error +} + +func (e errReader) Read(p []byte) (n int, err error) { + return 0, e.err +} + +func TestReadFailures(t *testing.T) { + customErr := errors.New("unable to read file :(") + cases := []struct { + file string + recordDelimiter string + fieldDelimiter string + sendErr error + header bool + wantColumns []string + wantFields string + wantErr error + }{ + { + file: "truncated-records.csv", + recordDelimiter: "^Y", + fieldDelimiter: ",", + header: true, + wantColumns: []string{"trip_id", "vendor_id", "pickup_datetime", "dropoff_datetime", "store_and_fwd_flag", "rate_code_id", "pickup_longitude", "pickup_latitude", "dropoff_longitude", "dropoff_latitude", "passenger_count", "trip_distance", "fare_amount", "extra", "mta_tax", "tip_amount", "tolls_amount", "ehail_fee", "improvement_surcharge", "total_amount", "payment_type", "trip_type", "pickup", "dropoff", "cab_type", "precipitation", "snow_depth", "snowfall", "max_temp", "min_temp", "wind", "pickup_nyct2010_gid", "pickup_ctlabel", "pickup_borocode", "pickup_boroname", "pickup_ct2010", "pickup_boroct2010", "pickup_cdeligibil", "pickup_ntacode", "pickup_ntaname", "pickup_puma", "dropoff_nyct2010_gid", "dropoff_ctlabel", "dropoff_borocode", "dropoff_boroname", "dropoff_ct2010", "dropoff_boroct2010", "dropoff_cdeligibil", "dropoff_ntacode", "dropoff_ntaname", "dropoff_puma"}, + wantFields: `3389224,2,2014-03-26 00:26:15,2014-03-26 00:28:38,N,1,-73.950431823730469,40.792251586914063,-73.938949584960937,40.794425964355469,1,0.84,4.5,0.5,0.5,1,0,,,6.5,1,1,75,74,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1828,180,1,Manhattan,018000,1018000,E,MN34,East Harlem North,3804 +3389225,2,2014-03-31 09:42:15,2014-03-31 10:01:17,N,1,-73.950340270996094,40.792228698730469,-73.941970825195313,40.842235565185547,1,4.47,17.5,0,0.5,0,0,,,18,2,1,75,244,green,0.16,0.0,0.0,56,36,8.28,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,911,251,1,Manhattan,025100 +`, + wantErr: io.EOF, + }, + { + file: "truncated-records.csv", + recordDelimiter: "^Y", + fieldDelimiter: ",", + sendErr: customErr, + header: true, + wantColumns: []string{"trip_id", "vendor_id", "pickup_datetime", "dropoff_datetime", "store_and_fwd_flag", "rate_code_id", "pickup_longitude", "pickup_latitude", "dropoff_longitude", "dropoff_latitude", "passenger_count", "trip_distance", "fare_amount", "extra", "mta_tax", "tip_amount", "tolls_amount", "ehail_fee", "improvement_surcharge", "total_amount", "payment_type", "trip_type", "pickup", "dropoff", "cab_type", "precipitation", "snow_depth", "snowfall", "max_temp", "min_temp", "wind", "pickup_nyct2010_gid", "pickup_ctlabel", "pickup_borocode", "pickup_boroname", "pickup_ct2010", "pickup_boroct2010", "pickup_cdeligibil", "pickup_ntacode", "pickup_ntaname", "pickup_puma", "dropoff_nyct2010_gid", "dropoff_ctlabel", "dropoff_borocode", "dropoff_boroname", "dropoff_ct2010", "dropoff_boroct2010", "dropoff_cdeligibil", "dropoff_ntacode", "dropoff_ntaname", "dropoff_puma"}, + wantFields: `3389224,2,2014-03-26 00:26:15,2014-03-26 00:28:38,N,1,-73.950431823730469,40.792251586914063,-73.938949584960937,40.794425964355469,1,0.84,4.5,0.5,0.5,1,0,,,6.5,1,1,75,74,green,0.00,0.0,0.0,36,24,11.86,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,1828,180,1,Manhattan,018000,1018000,E,MN34,East Harlem North,3804 +3389225,2,2014-03-31 09:42:15,2014-03-31 10:01:17,N,1,-73.950340270996094,40.792228698730469,-73.941970825195313,40.842235565185547,1,4.47,17.5,0,0.5,0,0,,,18,2,1,75,244,green,0.16,0.0,0.0,56,36,8.28,1267,168,1,Manhattan,016800,1016800,E,MN33,East Harlem South,3804,911,251,1,Manhattan,025100 +`, + wantErr: customErr, + }, + { + // This works since LazyQuotes is true: + file: "invalid-badbarequote.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `"a ""word""",b` + "\n", + wantErr: io.EOF, + }, + { + // This works since LazyQuotes is true: + file: "invalid-baddoubleq.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `"a""""b",c` + "\n", + wantErr: io.EOF, + }, + { + // This works since LazyQuotes is true: + file: "invalid-badextraq.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `a word,"b"""` + "\n", + wantErr: io.EOF, + }, + { + // This works since LazyQuotes is true: + file: "invalid-badstartline.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `a,"b` + "\n" + `c""d,e` + "\n\"\n", + wantErr: io.EOF, + }, + { + // This works since LazyQuotes is true: + file: "invalid-badstartline2.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `a,b` + "\n" + `"d` + "\n\ne\"\n", + wantErr: io.EOF, + }, + { + // This works since LazyQuotes is true: + file: "invalid-badtrailingq.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `a word,"b"""` + "\n", + wantErr: io.EOF, + }, + { + // This works since LazyQuotes is true: + file: "invalid-crlfquoted.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `"foo""bar"` + "\n", + wantErr: io.EOF, + }, + { + // This works since LazyQuotes is true: + file: "invalid-csv.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `"a""""b",c` + "\n", + wantErr: io.EOF, + }, + { + // This works since LazyQuotes is true, but output is very weird. + file: "invalid-oddquote.csv", + recordDelimiter: "\n", + fieldDelimiter: ",", + sendErr: nil, + header: true, + wantColumns: []string{"header1", "header2", "header3"}, + wantFields: "ok1,ok2,ok3\n" + `""""""",b,c` + "\n\"\n", + wantErr: io.EOF, + }, + { + // Test when file ends with a half separator + file: "endswithhalfsep.csv", + recordDelimiter: "%!", + fieldDelimiter: ",", + sendErr: nil, + header: false, + wantColumns: []string{"_1", "_2", "_3"}, + wantFields: "a,b,c\na2,b2,c2%\n", + wantErr: io.EOF, + }, + } + + for i, c := range cases { + t.Run(c.file, func(t *testing.T) { + + var err error + var record sql.Record + var result bytes.Buffer + input := openTestFile(t, c.file) + args := ReaderArgs{ + FileHeaderInfo: use, + RecordDelimiter: c.recordDelimiter, + FieldDelimiter: c.fieldDelimiter, + QuoteCharacter: defaultQuoteCharacter, + QuoteEscapeCharacter: defaultQuoteEscapeCharacter, + CommentCharacter: defaultCommentCharacter, + AllowQuotedRecordDelimiter: false, + unmarshaled: true, + } + if !c.header { + args.FileHeaderInfo = none + } + inr := io.Reader(bytes.NewReader(input)) + if c.sendErr != nil { + inr = io.MultiReader(inr, errReader{c.sendErr}) + } + r, _ := NewReader(ioutil.NopCloser(inr), &args) + fields := 0 + for { + record, err = r.Read(record) + if err != nil { + break + } + // Write with fixed delimiters, newlines. + err := record.WriteCSV(&result, ',') + if err != nil { + t.Error(err) + } + fields++ + } + r.Close() + if err != c.wantErr { + t.Fatalf("Case %d failed with %s", i, err) + } + if !reflect.DeepEqual(r.columnNames, c.wantColumns) { + t.Errorf("Case %d failed: expected \n%#v, got result \n%#v", i, c.wantColumns, r.columnNames) + } + if result.String() != c.wantFields { + t.Errorf("Case %d failed: expected \n%v\nGot result \n%v", i, c.wantFields, result.String()) + } + }) + } +} + +func BenchmarkReaderBasic(b *testing.B) { + args := ReaderArgs{ + FileHeaderInfo: use, + RecordDelimiter: "\n", + FieldDelimiter: ",", + QuoteCharacter: defaultQuoteCharacter, + QuoteEscapeCharacter: defaultQuoteEscapeCharacter, + CommentCharacter: defaultCommentCharacter, + AllowQuotedRecordDelimiter: false, + unmarshaled: true, + } + f := openTestFile(b, "nyc-taxi-data-100k.csv") + r, err := NewReader(ioutil.NopCloser(bytes.NewBuffer(f)), &args) + if err != nil { + b.Fatalf("Reading init failed with %s", err) + } + defer r.Close() + b.ReportAllocs() + b.ResetTimer() + b.SetBytes(int64(len(f))) + var record sql.Record + for i := 0; i < b.N; i++ { + r, err = NewReader(ioutil.NopCloser(bytes.NewBuffer(f)), &args) + if err != nil { + b.Fatalf("Reading init failed with %s", err) + } + for err == nil { + record, err = r.Read(record) + if err != nil && err != io.EOF { + b.Fatalf("Reading failed with %s", err) + } + } + r.Close() + } +} + +func BenchmarkReaderHuge(b *testing.B) { + args := ReaderArgs{ + FileHeaderInfo: use, + RecordDelimiter: "\n", + FieldDelimiter: ",", + QuoteCharacter: defaultQuoteCharacter, + QuoteEscapeCharacter: defaultQuoteEscapeCharacter, + CommentCharacter: defaultCommentCharacter, + AllowQuotedRecordDelimiter: false, + unmarshaled: true, + } + for n := 0; n < 11; n++ { + f := openTestFile(b, "nyc-taxi-data-100k.csv") + want := 309 + for i := 0; i < n; i++ { + f = append(f, f...) + want *= 2 + } + b.Run(fmt.Sprint(len(f)/(1<<10), "K"), func(b *testing.B) { + b.ReportAllocs() + b.SetBytes(int64(len(f))) + b.ResetTimer() + var record sql.Record + for i := 0; i < b.N; i++ { + r, err := NewReader(ioutil.NopCloser(bytes.NewBuffer(f)), &args) + if err != nil { + b.Fatalf("Reading init failed with %s", err) + } + + got := 0 + for err == nil { + record, err = r.Read(record) + if err != nil && err != io.EOF { + b.Fatalf("Reading failed with %s", err) + } + got++ + } + r.Close() + if got != want { + b.Errorf("want %d records, got %d", want, got) + } + } + }) + } +} + +func BenchmarkReaderReplace(b *testing.B) { + args := ReaderArgs{ + FileHeaderInfo: use, + RecordDelimiter: "^", + FieldDelimiter: ",", + QuoteCharacter: defaultQuoteCharacter, + QuoteEscapeCharacter: defaultQuoteEscapeCharacter, + CommentCharacter: defaultCommentCharacter, + AllowQuotedRecordDelimiter: false, + unmarshaled: true, + } + f := openTestFile(b, "nyc-taxi-data-100k-single-delim.csv") + r, err := NewReader(ioutil.NopCloser(bytes.NewBuffer(f)), &args) + if err != nil { + b.Fatalf("Reading init failed with %s", err) + } + defer r.Close() + b.ReportAllocs() + b.ResetTimer() + b.SetBytes(int64(len(f))) + var record sql.Record + for i := 0; i < b.N; i++ { + r, err = NewReader(ioutil.NopCloser(bytes.NewBuffer(f)), &args) + if err != nil { + b.Fatalf("Reading init failed with %s", err) + } + + for err == nil { + record, err = r.Read(record) + if err != nil && err != io.EOF { + b.Fatalf("Reading failed with %s", err) + } + } + r.Close() + } +} + +func BenchmarkReaderReplaceTwo(b *testing.B) { + args := ReaderArgs{ + FileHeaderInfo: use, + RecordDelimiter: "^Y", + FieldDelimiter: ",", + QuoteCharacter: defaultQuoteCharacter, + QuoteEscapeCharacter: defaultQuoteEscapeCharacter, + CommentCharacter: defaultCommentCharacter, + AllowQuotedRecordDelimiter: false, + unmarshaled: true, + } + f := openTestFile(b, "nyc-taxi-data-100k-multi-delim.csv") + r, err := NewReader(ioutil.NopCloser(bytes.NewBuffer(f)), &args) + if err != nil { + b.Fatalf("Reading init failed with %s", err) + } + defer r.Close() + b.ReportAllocs() + b.ResetTimer() + b.SetBytes(int64(len(f))) + var record sql.Record + for i := 0; i < b.N; i++ { + r, err = NewReader(ioutil.NopCloser(bytes.NewBuffer(f)), &args) + if err != nil { + b.Fatalf("Reading init failed with %s", err) + } + + for err == nil { + record, err = r.Read(record) + if err != nil && err != io.EOF { + b.Fatalf("Reading failed with %s", err) + } + } + r.Close() + } +} diff --git a/pkg/s3select/csv/record.go b/pkg/s3select/csv/record.go index 3152749a6..1a53ea478 100644 --- a/pkg/s3select/csv/record.go +++ b/pkg/s3select/csv/record.go @@ -27,7 +27,7 @@ import ( "github.com/minio/minio/pkg/s3select/sql" ) -// Record - is CSV record. +// Record - is a CSV record. type Record struct { columnNames []string csvRecord []string diff --git a/pkg/s3select/csv/recordtransform.go b/pkg/s3select/csv/recordtransform.go new file mode 100644 index 000000000..18b5ea48f --- /dev/null +++ b/pkg/s3select/csv/recordtransform.go @@ -0,0 +1,93 @@ +/* + * MinIO Cloud Storage, (C) 2019 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package csv + +import ( + "bytes" + "io" +) + +// recordTransform will convert records to always have newline records. +type recordTransform struct { + reader io.Reader + // recordDelimiter can be up to 2 characters. + recordDelimiter []byte + oneByte []byte + useOneByte bool +} + +func (rr *recordTransform) Read(p []byte) (n int, err error) { + if rr.useOneByte { + p[0] = rr.oneByte[0] + rr.useOneByte = false + n, err = rr.reader.Read(p[1:]) + n++ + } else { + n, err = rr.reader.Read(p) + } + + if err != nil { + return n, err + } + + // Do nothing if record-delimiter is already newline. + if string(rr.recordDelimiter) == "\n" { + return n, nil + } + + // Change record delimiters to newline. + if len(rr.recordDelimiter) == 1 { + for idx := 0; idx < len(p); { + i := bytes.Index(p[idx:], rr.recordDelimiter) + if i < 0 { + break + } + idx += i + p[idx] = '\n' + } + return n, nil + } + + // 2 characters... + for idx := 0; idx < len(p); { + i := bytes.Index(p[idx:], rr.recordDelimiter) + if i < 0 { + break + } + idx += i + + p[idx] = '\n' + p = append(p[:idx+1], p[idx+2:]...) + n-- + } + + if p[n-1] != rr.recordDelimiter[0] { + return n, nil + } + + if _, err = rr.reader.Read(rr.oneByte); err != nil { + return n, err + } + + if rr.oneByte[0] == rr.recordDelimiter[1] { + p[n-1] = '\n' + return n, nil + } + + rr.useOneByte = true + return n, nil +} diff --git a/pkg/s3select/csv/testdata/testdata.zip b/pkg/s3select/csv/testdata/testdata.zip new file mode 100644 index 0000000000000000000000000000000000000000..e4519334f557b291456c77856e60b96661704073 GIT binary patch literal 114303 zcmV)MK)An9O9KQH00ICA0GxU!Pa{)GU1e1O0BMB*02TlM0B(6>Ep%acX)R=7bYU$q zFfeN_V{>*?kWFsIAPj`}JqwH1UI;nh)6H zZ&-bhoz->BNKJtQx)lz9+!K6h1(y}P!pkX@5vZipZ0in`{EFx(mnsj;$m-i(!yW2| zPDLS&>>$_mT`IyEt2CECO{P`6F6;e8f zlDxhB6f2okOpe;>zb$#C8bQLLF8~4rkRn;k3Ji>-Z&2Q_Um-5+3)w8oMG}4w1aCqP zWvD{Q5`yXliiap>BTFw*bXm7amX@WKV@a&rRN2Z^%-Tr;Jfwmi46y%8fB@j9p9sPr z6h6Jbxw!>AAOarNf5m|nhz{vM>;dEn1i!n!{p|Yj`0E0aCn8W^e+Iw%y$Jm5`r+~F zm)H02ZvK1qm%A^I<|c^>l$=0`lZM1qL*k}EcN%_wci%O1nl{iBy24tN7qU@CxF9VQ zBTZ4{z-pXyqsi%&E8umTQUxX(l?h4PmdY$R6ZO4)QY<73 z=PauQ2clY9qL_?AY%5VQbY~V)HR;<#LR}=X(B-r(oy-E!Q>=8gPX=q&H~@{aZQ2iD zo03g~&Kk69(C0LCk};4Zy<%FhftzGRi_&PDCh1~qQt0ouO-v%PV0K$cCKj7`v9p&+ z19ngZUnveGyJadrMN%F3guh+_B&7o5q=9kOz_@8Zfu9@$+5Ppqou9w?M>bnffBL%xuq<1qoMN z-SxAgGQ>~j={+=AqI0N{qVpD-G&j<#!FoV;s=VH$_`bL#hgy9WLH9Oy-2kv34F2jtl%wTb%r0pUn^4D5J9=?mv1V_L*Ul$IjImB~pRrID*0p$ud;8>BT2eQgFw z3dig5-*@kBum19Xzr4ObS*3TBGNP<@DM~Ih%TyQg5oK_4kfuu5h7ySidpgQ(*Fi`l@m&gCr1E7W>g7)=i@TcE%!O!0P_p>kWA09yoqvJE+ z-92cF*(sqUKjhU{CBgASQ+xhCmQE6(-mUYrg@I+-Go;7gS z!1)mlU%=B-l4}h?)ALeycc>yVhS8Z>u?DAPEX6HB(0<+^@&sZ)7!erk@+G0Sq19neBebt+QafO=tex9-tijX5pc9Xt2Lu)Xus zLGruPc%|2Oe~|!2oR}i6OcA>VqH~dMRwA9Jj0U=viE&|_WwGdvC$YOmX))DynW~ex z4lhh+PUH|hr d?xBh5b)snuvC*9KQL-l^zA^~DSb$N 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

g{O$k-Ul*r57G#em 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

0{uQ>_e?Y4oWYRyN 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 literal 0 HcmV?d00001 diff --git a/pkg/s3select/json/reader.go b/pkg/s3select/json/reader.go index b29365ccd..4be0fad0c 100644 --- a/pkg/s3select/json/reader.go +++ b/pkg/s3select/json/reader.go @@ -33,7 +33,7 @@ type Reader struct { } // Read - reads single record. -func (r *Reader) Read() (sql.Record, error) { +func (r *Reader) Read(dst sql.Record) (sql.Record, error) { v, ok := <-r.valueCh if !ok { if err := r.decoder.Err(); err != nil { @@ -55,15 +55,25 @@ func (r *Reader) Read() (sql.Record, error) { kvs = jstream.KVS{jstream.KV{Key: "_1", Value: v.Value}} } - return &Record{ - KVS: kvs, - SelectFormat: sql.SelectFmtJSON, - }, nil + dstRec, ok := dst.(*Record) + if !ok { + dstRec = &Record{} + } + dstRec.KVS = kvs + dstRec.SelectFormat = sql.SelectFmtJSON + return dstRec, nil } -// Close - closes underlaying reader. +// Close - closes underlying reader. func (r *Reader) Close() error { - return r.readCloser.Close() + // Close the input. + // Potentially racy if the stream decoder is still reading. + err := r.readCloser.Close() + for range r.valueCh { + // Drain values so we don't leak a goroutine. + // Since we have closed the input, it should fail rather quickly. + } + return err } // NewReader - creates new JSON reader using readCloser. diff --git a/pkg/s3select/json/reader_test.go b/pkg/s3select/json/reader_test.go index a0a9e277c..130fde9df 100644 --- a/pkg/s3select/json/reader_test.go +++ b/pkg/s3select/json/reader_test.go @@ -17,26 +17,30 @@ package json import ( + "bytes" "io" "io/ioutil" "os" "path/filepath" "testing" + + "github.com/minio/minio/pkg/s3select/sql" ) func TestNewReader(t *testing.T) { - files, err := ioutil.ReadDir("data") + files, err := ioutil.ReadDir("testdata") if err != nil { t.Fatal(err) } for _, file := range files { - f, err := os.Open(filepath.Join("data", file.Name())) + f, err := os.Open(filepath.Join("testdata", file.Name())) if err != nil { t.Fatal(err) } r := NewReader(f, &ReaderArgs{}) + var record sql.Record for { - _, err = r.Read() + record, err = r.Read(record) if err != nil { break } @@ -47,3 +51,35 @@ func TestNewReader(t *testing.T) { } } } + +func BenchmarkReader(b *testing.B) { + files, err := ioutil.ReadDir("testdata") + if err != nil { + b.Fatal(err) + } + for _, file := range files { + b.Run(file.Name(), func(b *testing.B) { + f, err := ioutil.ReadFile(filepath.Join("testdata", file.Name())) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(f))) + b.ReportAllocs() + b.ResetTimer() + var record sql.Record + for i := 0; i < b.N; i++ { + r := NewReader(ioutil.NopCloser(bytes.NewBuffer(f)), &ReaderArgs{}) + for { + record, err = r.Read(record) + if err != nil { + break + } + } + r.Close() + if err != io.EOF { + b.Fatalf("Reading failed with %s, %s", err, file.Name()) + } + } + }) + } +} diff --git a/pkg/s3select/json/data/10.json b/pkg/s3select/json/testdata/10.json similarity index 100% rename from pkg/s3select/json/data/10.json rename to pkg/s3select/json/testdata/10.json diff --git a/pkg/s3select/json/data/11.json b/pkg/s3select/json/testdata/11.json similarity index 100% rename from pkg/s3select/json/data/11.json rename to pkg/s3select/json/testdata/11.json diff --git a/pkg/s3select/json/data/12.json b/pkg/s3select/json/testdata/12.json similarity index 100% rename from pkg/s3select/json/data/12.json rename to pkg/s3select/json/testdata/12.json diff --git a/pkg/s3select/json/data/2.json b/pkg/s3select/json/testdata/2.json similarity index 100% rename from pkg/s3select/json/data/2.json rename to pkg/s3select/json/testdata/2.json diff --git a/pkg/s3select/json/data/3.json b/pkg/s3select/json/testdata/3.json similarity index 100% rename from pkg/s3select/json/data/3.json rename to pkg/s3select/json/testdata/3.json diff --git a/pkg/s3select/json/data/4.json b/pkg/s3select/json/testdata/4.json similarity index 100% rename from pkg/s3select/json/data/4.json rename to pkg/s3select/json/testdata/4.json diff --git a/pkg/s3select/json/data/5.json b/pkg/s3select/json/testdata/5.json similarity index 100% rename from pkg/s3select/json/data/5.json rename to pkg/s3select/json/testdata/5.json diff --git a/pkg/s3select/json/data/6.json b/pkg/s3select/json/testdata/6.json similarity index 100% rename from pkg/s3select/json/data/6.json rename to pkg/s3select/json/testdata/6.json diff --git a/pkg/s3select/json/data/7.json b/pkg/s3select/json/testdata/7.json similarity index 100% rename from pkg/s3select/json/data/7.json rename to pkg/s3select/json/testdata/7.json diff --git a/pkg/s3select/json/data/8.json b/pkg/s3select/json/testdata/8.json similarity index 100% rename from pkg/s3select/json/data/8.json rename to pkg/s3select/json/testdata/8.json diff --git a/pkg/s3select/json/data/9.json b/pkg/s3select/json/testdata/9.json similarity index 100% rename from pkg/s3select/json/data/9.json rename to pkg/s3select/json/testdata/9.json diff --git a/pkg/s3select/parquet/reader.go b/pkg/s3select/parquet/reader.go index 57d7a1deb..c63967bc3 100644 --- a/pkg/s3select/parquet/reader.go +++ b/pkg/s3select/parquet/reader.go @@ -33,7 +33,7 @@ type Reader struct { } // Read - reads single record. -func (r *Reader) Read() (rec sql.Record, rerr error) { +func (r *Reader) Read(dst sql.Record) (rec sql.Record, rerr error) { parquetRecord, err := r.reader.Read() if err != nil { if err != io.EOF { @@ -73,11 +73,20 @@ func (r *Reader) Read() (rec sql.Record, rerr error) { return true } + // Apply our range parquetRecord.Range(f) - return &jsonfmt.Record{KVS: kvs, SelectFormat: sql.SelectFmtParquet}, nil + + // Reuse destination if we can. + dstRec, ok := dst.(*jsonfmt.Record) + if !ok { + dstRec = &jsonfmt.Record{} + } + dstRec.SelectFormat = sql.SelectFmtParquet + dstRec.KVS = kvs + return dstRec, nil } -// Close - closes underlaying readers. +// Close - closes underlying readers. func (r *Reader) Close() error { return r.reader.Close() } diff --git a/pkg/s3select/select.go b/pkg/s3select/select.go index 923d3a920..a594d57f9 100644 --- a/pkg/s3select/select.go +++ b/pkg/s3select/select.go @@ -34,7 +34,9 @@ import ( ) type recordReader interface { - Read() (sql.Record, error) + // Read a record. + // dst is optional but will be used if valid. + Read(dst sql.Record) (sql.Record, error) Close() error } @@ -399,6 +401,7 @@ func (s3Select *S3Select) Evaluate(w http.ResponseWriter) { return true } + var rec sql.Record for { if s3Select.statement.LimitReached() { if err = writer.Finish(s3Select.getProgress()); err != nil { @@ -408,7 +411,7 @@ func (s3Select *S3Select) Evaluate(w http.ResponseWriter) { break } - if inputRecord, err = s3Select.recordReader.Read(); err != nil { + if rec, err = s3Select.recordReader.Read(rec); err != nil { if err != io.EOF { break } @@ -431,7 +434,7 @@ func (s3Select *S3Select) Evaluate(w http.ResponseWriter) { break } - if inputRecord, err = s3Select.statement.EvalFrom(s3Select.Input.format, inputRecord); err != nil { + if inputRecord, err = s3Select.statement.EvalFrom(s3Select.Input.format, rec); err != nil { break } diff --git a/pkg/s3select/select_test.go b/pkg/s3select/select_test.go index 3231356ee..569e47415 100644 --- a/pkg/s3select/select_test.go +++ b/pkg/s3select/select_test.go @@ -18,6 +18,7 @@ package s3select import ( "bytes" + "fmt" "io" "io/ioutil" "net/http" @@ -108,26 +109,29 @@ func TestCSVInput(t *testing.T) { 2.5,baz,true `) - for _, testCase := range testTable { - s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) - if err != nil { - t.Fatal(err) - } + for i, testCase := range testTable { + t.Run(fmt.Sprint(i), func(t *testing.T) { + s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) + if err != nil { + t.Fatal(err) + } - if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { - return ioutil.NopCloser(bytes.NewReader(csvData)), nil - }); err != nil { - t.Fatal(err) - } + if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { + return ioutil.NopCloser(bytes.NewReader(csvData)), nil + }); err != nil { + t.Fatal(err) + } - w := &testResponseWriter{} - s3Select.Evaluate(w) - s3Select.Close() + w := &testResponseWriter{} + s3Select.Evaluate(w) + s3Select.Close() - if !reflect.DeepEqual(w.response, testCase.expectedResult) { - t.Fatalf("received response does not match with expected reply") - } + if !reflect.DeepEqual(w.response, testCase.expectedResult) { + t.Errorf("received response does not match with expected reply\ngot: %#v\nwant:%#v", w.response, testCase.expectedResult) + } + }) } + } func TestJSONInput(t *testing.T) { @@ -191,26 +195,27 @@ func TestJSONInput(t *testing.T) { {"three":true,"two":"baz","one":2.5} `) - for _, testCase := range testTable { + for i, testCase := range testTable { + t.Run(fmt.Sprint(i), func(t *testing.T) { + s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) + if err != nil { + t.Fatal(err) + } - s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) - if err != nil { - t.Fatal(err) - } + if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { + return ioutil.NopCloser(bytes.NewReader(jsonData)), nil + }); err != nil { + t.Fatal(err) + } - if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { - return ioutil.NopCloser(bytes.NewReader(jsonData)), nil - }); err != nil { - t.Fatal(err) - } + w := &testResponseWriter{} + s3Select.Evaluate(w) + s3Select.Close() - w := &testResponseWriter{} - s3Select.Evaluate(w) - s3Select.Close() - - if !reflect.DeepEqual(w.response, testCase.expectedResult) { - t.Fatalf("received response does not match with expected reply") - } + if !reflect.DeepEqual(w.response, testCase.expectedResult) { + t.Errorf("received response does not match with expected reply\ngot: %s\nwant:%s", string(w.response), string(testCase.expectedResult)) + } + }) } } @@ -268,45 +273,47 @@ func TestParquetInput(t *testing.T) { }, } - for _, testCase := range testTable { - getReader := func(offset int64, length int64) (io.ReadCloser, error) { - testdataFile := "testdata.parquet" - file, err := os.Open(testdataFile) + for i, testCase := range testTable { + t.Run(fmt.Sprint(i), func(t *testing.T) { + getReader := func(offset int64, length int64) (io.ReadCloser, error) { + testdataFile := "testdata.parquet" + file, err := os.Open(testdataFile) + if err != nil { + return nil, err + } + + fi, err := file.Stat() + if err != nil { + return nil, err + } + + if offset < 0 { + offset = fi.Size() + offset + } + + if _, err = file.Seek(offset, os.SEEK_SET); err != nil { + return nil, err + } + + return file, nil + } + + s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) if err != nil { - return nil, err + t.Fatal(err) } - fi, err := file.Stat() - if err != nil { - return nil, err + if err = s3Select.Open(getReader); err != nil { + t.Fatal(err) } - if offset < 0 { - offset = fi.Size() + offset + w := &testResponseWriter{} + s3Select.Evaluate(w) + s3Select.Close() + + if !reflect.DeepEqual(w.response, testCase.expectedResult) { + t.Errorf("received response does not match with expected reply\ngot: %#v\nwant:%#v", w.response, testCase.expectedResult) } - - if _, err = file.Seek(offset, os.SEEK_SET); err != nil { - return nil, err - } - - return file, nil - } - - s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) - if err != nil { - t.Fatal(err) - } - - if err = s3Select.Open(getReader); err != nil { - t.Fatal(err) - } - - w := &testResponseWriter{} - s3Select.Evaluate(w) - s3Select.Close() - - if !reflect.DeepEqual(w.response, testCase.expectedResult) { - t.Fatalf("received response does not match with expected reply") - } + }) } }