storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/s3select/json/record.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package json 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "io" 24 "math" 25 "strconv" 26 "strings" 27 28 "github.com/bcicen/jstream" 29 30 csv "storj.io/minio/pkg/csvparser" 31 "storj.io/minio/pkg/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 b, ok := value.ToBytes(); ok { 93 // This can either be raw json or a CSV value. 94 // Only treat objects and arrays as JSON. 95 if len(b) > 0 && (b[0] == '{' || b[0] == '[') { 96 v = RawJSON(b) 97 } else { 98 v = string(b) 99 } 100 } else if arr, ok := value.ToArray(); ok { 101 v = arr 102 } else { 103 return nil, fmt.Errorf("unsupported sql value %v and type %v", value, value.GetTypeString()) 104 } 105 106 name = strings.Replace(name, "*", "__ALL__", -1) 107 r.KVS = append(r.KVS, jstream.KV{Key: name, Value: v}) 108 return r, nil 109 } 110 111 // WriteCSV - encodes to CSV data. 112 func (r *Record) WriteCSV(writer io.Writer, opts sql.WriteCSVOpts) error { 113 var csvRecord []string 114 for _, kv := range r.KVS { 115 var columnValue string 116 switch val := kv.Value.(type) { 117 case float64: 118 columnValue = jsonFloat(val) 119 case string: 120 columnValue = val 121 case bool, int64: 122 columnValue = fmt.Sprintf("%v", val) 123 case nil: 124 columnValue = "" 125 case RawJSON: 126 columnValue = string([]byte(val)) 127 case []interface{}: 128 b, err := json.Marshal(val) 129 if err != nil { 130 return err 131 } 132 columnValue = string(b) 133 default: 134 return fmt.Errorf("Cannot marshal unhandled type: %T", kv.Value) 135 } 136 csvRecord = append(csvRecord, columnValue) 137 } 138 139 w := csv.NewWriter(writer) 140 w.Comma = opts.FieldDelimiter 141 w.Quote = opts.Quote 142 w.AlwaysQuote = opts.AlwaysQuote 143 w.QuoteEscape = opts.QuoteEscape 144 if err := w.Write(csvRecord); err != nil { 145 return err 146 } 147 w.Flush() 148 if err := w.Error(); err != nil { 149 return err 150 } 151 152 return nil 153 } 154 155 // Raw - returns the underlying representation. 156 func (r *Record) Raw() (sql.SelectObjectFormat, interface{}) { 157 return r.SelectFormat, r.KVS 158 } 159 160 // WriteJSON - encodes to JSON data. 161 func (r *Record) WriteJSON(writer io.Writer) error { 162 return json.NewEncoder(writer).Encode(r.KVS) 163 } 164 165 // Replace the underlying buffer of json data. 166 func (r *Record) Replace(k interface{}) error { 167 v, ok := k.(jstream.KVS) 168 if !ok { 169 return fmt.Errorf("cannot replace internal data in json record with type %T", k) 170 } 171 r.KVS = v 172 return nil 173 } 174 175 // NewRecord - creates new empty JSON record. 176 func NewRecord(f sql.SelectObjectFormat) *Record { 177 return &Record{ 178 KVS: jstream.KVS{}, 179 SelectFormat: f, 180 } 181 } 182 183 // jsonFloat converts a float to string similar to Go stdlib formats json floats. 184 func jsonFloat(f float64) string { 185 var tmp [32]byte 186 dst := tmp[:0] 187 188 // Convert as if by ES6 number to string conversion. 189 // This matches most other JSON generators. 190 // See golang.org/issue/6384 and golang.org/issue/14135. 191 // Like fmt %g, but the exponent cutoffs are different 192 // and exponents themselves are not padded to two digits. 193 abs := math.Abs(f) 194 fmt := byte('f') 195 if abs != 0 { 196 if abs < 1e-6 || abs >= 1e21 { 197 fmt = 'e' 198 } 199 } 200 dst = strconv.AppendFloat(dst, f, fmt, -1, 64) 201 if fmt == 'e' { 202 // clean up e-09 to e-9 203 n := len(dst) 204 if n >= 4 && dst[n-4] == 'e' && dst[n-3] == '-' && dst[n-2] == '0' { 205 dst[n-2] = dst[n-1] 206 dst = dst[:n-1] 207 } 208 } 209 return string(dst) 210 }