github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/s3select/json/record.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package json 19 20 import ( 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "math" 26 "strconv" 27 "strings" 28 29 "github.com/bcicen/jstream" 30 csv "github.com/minio/csvparser" 31 "github.com/minio/minio/internal/s3select/sql" 32 ) 33 34 // RawJSON is a byte-slice that contains valid JSON 35 type RawJSON []byte 36 37 // MarshalJSON instance for []byte that assumes that byte-slice is 38 // already serialized JSON 39 func (b RawJSON) MarshalJSON() ([]byte, error) { 40 return b, nil 41 } 42 43 // Record - is JSON record. 44 type Record struct { 45 // Used in Set(), Marshal*() 46 KVS jstream.KVS 47 48 SelectFormat sql.SelectObjectFormat 49 } 50 51 // Get - gets the value for a column name. 52 func (r *Record) Get(name string) (*sql.Value, error) { 53 // Get is implemented directly in the sql package. 54 return nil, errors.New("not implemented here") 55 } 56 57 // Reset the record. 58 func (r *Record) Reset() { 59 if len(r.KVS) > 0 { 60 r.KVS = r.KVS[:0] 61 } 62 } 63 64 // Clone the record and if possible use the destination provided. 65 func (r *Record) Clone(dst sql.Record) sql.Record { 66 other, ok := dst.(*Record) 67 if !ok { 68 other = &Record{} 69 } 70 if len(other.KVS) > 0 { 71 other.KVS = other.KVS[:0] 72 } 73 other.KVS = append(other.KVS, r.KVS...) 74 return other 75 } 76 77 // Set - sets the value for a column name. 78 func (r *Record) Set(name string, value *sql.Value) (sql.Record, error) { 79 var v interface{} 80 if b, ok := value.ToBool(); ok { 81 v = b 82 } else if f, ok := value.ToFloat(); ok { 83 v = f 84 } else if i, ok := value.ToInt(); ok { 85 v = i 86 } else if t, ok := value.ToTimestamp(); ok { 87 v = sql.FormatSQLTimestamp(t) 88 } else if s, ok := value.ToString(); ok { 89 v = s 90 } else if value.IsNull() { 91 v = nil 92 } else if value.IsMissing() { 93 return r, nil 94 } else if b, ok := value.ToBytes(); ok { 95 // This can either be raw json or a CSV value. 96 // Only treat objects and arrays as JSON. 97 if len(b) > 0 && (b[0] == '{' || b[0] == '[') { 98 v = RawJSON(b) 99 } else { 100 v = string(b) 101 } 102 } else if arr, ok := value.ToArray(); ok { 103 v = arr 104 } else { 105 return nil, fmt.Errorf("unsupported sql value %v and type %v", value, value.GetTypeString()) 106 } 107 108 name = strings.ReplaceAll(name, "*", "__ALL__") 109 r.KVS = append(r.KVS, jstream.KV{Key: name, Value: v}) 110 return r, nil 111 } 112 113 // WriteCSV - encodes to CSV data. 114 func (r *Record) WriteCSV(writer io.Writer, opts sql.WriteCSVOpts) error { 115 var csvRecord []string 116 for _, kv := range r.KVS { 117 var columnValue string 118 switch val := kv.Value.(type) { 119 case float64: 120 columnValue = jsonFloat(val) 121 case string: 122 columnValue = val 123 case bool, int64: 124 columnValue = fmt.Sprintf("%v", val) 125 case nil: 126 columnValue = "" 127 case RawJSON: 128 columnValue = string([]byte(val)) 129 case []interface{}: 130 b, err := json.Marshal(val) 131 if err != nil { 132 return err 133 } 134 columnValue = string(b) 135 default: 136 return fmt.Errorf("Cannot marshal unhandled type: %T", kv.Value) 137 } 138 csvRecord = append(csvRecord, columnValue) 139 } 140 141 w := csv.NewWriter(writer) 142 w.Comma = opts.FieldDelimiter 143 w.Quote = opts.Quote 144 w.AlwaysQuote = opts.AlwaysQuote 145 w.QuoteEscape = opts.QuoteEscape 146 if err := w.Write(csvRecord); err != nil { 147 return err 148 } 149 w.Flush() 150 return w.Error() 151 } 152 153 // Raw - returns the underlying representation. 154 func (r *Record) Raw() (sql.SelectObjectFormat, interface{}) { 155 return r.SelectFormat, r.KVS 156 } 157 158 // WriteJSON - encodes to JSON data. 159 func (r *Record) WriteJSON(writer io.Writer) error { 160 return json.NewEncoder(writer).Encode(r.KVS) 161 } 162 163 // Replace the underlying buffer of json data. 164 func (r *Record) Replace(k interface{}) error { 165 v, ok := k.(jstream.KVS) 166 if !ok { 167 return fmt.Errorf("cannot replace internal data in json record with type %T", k) 168 } 169 r.KVS = v 170 return nil 171 } 172 173 // NewRecord - creates new empty JSON record. 174 func NewRecord(f sql.SelectObjectFormat) *Record { 175 return &Record{ 176 KVS: jstream.KVS{}, 177 SelectFormat: f, 178 } 179 } 180 181 // jsonFloat converts a float to string similar to Go stdlib formats json floats. 182 func jsonFloat(f float64) string { 183 var tmp [32]byte 184 dst := tmp[:0] 185 186 // Convert as if by ES6 number to string conversion. 187 // This matches most other JSON generators. 188 // See golang.org/issue/6384 and golang.org/issue/14135. 189 // Like fmt %g, but the exponent cutoffs are different 190 // and exponents themselves are not padded to two digits. 191 abs := math.Abs(f) 192 fmt := byte('f') 193 if abs != 0 { 194 if abs < 1e-6 || abs >= 1e21 { 195 fmt = 'e' 196 } 197 } 198 dst = strconv.AppendFloat(dst, f, fmt, -1, 64) 199 if fmt == 'e' { 200 // clean up e-09 to e-9 201 n := len(dst) 202 if n >= 4 && dst[n-4] == 'e' && dst[n-3] == '-' && dst[n-2] == '0' { 203 dst[n-2] = dst[n-1] 204 dst = dst[:n-1] 205 } 206 } 207 return string(dst) 208 }