forked from joho/sqltocsv
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsqltocsv.go
186 lines (159 loc) · 4.74 KB
/
sqltocsv.go
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// sqltocsv is a package to make it dead easy to turn arbitrary database query
// results (in the form of database/sql Rows) into CSV output.
//
// Source and README at https://github.com/joho/sqltocsv
package sqltocsv
import (
"bytes"
"database/sql"
"encoding/csv"
"fmt"
"io"
"os"
"time"
)
// WriteFile will write a CSV file to the file name specified (with headers)
// based on whatever is in the sql.Rows you pass in. It calls WriteCsvToWriter under
// the hood.
func WriteFile(csvFileName string, rows *sql.Rows) error {
return New(rows, ',').WriteFile(csvFileName)
}
// WriteString will return a string of the CSV. Don't use this unless you've
// got a small data set or a lot of memory
func WriteString(rows *sql.Rows) (string, error) {
return New(rows, ',').WriteString()
}
// Write will write a CSV file to the writer passed in (with headers)
// based on whatever is in the sql.Rows you pass in.
func Write(writer io.Writer, rows *sql.Rows) error {
return New(rows, ',').Write(writer)
}
// CsvPreprocessorFunc is a function type for preprocessing your CSV.
// It takes the columns after they've been munged into strings but
// before they've been passed into the CSV writer.
//
// Return an outputRow of false if you want the row skipped otherwise
// return the processed Row slice as you want it written to the CSV.
type CsvPreProcessorFunc func(row []string, columnNames []string) (outputRow bool, processedRow []string)
// Converter does the actual work of converting the rows to CSV.
// There are a few settings you can override if you want to do
// some fancy stuff to your CSV.
type Converter struct {
Headers []string // Column headers to use (default is rows.Columns())
WriteHeaders bool // Flag to output headers in your CSV (default is true)
TimeFormat string // Format string for any time.Time values (default is time's default)
Delimeter rune
rows *sql.Rows
rowPreProcessor CsvPreProcessorFunc
}
// SetRowPreProcessor lets you specify a CsvPreprocessorFunc for this conversion
func (c *Converter) SetRowPreProcessor(processor CsvPreProcessorFunc) {
c.rowPreProcessor = processor
}
// String returns the CSV as a string in an fmt package friendly way
func (c Converter) String() string {
csv, err := c.WriteString()
if err != nil {
return ""
}
return csv
}
// WriteString returns the CSV as a string and an error if something goes wrong
func (c Converter) WriteString() (string, error) {
buffer := bytes.Buffer{}
err := c.Write(&buffer)
return buffer.String(), err
}
// WriteFile writes the CSV to the filename specified, return an error if problem
func (c Converter) WriteFile(csvFileName string) error {
f, err := os.Create(csvFileName)
if err != nil {
return err
}
err = c.Write(f)
if err != nil {
f.Close() // close, but only return/handle the write error
return err
}
return f.Close()
}
// Write writes the CSV to the Writer provided
func (c Converter) Write(writer io.Writer) error {
rows := c.rows
csvWriter := csv.NewWriter(writer)
csvWriter.Comma = c.Delimeter
columnNames, err := rows.Columns()
if err != nil {
return err
}
if c.WriteHeaders {
// use Headers if set, otherwise default to
// query Columns
var headers []string
if len(c.Headers) > 0 {
headers = c.Headers
} else {
headers = columnNames
}
err = csvWriter.Write(headers)
if err != nil {
// TODO wrap err to say it was an issue with headers?
return err
}
}
count := len(columnNames)
values := make([]interface{}, count)
valuePtrs := make([]interface{}, count)
for rows.Next() {
row := make([]string, count)
for i, _ := range columnNames {
valuePtrs[i] = &values[i]
}
if err = rows.Scan(valuePtrs...); err != nil {
return err
}
for i, _ := range columnNames {
var value interface{}
rawValue := values[i]
byteArray, ok := rawValue.([]byte)
if ok {
value = string(byteArray)
} else {
value = rawValue
}
timeValue, ok := value.(time.Time)
if ok && c.TimeFormat != "" {
value = timeValue.Format(c.TimeFormat)
}
if value == nil {
row[i] = ""
} else {
row[i] = fmt.Sprintf("%v", value)
}
}
writeRow := true
if c.rowPreProcessor != nil {
writeRow, row = c.rowPreProcessor(row, columnNames)
}
if writeRow {
err = csvWriter.Write(row)
if err != nil {
// TODO wrap this err to give context as to why it failed?
return err
}
}
}
err = rows.Err()
csvWriter.Flush()
return err
}
// New will return a Converter which will write your CSV however you like
// but will allow you to set a bunch of non-default behaivour like overriding
// headers or injecting a pre-processing step into your conversion
func New(rows *sql.Rows, delim rune) *Converter {
return &Converter{
rows: rows,
WriteHeaders: true,
Delimeter:delim,
}
}