nextRow returns the next SQL row from the reader provided, using any previously saved partial line. Returns true if there was another row.
(ctx *sql.Context, data *bufio.Reader)
| 71 | // nextRow returns the next SQL row from the reader provided, using any previously saved partial line. Returns true if |
| 72 | // there was another row. |
| 73 | func (tdl *TabularDataLoader) nextRow(ctx *sql.Context, data *bufio.Reader) (sql.Row, bool, error) { |
| 74 | if tdl.removeHeader { |
| 75 | _, err := data.ReadString('\n') |
| 76 | tdl.removeHeader = false |
| 77 | if err != nil { |
| 78 | return nil, false, err |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | for { |
| 83 | // Read the next line from the file |
| 84 | line, err := data.ReadString('\n') |
| 85 | if err != nil { |
| 86 | if err != io.EOF { |
| 87 | return nil, false, err |
| 88 | } |
| 89 | |
| 90 | // bufio.Reader.ReadString will return an error AND a line |
| 91 | // if the final contents of the data does NOT end in the |
| 92 | // delimiter. In this case, that means that we need to save |
| 93 | // the partial line and use it in the next chunk. |
| 94 | tdl.partialLine.WriteString(line) |
| 95 | return nil, false, nil |
| 96 | } |
| 97 | |
| 98 | // If we've not reached EOF, then there will be a newline appended to the end that we must remove. |
| 99 | line = strings.TrimSuffix(line, "\n") |
| 100 | // Data with windows line endings will also have a carriage return character that we need to remove. |
| 101 | line = strings.TrimSuffix(line, "\r") |
| 102 | |
| 103 | if tdl.partialLine.Len() > 0 { |
| 104 | tdl.partialLine.WriteString(line) |
| 105 | line = tdl.partialLine.String() |
| 106 | tdl.partialLine.Reset() |
| 107 | } |
| 108 | |
| 109 | // If we see the end of data marker, return early |
| 110 | if line == `\.` { |
| 111 | return nil, false, nil |
| 112 | } |
| 113 | |
| 114 | // Skip over empty lines |
| 115 | if len(line) == 0 { |
| 116 | continue |
| 117 | } |
| 118 | |
| 119 | // Split the values by the delimiter, ensuring the correct number of values have been read |
| 120 | values := strings.Split(line, tdl.delimiterChar) |
| 121 | if len(values) > len(tdl.colTypes) { |
| 122 | return nil, false, errors.Errorf("extra data after last expected column") |
| 123 | } else if len(values) < len(tdl.colTypes) { |
| 124 | return nil, false, errors.Errorf(`missing data for column "%s"`, tdl.sch[len(values)].Name) |
| 125 | } |
| 126 | |
| 127 | // Cast the values using I/O input |
| 128 | row := make(sql.Row, len(tdl.colTypes)) |
| 129 | for i := range tdl.colTypes { |
| 130 | if values[i] == tdl.nullChar { |
no test coverage detected