-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.go
122 lines (114 loc) · 3.31 KB
/
utils.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
package db
import (
"bytes"
"context"
"database/sql"
"fmt"
"io"
"os"
"unicode/utf8"
"github.com/domonda/go-sqldb"
)
// ReplaceErrNoRows returns the passed replacement error
// if errors.Is(err, sql.ErrNoRows),
// else the passed err is returned unchanged.
func ReplaceErrNoRows(err, replacement error) error {
return sqldb.ReplaceErrNoRows(err, replacement)
}
// IsOtherThanErrNoRows returns true if the passed error is not nil
// and does not unwrap to, or is sql.ErrNoRows.
func IsOtherThanErrNoRows(err error) bool {
return sqldb.IsOtherThanErrNoRows(err)
}
// DebugPrintConn prints a line to stderr using the passed args
// and appending the transaction state of the connection
// and the current time of the database using `select now()`
// or an error if the time could not be queried.
func DebugPrintConn(ctx context.Context, args ...any) {
opts, isTx := Conn(ctx).TransactionOptions()
if isTx {
args = append(args, "SQL-Transaction")
if optsStr := TxOptionsString(opts); optsStr != "" {
args = append(args, "Isolation", optsStr)
}
}
now, err := Conn(ctx).Now()
if err == nil {
args = append(args, "NOW():", now)
} else {
args = append(args, "ERROR:", err)
}
fmt.Fprintln(os.Stderr, args...)
}
// TxOptionsString returns a string representing the
// passed TxOptions wich will be empty for the default options.
func TxOptionsString(opts *sql.TxOptions) string {
switch {
case opts == nil:
return ""
case opts.ReadOnly && opts.Isolation == sql.LevelDefault:
return "Read-Only"
case opts.ReadOnly && opts.Isolation != sql.LevelDefault:
return "Read-Only " + opts.Isolation.String()
case opts.Isolation != sql.LevelDefault:
return opts.Isolation.String()
default:
return ""
}
}
// PrintlnTable prints a string table to stdout
// padding the table with spaces and using '|' as
// delimiter between columns.
func PrintlnTable(rows [][]string, err error) error {
if err != nil {
_, e := fmt.Println(err)
return e
}
return FprintTable(os.Stdout, rows, "|")
}
// FprintTable prints a string table to an io.Writer
// padding the table with spaces and using the passed
// columnDelimiter between columns.
func FprintTable(w io.Writer, rows [][]string, columnDelimiter string) error {
// Collect column widths
var colRuneCount []int
for row := range rows {
for col, str := range rows[row] {
count := utf8.RuneCountInString(str)
if col >= len(colRuneCount) {
colRuneCount = append(colRuneCount, count)
} else if count > colRuneCount[col] {
colRuneCount[col] = count
}
}
}
// Print with padded cell widths and columnDelimiter
line := make([]byte, 0, 1024)
for row := range rows {
// Append cells of row to line
for col, str := range rows[row] {
if col > 0 {
line = append(line, columnDelimiter...)
}
line = append(line, str...)
if pad := colRuneCount[col] - utf8.RuneCountInString(str); pad > 0 {
line = append(line, bytes.Repeat([]byte{' '}, pad)...)
}
}
// In case not all rows have the same number of cells
// pad line with empty cells
for col := len(rows[row]); col < len(colRuneCount); col++ {
if col > 0 {
line = append(line, columnDelimiter...)
}
line = append(line, bytes.Repeat([]byte{' '}, colRuneCount[col])...)
}
line = append(line, '\n')
_, err := w.Write(line)
if err != nil {
return err
}
line = line[:0]
}
return nil
}