1// Copyright 2015 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package bigquery
16
17import (
18	"bytes"
19	"encoding/json"
20	"fmt"
21	"reflect"
22	"strconv"
23	"time"
24
25	"cloud.google.com/go/civil"
26)
27
28// NullInt64 represents a BigQuery INT64 that may be NULL.
29type NullInt64 struct {
30	Int64 int64
31	Valid bool // Valid is true if Int64 is not NULL.
32}
33
34func (n NullInt64) String() string { return nullstr(n.Valid, n.Int64) }
35
36// NullString represents a BigQuery STRING that may be NULL.
37type NullString struct {
38	StringVal string
39	Valid     bool // Valid is true if StringVal is not NULL.
40}
41
42func (n NullString) String() string { return nullstr(n.Valid, n.StringVal) }
43
44// NullGeography represents a BigQuery GEOGRAPHY string that may be NULL.
45type NullGeography struct {
46	GeographyVal string
47	Valid        bool // Valid is true if GeographyVal is not NULL.
48}
49
50func (n NullGeography) String() string { return nullstr(n.Valid, n.GeographyVal) }
51
52// NullFloat64 represents a BigQuery FLOAT64 that may be NULL.
53type NullFloat64 struct {
54	Float64 float64
55	Valid   bool // Valid is true if Float64 is not NULL.
56}
57
58func (n NullFloat64) String() string { return nullstr(n.Valid, n.Float64) }
59
60// NullBool represents a BigQuery BOOL that may be NULL.
61type NullBool struct {
62	Bool  bool
63	Valid bool // Valid is true if Bool is not NULL.
64}
65
66func (n NullBool) String() string { return nullstr(n.Valid, n.Bool) }
67
68// NullTimestamp represents a BigQuery TIMESTAMP that may be null.
69type NullTimestamp struct {
70	Timestamp time.Time
71	Valid     bool // Valid is true if Time is not NULL.
72}
73
74func (n NullTimestamp) String() string { return nullstr(n.Valid, n.Timestamp) }
75
76// NullDate represents a BigQuery DATE that may be null.
77type NullDate struct {
78	Date  civil.Date
79	Valid bool // Valid is true if Date is not NULL.
80}
81
82func (n NullDate) String() string { return nullstr(n.Valid, n.Date) }
83
84// NullTime represents a BigQuery TIME that may be null.
85type NullTime struct {
86	Time  civil.Time
87	Valid bool // Valid is true if Time is not NULL.
88}
89
90func (n NullTime) String() string {
91	if !n.Valid {
92		return "<null>"
93	}
94	return CivilTimeString(n.Time)
95}
96
97// NullDateTime represents a BigQuery DATETIME that may be null.
98type NullDateTime struct {
99	DateTime civil.DateTime
100	Valid    bool // Valid is true if DateTime is not NULL.
101}
102
103func (n NullDateTime) String() string {
104	if !n.Valid {
105		return "<null>"
106	}
107	return CivilDateTimeString(n.DateTime)
108}
109
110// MarshalJSON converts the NullInt64 to JSON.
111func (n NullInt64) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Int64) }
112
113// MarshalJSON converts the NullFloat64 to JSON.
114func (n NullFloat64) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Float64) }
115
116// MarshalJSON converts the NullBool to JSON.
117func (n NullBool) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Bool) }
118
119// MarshalJSON converts the NullString to JSON.
120func (n NullString) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.StringVal) }
121
122// MarshalJSON converts the NullGeography to JSON.
123func (n NullGeography) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.GeographyVal) }
124
125// MarshalJSON converts the NullTimestamp to JSON.
126func (n NullTimestamp) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Timestamp) }
127
128// MarshalJSON converts the NullDate to JSON.
129func (n NullDate) MarshalJSON() ([]byte, error) { return nulljson(n.Valid, n.Date) }
130
131// MarshalJSON converts the NullTime to JSON.
132func (n NullTime) MarshalJSON() ([]byte, error) {
133	if !n.Valid {
134		return jsonNull, nil
135	}
136	return []byte(`"` + CivilTimeString(n.Time) + `"`), nil
137}
138
139// MarshalJSON converts the NullDateTime to JSON.
140func (n NullDateTime) MarshalJSON() ([]byte, error) {
141	if !n.Valid {
142		return jsonNull, nil
143	}
144	return []byte(`"` + CivilDateTimeString(n.DateTime) + `"`), nil
145}
146
147func nullstr(valid bool, v interface{}) string {
148	if !valid {
149		return "NULL"
150	}
151	return fmt.Sprint(v)
152}
153
154var jsonNull = []byte("null")
155
156func nulljson(valid bool, v interface{}) ([]byte, error) {
157	if !valid {
158		return jsonNull, nil
159	}
160	return json.Marshal(v)
161}
162
163// UnmarshalJSON converts JSON into a NullInt64.
164func (n *NullInt64) UnmarshalJSON(b []byte) error {
165	n.Valid = false
166	n.Int64 = 0
167	if bytes.Equal(b, jsonNull) {
168		return nil
169	}
170
171	if err := json.Unmarshal(b, &n.Int64); err != nil {
172		return err
173	}
174	n.Valid = true
175	return nil
176}
177
178// UnmarshalJSON converts JSON into a NullFloat64.
179func (n *NullFloat64) UnmarshalJSON(b []byte) error {
180	n.Valid = false
181	n.Float64 = 0
182	if bytes.Equal(b, jsonNull) {
183		return nil
184	}
185
186	if err := json.Unmarshal(b, &n.Float64); err != nil {
187		return err
188	}
189	n.Valid = true
190	return nil
191}
192
193// UnmarshalJSON converts JSON into a NullBool.
194func (n *NullBool) UnmarshalJSON(b []byte) error {
195	n.Valid = false
196	n.Bool = false
197	if bytes.Equal(b, jsonNull) {
198		return nil
199	}
200
201	if err := json.Unmarshal(b, &n.Bool); err != nil {
202		return err
203	}
204	n.Valid = true
205	return nil
206}
207
208// UnmarshalJSON converts JSON into a NullString.
209func (n *NullString) UnmarshalJSON(b []byte) error {
210	n.Valid = false
211	n.StringVal = ""
212	if bytes.Equal(b, jsonNull) {
213		return nil
214	}
215
216	if err := json.Unmarshal(b, &n.StringVal); err != nil {
217		return err
218	}
219	n.Valid = true
220	return nil
221}
222
223// UnmarshalJSON converts JSON into a NullGeography.
224func (n *NullGeography) UnmarshalJSON(b []byte) error {
225	n.Valid = false
226	n.GeographyVal = ""
227	if bytes.Equal(b, jsonNull) {
228		return nil
229	}
230	if err := json.Unmarshal(b, &n.GeographyVal); err != nil {
231		return err
232	}
233	n.Valid = true
234	return nil
235}
236
237// UnmarshalJSON converts JSON into a NullTimestamp.
238func (n *NullTimestamp) UnmarshalJSON(b []byte) error {
239	n.Valid = false
240	n.Timestamp = time.Time{}
241	if bytes.Equal(b, jsonNull) {
242		return nil
243	}
244
245	if err := json.Unmarshal(b, &n.Timestamp); err != nil {
246		return err
247	}
248	n.Valid = true
249	return nil
250}
251
252// UnmarshalJSON converts JSON into a NullDate.
253func (n *NullDate) UnmarshalJSON(b []byte) error {
254	n.Valid = false
255	n.Date = civil.Date{}
256	if bytes.Equal(b, jsonNull) {
257		return nil
258	}
259
260	if err := json.Unmarshal(b, &n.Date); err != nil {
261		return err
262	}
263	n.Valid = true
264	return nil
265}
266
267// UnmarshalJSON converts JSON into a NullTime.
268func (n *NullTime) UnmarshalJSON(b []byte) error {
269	n.Valid = false
270	n.Time = civil.Time{}
271	if bytes.Equal(b, jsonNull) {
272		return nil
273	}
274
275	s, err := strconv.Unquote(string(b))
276	if err != nil {
277		return err
278	}
279
280	t, err := civil.ParseTime(s)
281	if err != nil {
282		return err
283	}
284	n.Time = t
285
286	n.Valid = true
287	return nil
288}
289
290// UnmarshalJSON converts JSON into a NullDateTime.
291func (n *NullDateTime) UnmarshalJSON(b []byte) error {
292	n.Valid = false
293	n.DateTime = civil.DateTime{}
294	if bytes.Equal(b, jsonNull) {
295		return nil
296	}
297
298	s, err := strconv.Unquote(string(b))
299	if err != nil {
300		return err
301	}
302
303	dt, err := parseCivilDateTime(s)
304	if err != nil {
305		return err
306	}
307	n.DateTime = dt
308
309	n.Valid = true
310	return nil
311}
312
313var (
314	typeOfNullInt64     = reflect.TypeOf(NullInt64{})
315	typeOfNullFloat64   = reflect.TypeOf(NullFloat64{})
316	typeOfNullBool      = reflect.TypeOf(NullBool{})
317	typeOfNullString    = reflect.TypeOf(NullString{})
318	typeOfNullGeography = reflect.TypeOf(NullGeography{})
319	typeOfNullTimestamp = reflect.TypeOf(NullTimestamp{})
320	typeOfNullDate      = reflect.TypeOf(NullDate{})
321	typeOfNullTime      = reflect.TypeOf(NullTime{})
322	typeOfNullDateTime  = reflect.TypeOf(NullDateTime{})
323)
324
325func nullableFieldType(t reflect.Type) FieldType {
326	switch t {
327	case typeOfNullInt64:
328		return IntegerFieldType
329	case typeOfNullFloat64:
330		return FloatFieldType
331	case typeOfNullBool:
332		return BooleanFieldType
333	case typeOfNullString:
334		return StringFieldType
335	case typeOfNullGeography:
336		return GeographyFieldType
337	case typeOfNullTimestamp:
338		return TimestampFieldType
339	case typeOfNullDate:
340		return DateFieldType
341	case typeOfNullTime:
342		return TimeFieldType
343	case typeOfNullDateTime:
344		return DateTimeFieldType
345	default:
346		return ""
347	}
348}
349