Skip to content

Commit

Permalink
Merge pull request #66130 from knz/backport21.1-65063-66079
Browse files Browse the repository at this point in the history
  • Loading branch information
knz authored Jun 16, 2021
2 parents af2c90b + aba5a3d commit 12b59c6
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 25 deletions.
47 changes: 41 additions & 6 deletions pkg/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,8 +654,19 @@ func Example_sql_format() {
c.RunWithArgs([]string{"sql", "-e", "create database t; create table t.times (bare timestamp, withtz timestamptz)"})
c.RunWithArgs([]string{"sql", "-e", "insert into t.times values ('2016-01-25 10:10:10', '2016-01-25 10:10:10-05:00')"})
c.RunWithArgs([]string{"sql", "-e", "select bare from t.times; select withtz from t.times"})
c.RunWithArgs([]string{"sql", "-e", "select '2021-03-20'::date; select '01:01'::time; select '01:01'::timetz"})
c.RunWithArgs([]string{"sql", "-e", "select (1/3.0)::real; select (1/3.0)::double precision"})
c.RunWithArgs([]string{"sql", "-e",
"select '2021-03-20'::date; select '01:01'::time; select '01:01'::timetz; select '01:01+02:02'::timetz"})
c.RunWithArgs([]string{"sql", "-e", "select (1/3.0)::real; select (1/3.0)::double precision; select '-inf'::float8"})
// Special characters inside arrays used to be represented as escaped bytes.
c.RunWithArgs([]string{"sql", "-e", "select array['哈哈'::TEXT], array['哈哈'::NAME], array['哈哈'::VARCHAR]"})
c.RunWithArgs([]string{"sql", "-e", "select array['哈哈'::CHAR(2)], array['哈'::\"char\"]"})
// Preserve quoting of arrays containing commas or double quotes.
c.RunWithArgs([]string{"sql", "-e", `select array['a,b', 'a"b', 'a\b']`, "--format=table"})
// Infinities inside float arrays used to be represented differently from infinities as simpler scalar.
c.RunWithArgs([]string{"sql", "-e", "select array['Inf'::FLOAT4, '-Inf'::FLOAT4], array['Inf'::FLOAT8]"})
// Sanity check for other array types.
c.RunWithArgs([]string{"sql", "-e", "select array[true, false], array['01:01'::time], array['2021-03-20'::date]"})
c.RunWithArgs([]string{"sql", "-e", "select array[123::int2], array[123::int4], array[123::int8]"})

// Output:
// sql -e create database t; create table t.times (bare timestamp, withtz timestamptz)
Expand All @@ -666,19 +677,43 @@ func Example_sql_format() {
// bare
// 2016-01-25 10:10:10
// withtz
// 2016-01-25 15:10:10+00:00:00
// sql -e select '2021-03-20'::date; select '01:01'::time; select '01:01'::timetz
// 2016-01-25 15:10:10+00
// sql -e select '2021-03-20'::date; select '01:01'::time; select '01:01'::timetz; select '01:01+02:02'::timetz
// date
// 2021-03-20
// time
// 01:01:00
// timetz
// 01:01:00+00:00:00
// sql -e select (1/3.0)::real; select (1/3.0)::double precision
// 01:01:00+00
// timetz
// 01:01:00+02:02
// sql -e select (1/3.0)::real; select (1/3.0)::double precision; select '-inf'::float8
// float4
// 0.33333334
// float8
// 0.3333333333333333
// float8
// -Infinity
// sql -e select array['哈哈'::TEXT], array['哈哈'::NAME], array['哈哈'::VARCHAR]
// array array array
// {哈哈} {哈哈} {哈哈}
// sql -e select array['哈哈'::CHAR(2)], array['哈'::"char"]
// array array
// {哈哈} {哈}
// sql -e select array['a,b', 'a"b', 'a\b'] --format=table
// array
// -------------------------
// {"a,b","a\"b","a\\b"}
// (1 row)
// sql -e select array['Inf'::FLOAT4, '-Inf'::FLOAT4], array['Inf'::FLOAT8]
// array array
// {Infinity,-Infinity} {Infinity}
// sql -e select array[true, false], array['01:01'::time], array['2021-03-20'::date]
// array array array
// {true,false} {01:01:00} {2021-03-20}
// sql -e select array[123::int2], array[123::int4], array[123::int8]
// array array array
// {123} {123} {123}
}

func Example_sql_column_labels() {
Expand Down
107 changes: 103 additions & 4 deletions pkg/cli/sql_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ package cli

import (
"context"
gosql "database/sql"
"database/sql/driver"
"fmt"
"io"
"math"
"net/url"
"reflect"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -1120,9 +1123,17 @@ func isNotGraphicUnicodeOrTabOrNewline(r rune) bool {
func formatVal(
val driver.Value, colType string, showPrintableUnicode bool, showNewLinesAndTabs bool,
) string {
if b, ok := val.([]byte); ok && colType == "NAME" {
val = string(b)
colType = "VARCHAR"
log.VInfof(context.Background(), 2, "value: go %T, sql %q", val, colType)

if b, ok := val.([]byte); ok {
if strings.HasPrefix(colType, "_") && len(b) > 0 && b[0] == '{' {
return formatArray(b, colType[1:], showPrintableUnicode, showNewLinesAndTabs)
}

if colType == "NAME" {
val = string(b)
colType = "VARCHAR"
}
}

switch t := val.(type) {
Expand All @@ -1134,6 +1145,11 @@ func formatVal(
if colType == "FLOAT4" {
width = 32
}
if math.IsInf(t, 1) {
return "Infinity"
} else if math.IsInf(t, -1) {
return "-Infinity"
}
return strconv.FormatFloat(t, 'g', -1, width)

case string:
Expand Down Expand Up @@ -1178,15 +1194,98 @@ func formatVal(
// Some unknown/new time-like format.
tfmt = timeutil.FullTimeFormat
}
if tfmt == timeutil.TimestampWithTZFormat || tfmt == timeutil.TimeWithTZFormat {
if _, offsetSeconds := t.Zone(); offsetSeconds%60 != 0 {
tfmt += ":00:00"
} else if offsetSeconds%3600 != 0 {
tfmt += ":00"
}
}
return t.Format(tfmt)
}

return fmt.Sprint(val)
}

func formatArray(
b []byte, colType string, showPrintableUnicode bool, showNewLinesAndTabs bool,
) string {
// backingArray is the array we're going to parse the server data
// into.
var backingArray interface{}
// parsingArray is a helper structure provided by lib/pq to parse
// arrays.
var parsingArray gosql.Scanner

// lib.pq has different array parsers for special value types.
//
// TODO(knz): This would better use a general-purpose parser
// using the OID to look up an array parser in crdb's sql package.
// However, unfortunately the OID is hidden from us.
switch colType {
case "BOOL":
boolArray := []bool{}
backingArray = &boolArray
parsingArray = (*pq.BoolArray)(&boolArray)
case "FLOAT4", "FLOAT8":
floatArray := []float64{}
backingArray = &floatArray
parsingArray = (*pq.Float64Array)(&floatArray)
case "INT2", "INT4", "INT8", "OID":
intArray := []int64{}
backingArray = &intArray
parsingArray = (*pq.Int64Array)(&intArray)
case "TEXT", "VARCHAR", "NAME", "CHAR", "BPCHAR":
stringArray := []string{}
backingArray = &stringArray
parsingArray = (*pq.StringArray)(&stringArray)
default:
genArray := [][]byte{}
backingArray = &genArray
parsingArray = &pq.GenericArray{A: &genArray}
}

// Now ask the pq array parser to convert the byte slice
// from the server into a Go array.
if err := parsingArray.Scan(b); err != nil {
// A parsing failure is not a catastrophe; we can still print out
// the array as a byte slice. This will do in many cases.
log.VInfof(context.Background(), 1, "unable to parse %q (sql %q) as array: %v", b, colType, err)
return formatVal(b, "BYTEA", showPrintableUnicode, showNewLinesAndTabs)
}

// We have a go array in "backingArray". Now print it out.
var buf strings.Builder
buf.WriteByte('{')
comma := "" // delimiter
v := reflect.ValueOf(backingArray).Elem()
for i := 0; i < v.Len(); i++ {
buf.WriteString(comma)

// Access the i-th element in the backingArray.
arrayVal := driver.Value(v.Index(i).Interface())
// Format the value recursively into a string.
vs := formatVal(arrayVal, colType, showPrintableUnicode, showNewLinesAndTabs)

// If the value contains special characters or a comma, enclose in double quotes.
// Also escape the special characters.
if strings.IndexByte(vs, ',') >= 0 || reArrayStringEscape.MatchString(vs) {
vs = "\"" + reArrayStringEscape.ReplaceAllString(vs, "\\$1") + "\""
}

// Add the string for that one value to the output array representation.
buf.WriteString(vs)
comma = ","
}
buf.WriteByte('}')
return buf.String()
}

var reArrayStringEscape = regexp.MustCompile(`(["\\])`)

var timeOutputFormats = map[string]string{
"TIMESTAMP": timeutil.TimestampWithoutTZFormat,
"TIMESTAMPTZ": timeutil.FullTimeFormat,
"TIMESTAMPTZ": timeutil.TimestampWithTZFormat,
"TIME": timeutil.TimeWithoutTZFormat,
"TIMETZ": timeutil.TimeWithTZFormat,
"DATE": timeutil.DateFormat,
Expand Down
12 changes: 10 additions & 2 deletions pkg/cmd/generate-binary/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,6 @@ var inputs = map[string][]string{
"9004-10-19 10:23:54",
},

/* TODO(mjibson): fix these; there's a slight timezone display difference
"'%s'::timestamptz": {
"1999-01-08 04:05:06+00",
"1999-01-08 04:05:06+00:00",
Expand All @@ -312,10 +311,19 @@ var inputs = map[string][]string{
"4004-10-19 10:23:54",
"9004-10-19 10:23:54",
},
*/

"'%s'::timetz": {
"04:05:06+00",
"04:05:06+00:00",
"04:05:06+10",
"04:05:06+10:00",
"04:05:06+10:30",
"04:05:06",
"10:23:54",
"00:00:00",
"10:23:54",
"10:23:54 BC",
"10:23:54",
"10:23:54+1:2:3",
"10:23:54+1:2",
},
Expand Down
Loading

0 comments on commit 12b59c6

Please sign in to comment.