Coverage Report

Created: 2026-10-24 13:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/table/iceberg_default_value.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <gen_cpp/ExternalTableSchema_types.h>
21
#include <rapidjson/document.h>
22
#include <rapidjson/stringbuffer.h>
23
#include <rapidjson/writer.h>
24
25
#include <cstddef>
26
#include <deque>
27
#include <limits>
28
#include <string>
29
#include <string_view>
30
#include <unordered_map>
31
#include <utility>
32
33
#include "common/status.h"
34
#include "core/assert_cast.h"
35
#include "core/column/column.h"
36
#include "core/data_type/data_type.h"
37
#include "core/data_type/data_type_array.h"
38
#include "core/data_type/data_type_map.h"
39
#include "core/data_type/data_type_nullable.h"
40
#include "core/data_type/data_type_struct.h"
41
#include "core/data_type/primitive_type.h"
42
#include "core/field.h"
43
#include "util/string_util.h"
44
#include "util/url_coding.h"
45
46
namespace doris::iceberg {
47
48
namespace detail {
49
50
inline bool parse_non_finite_default(doris::PrimitiveType type, std::string_view value,
51
77
                                     Field* result) {
52
77
    DORIS_CHECK(result != nullptr);
53
77
    if (type != TYPE_FLOAT && type != TYPE_DOUBLE) {
54
68
        return false;
55
68
    }
56
9
    double parsed;
57
9
    if (value == "NaN") {
58
3
        parsed = std::numeric_limits<double>::quiet_NaN();
59
6
    } else if (value == "Infinity") {
60
2
        parsed = std::numeric_limits<double>::infinity();
61
4
    } else if (value == "-Infinity") {
62
2
        parsed = -std::numeric_limits<double>::infinity();
63
2
    } else {
64
2
        return false;
65
2
    }
66
    // Iceberg serializes non-finite defaults as strings, which generic Doris numeric parsers reject.
67
7
    *result = type == TYPE_FLOAT ? Field::create_field<TYPE_FLOAT>(static_cast<float>(parsed))
68
7
                                 : Field::create_field<TYPE_DOUBLE>(parsed);
69
7
    return true;
70
9
}
71
72
19
inline const schema::external::TField* get_field_ptr(const schema::external::TFieldPtr& field_ptr) {
73
19
    if (!field_ptr.__isset.field_ptr || field_ptr.field_ptr == nullptr) {
74
0
        return nullptr;
75
0
    }
76
19
    return field_ptr.field_ptr.get();
77
19
}
78
79
inline const schema::external::TField* find_struct_child(
80
8
        const schema::external::TStructField& struct_field, const std::string& name) {
81
8
    if (!struct_field.__isset.fields) {
82
0
        return nullptr;
83
0
    }
84
13
    for (const auto& child_ptr : struct_field.fields) {
85
13
        const auto* child = get_field_ptr(child_ptr);
86
13
        if (child != nullptr && child->__isset.name && iequal(child->name, name)) {
87
6
            return child;
88
6
        }
89
13
    }
90
2
    for (const auto& child_ptr : struct_field.fields) {
91
2
        const auto* child = get_field_ptr(child_ptr);
92
2
        if (child == nullptr || !child->__isset.name_mapping) {
93
0
            continue;
94
0
        }
95
2
        for (const auto& alias : child->name_mapping) {
96
2
            if (iequal(alias, name)) {
97
2
                return child;
98
2
            }
99
2
        }
100
2
    }
101
0
    return nullptr;
102
2
}
103
104
76
inline int hex_value(char c) {
105
76
    if (c >= '0' && c <= '9') {
106
52
        return c - '0';
107
52
    }
108
24
    if (c >= 'a' && c <= 'f') {
109
17
        return c - 'a' + 10;
110
17
    }
111
7
    if (c >= 'A' && c <= 'F') {
112
6
        return c - 'A' + 10;
113
6
    }
114
1
    return -1;
115
7
}
116
117
5
inline Status decode_hex(std::string_view encoded, std::string* decoded) {
118
5
    DORIS_CHECK(decoded != nullptr);
119
5
    if ((encoded.size() & 1U) != 0) {
120
1
        return Status::InvalidArgument("Invalid odd-length Iceberg binary default");
121
1
    }
122
4
    decoded->resize(encoded.size() / 2);
123
41
    for (size_t index = 0; index < encoded.size(); index += 2) {
124
38
        const int high = hex_value(encoded[index]);
125
38
        const int low = hex_value(encoded[index + 1]);
126
38
        if (high < 0 || low < 0) {
127
1
            return Status::InvalidArgument("Invalid hexadecimal Iceberg binary default");
128
1
        }
129
37
        (*decoded)[index / 2] = static_cast<char>((high << 4) | low);
130
37
    }
131
3
    return Status::OK();
132
4
}
133
134
2
inline Status decode_json_binary(std::string_view encoded, std::string* decoded) {
135
2
    DORIS_CHECK(decoded != nullptr);
136
2
    const bool is_uuid = encoded.size() == 36 && encoded[8] == '-' && encoded[13] == '-' &&
137
2
                         encoded[18] == '-' && encoded[23] == '-';
138
2
    if (is_uuid) {
139
1
        std::string uuid_hex;
140
1
        uuid_hex.reserve(32);
141
37
        for (size_t index = 0; index < encoded.size(); ++index) {
142
36
            if (index != 8 && index != 13 && index != 18 && index != 23) {
143
32
                uuid_hex.push_back(encoded[index]);
144
32
            }
145
36
        }
146
1
        return decode_hex(uuid_hex, decoded);
147
1
    }
148
1
    return decode_hex(encoded, decoded);
149
2
}
150
151
6
inline std::string json_scalar_text(const rapidjson::Value& value) {
152
6
    if (value.IsString()) {
153
3
        return {value.GetString(), value.GetStringLength()};
154
3
    }
155
3
    rapidjson::StringBuffer buffer;
156
3
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
157
3
    value.Accept(writer);
158
3
    return {buffer.GetString(), buffer.GetSize()};
159
6
}
160
161
8
inline void normalize_timestamp_for_doris(doris::PrimitiveType primitive_type, std::string* value) {
162
8
    if (primitive_type != TYPE_DATETIME && primitive_type != TYPE_DATETIMEV2 &&
163
8
        primitive_type != TYPE_TIMESTAMPTZ) {
164
4
        return;
165
4
    }
166
4
    if (const size_t separator = value->find('T'); separator != std::string::npos) {
167
3
        (*value)[separator] = ' ';
168
3
    }
169
4
    if (primitive_type == TYPE_TIMESTAMPTZ) {
170
1
        return;
171
1
    }
172
3
    if (value->ends_with('Z')) {
173
1
        value->pop_back();
174
1
        return;
175
1
    }
176
2
    const size_t time_start = value->find(' ');
177
2
    if (time_start == std::string::npos) {
178
1
        return;
179
1
    }
180
1
    const size_t offset = value->find_first_of("+-", time_start + 1);
181
1
    if (offset != std::string::npos) {
182
1
        value->erase(offset);
183
1
    }
184
1
}
185
186
inline Status make_null_field(const schema::external::TField& field, const DataTypePtr& data_type,
187
9
                              Field* result) {
188
9
    DORIS_CHECK(data_type != nullptr);
189
9
    DORIS_CHECK(result != nullptr);
190
9
    if (field.__isset.is_optional && !field.is_optional) {
191
1
        return Status::InvalidArgument("Required Iceberg field '{}' has a null default",
192
1
                                       field.name);
193
1
    }
194
8
    if (!data_type->is_nullable()) {
195
1
        return Status::InternalError(
196
1
                "Optional Iceberg field '{}' has a null default, but its Doris type '{}' is not "
197
1
                "nullable",
198
1
                field.name, data_type->get_name());
199
1
    }
200
7
    *result = Field();
201
7
    return Status::OK();
202
8
}
203
204
inline Status build_initial_default_field(const schema::external::TField& field,
205
                                          const DataTypePtr& data_type,
206
                                          std::deque<std::string>* binary_storage, Field* result);
207
208
inline Status build_json_default_field(const schema::external::TField& field,
209
                                       const DataTypePtr& data_type,
210
                                       const rapidjson::Value& json_value,
211
                                       std::deque<std::string>* binary_storage, Field* result);
212
213
inline Status build_json_struct_default(const schema::external::TField& field,
214
                                        const DataTypePtr& value_type,
215
                                        const rapidjson::Value& json_value,
216
5
                                        std::deque<std::string>* binary_storage, Field* result) {
217
5
    if (!json_value.IsObject() || !field.__isset.nestedField ||
218
5
        !field.nestedField.__isset.struct_field || !field.nestedField.struct_field.__isset.fields) {
219
0
        return Status::InvalidArgument("Invalid Iceberg struct default for field '{}'", field.name);
220
0
    }
221
222
5
    const auto& struct_type = assert_cast<const DataTypeStruct&>(*value_type);
223
5
    Struct struct_value;
224
5
    struct_value.reserve(struct_type.get_elements().size());
225
13
    for (size_t index = 0; index < struct_type.get_elements().size(); ++index) {
226
8
        const auto& child_name = struct_type.get_element_name(index);
227
8
        const auto* child = find_struct_child(field.nestedField.struct_field, child_name);
228
8
        if (child == nullptr || !child->__isset.id) {
229
0
            return Status::InvalidArgument(
230
0
                    "Iceberg struct default for field '{}' is missing metadata for projected "
231
0
                    "child '{}'",
232
0
                    field.name, child_name);
233
0
        }
234
235
8
        const std::string child_id = std::to_string(child->id);
236
8
        const auto member = json_value.FindMember(child_id.c_str());
237
8
        Field child_value;
238
8
        if (member == json_value.MemberEnd()) {
239
6
            RETURN_IF_ERROR(build_initial_default_field(*child, struct_type.get_element(index),
240
6
                                                        binary_storage, &child_value));
241
6
        } else {
242
2
            RETURN_IF_ERROR(build_json_default_field(*child, struct_type.get_element(index),
243
2
                                                     member->value, binary_storage, &child_value));
244
2
        }
245
8
        struct_value.push_back(std::move(child_value));
246
8
    }
247
5
    *result = Field::create_field<TYPE_STRUCT>(std::move(struct_value));
248
5
    return Status::OK();
249
5
}
250
251
// The recursive item TField describes the element schema and its field-level default metadata. It
252
// cannot represent a particular list literal's length or per-position values, so the parent
253
// initial-default keeps those values in Iceberg's single-value JSON array.
254
inline Status build_json_array_default(const schema::external::TField& field,
255
                                       const DataTypePtr& value_type,
256
                                       const rapidjson::Value& json_value,
257
2
                                       std::deque<std::string>* binary_storage, Field* result) {
258
2
    if (!json_value.IsArray() || !field.__isset.nestedField ||
259
2
        !field.nestedField.__isset.array_field ||
260
2
        !field.nestedField.array_field.__isset.item_field) {
261
0
        return Status::InvalidArgument("Invalid Iceberg list default for field '{}'", field.name);
262
0
    }
263
2
    const auto* element = get_field_ptr(field.nestedField.array_field.item_field);
264
2
    if (element == nullptr) {
265
0
        return Status::InvalidArgument(
266
0
                "Iceberg list default for field '{}' has incomplete element metadata", field.name);
267
0
    }
268
269
2
    const auto& array_type = assert_cast<const DataTypeArray&>(*value_type);
270
2
    Array array_value;
271
2
    array_value.reserve(json_value.Size());
272
3
    for (const auto& json_element : json_value.GetArray()) {
273
3
        Field element_value;
274
3
        RETURN_IF_ERROR(build_json_default_field(*element, array_type.get_nested_type(),
275
3
                                                 json_element, binary_storage, &element_value));
276
3
        array_value.push_back(std::move(element_value));
277
3
    }
278
2
    *result = Field::create_field<TYPE_ARRAY>(std::move(array_value));
279
2
    return Status::OK();
280
2
}
281
282
// The recursive key/value TFields describe entry schemas and field-level default metadata. They
283
// cannot represent the number, order, or concrete values of map entries, so the parent
284
// initial-default keeps the entries in Iceberg's single-value JSON key/value arrays.
285
inline Status build_json_map_default(const schema::external::TField& field,
286
                                     const DataTypePtr& value_type,
287
                                     const rapidjson::Value& json_value,
288
1
                                     std::deque<std::string>* binary_storage, Field* result) {
289
1
    if (!json_value.IsObject() || !json_value.HasMember("keys") || !json_value["keys"].IsArray() ||
290
1
        !json_value.HasMember("values") || !json_value["values"].IsArray() ||
291
1
        !field.__isset.nestedField || !field.nestedField.__isset.map_field ||
292
1
        !field.nestedField.map_field.__isset.key_field ||
293
1
        !field.nestedField.map_field.__isset.value_field) {
294
0
        return Status::InvalidArgument("Invalid Iceberg map default for field '{}'", field.name);
295
0
    }
296
1
    const auto& keys = json_value["keys"];
297
1
    const auto& values = json_value["values"];
298
1
    if (keys.Size() != values.Size()) {
299
0
        return Status::InvalidArgument(
300
0
                "Iceberg map default for field '{}' has {} keys but {} values", field.name,
301
0
                keys.Size(), values.Size());
302
0
    }
303
304
1
    const auto* key = get_field_ptr(field.nestedField.map_field.key_field);
305
1
    const auto* value = get_field_ptr(field.nestedField.map_field.value_field);
306
1
    if (key == nullptr || value == nullptr) {
307
0
        return Status::InvalidArgument(
308
0
                "Iceberg map default for field '{}' has incomplete key/value metadata", field.name);
309
0
    }
310
311
1
    const auto& map_type = assert_cast<const DataTypeMap&>(*value_type);
312
1
    Array key_fields;
313
1
    Array value_fields;
314
1
    key_fields.reserve(keys.Size());
315
1
    value_fields.reserve(values.Size());
316
2
    for (rapidjson::SizeType index = 0; index < keys.Size(); ++index) {
317
1
        Field key_value;
318
1
        Field mapped_value;
319
1
        RETURN_IF_ERROR(build_json_default_field(*key, map_type.get_key_type(), keys[index],
320
1
                                                 binary_storage, &key_value));
321
1
        RETURN_IF_ERROR(build_json_default_field(*value, map_type.get_value_type(), values[index],
322
1
                                                 binary_storage, &mapped_value));
323
1
        key_fields.push_back(std::move(key_value));
324
1
        value_fields.push_back(std::move(mapped_value));
325
1
    }
326
1
    Map map_value;
327
1
    map_value.push_back(Field::create_field<TYPE_ARRAY>(std::move(key_fields)));
328
1
    map_value.push_back(Field::create_field<TYPE_ARRAY>(std::move(value_fields)));
329
1
    *result = Field::create_field<TYPE_MAP>(std::move(map_value));
330
1
    return Status::OK();
331
1
}
332
333
inline Status build_json_scalar_default(const schema::external::TField& field,
334
                                        const DataTypePtr& value_type,
335
                                        const rapidjson::Value& json_value,
336
6
                                        std::deque<std::string>* binary_storage, Field* result) {
337
6
    const auto primitive_type = value_type->get_primitive_type();
338
6
    std::string serialized_value = json_scalar_text(json_value);
339
6
    const bool binary_like = (field.__isset.initial_default_value_is_base64 &&
340
6
                              field.initial_default_value_is_base64) ||
341
6
                             primitive_type == TYPE_VARBINARY;
342
6
    if (binary_like) {
343
1
        if (!json_value.IsString()) {
344
0
            return Status::InvalidArgument(
345
0
                    "Iceberg binary default for field '{}' is not a JSON string", field.name);
346
0
        }
347
1
        binary_storage->emplace_back();
348
1
        RETURN_IF_ERROR(decode_json_binary(serialized_value, &binary_storage->back()));
349
1
        if (primitive_type == TYPE_VARBINARY) {
350
0
            *result = Field::create_field<TYPE_VARBINARY>(StringView(binary_storage->back()));
351
1
        } else if (is_string_type(primitive_type)) {
352
1
            *result = Field::create_field<TYPE_STRING>(binary_storage->back());
353
1
        } else {
354
0
            return Status::InvalidArgument(
355
0
                    "Iceberg binary default for field '{}' has incompatible Doris type '{}'",
356
0
                    field.name, value_type->get_name());
357
0
        }
358
1
        return Status::OK();
359
1
    }
360
361
5
    if (is_string_type(primitive_type)) {
362
2
        if (!json_value.IsString()) {
363
0
            return Status::InvalidArgument("Iceberg string default for field '{}' is not a string",
364
0
                                           field.name);
365
0
        }
366
2
        *result = Field::create_field<TYPE_STRING>(std::move(serialized_value));
367
2
        return Status::OK();
368
2
    }
369
3
    normalize_timestamp_for_doris(primitive_type, &serialized_value);
370
3
    if (parse_non_finite_default(primitive_type, serialized_value, result)) {
371
0
        return Status::OK();
372
0
    }
373
3
    RETURN_IF_ERROR(value_type->get_serde()->from_fe_string(serialized_value, *result));
374
3
    return Status::OK();
375
3
}
376
377
inline Status build_json_default_field(const schema::external::TField& field,
378
                                       const DataTypePtr& data_type,
379
                                       const rapidjson::Value& json_value,
380
15
                                       std::deque<std::string>* binary_storage, Field* result) {
381
15
    DORIS_CHECK(data_type != nullptr);
382
15
    DORIS_CHECK(binary_storage != nullptr);
383
15
    DORIS_CHECK(result != nullptr);
384
15
    if (json_value.IsNull()) {
385
1
        return make_null_field(field, data_type, result);
386
1
    }
387
388
14
    const auto value_type = remove_nullable(data_type);
389
14
    switch (value_type->get_primitive_type()) {
390
5
    case TYPE_STRUCT:
391
5
        return build_json_struct_default(field, value_type, json_value, binary_storage, result);
392
2
    case TYPE_ARRAY:
393
2
        return build_json_array_default(field, value_type, json_value, binary_storage, result);
394
1
    case TYPE_MAP:
395
1
        return build_json_map_default(field, value_type, json_value, binary_storage, result);
396
6
    default:
397
6
        return build_json_scalar_default(field, value_type, json_value, binary_storage, result);
398
14
    }
399
14
}
400
401
inline Status build_initial_default_field(const schema::external::TField& field,
402
                                          const DataTypePtr& data_type,
403
43
                                          std::deque<std::string>* binary_storage, Field* result) {
404
43
    DORIS_CHECK(data_type != nullptr);
405
43
    DORIS_CHECK(binary_storage != nullptr);
406
43
    DORIS_CHECK(result != nullptr);
407
43
    if (!field.__isset.initial_default_value) {
408
6
        if (field.__isset.is_optional && !field.is_optional) {
409
1
            return Status::InvalidArgument(
410
1
                    "Required Iceberg field '{}' is missing from the data file and has no initial "
411
1
                    "default",
412
1
                    field.name);
413
1
        }
414
5
        return make_null_field(field, data_type, result);
415
6
    }
416
417
37
    const auto value_type = remove_nullable(data_type);
418
37
    const auto primitive_type = value_type->get_primitive_type();
419
37
    if (is_complex_type(primitive_type)) {
420
8
        rapidjson::Document document;
421
8
        document.Parse<rapidjson::kParseFullPrecisionFlag>(field.initial_default_value.data(),
422
8
                                                           field.initial_default_value.size());
423
8
        if (document.HasParseError()) {
424
0
            return Status::InvalidArgument("Invalid Iceberg JSON initial default for field '{}'",
425
0
                                           field.name);
426
0
        }
427
8
        return build_json_default_field(field, data_type, document, binary_storage, result);
428
8
    }
429
430
29
    const bool default_is_base64 = (field.__isset.initial_default_value_is_base64 &&
431
29
                                    field.initial_default_value_is_base64) ||
432
29
                                   primitive_type == TYPE_VARBINARY;
433
29
    if (default_is_base64) {
434
3
        binary_storage->emplace_back();
435
3
        if (!base64_decode(field.initial_default_value, &binary_storage->back())) {
436
0
            return Status::InvalidArgument("Invalid Base64 Iceberg initial default for field '{}'",
437
0
                                           field.name);
438
0
        }
439
3
        if (primitive_type == TYPE_VARBINARY) {
440
2
            *result = Field::create_field<TYPE_VARBINARY>(StringView(binary_storage->back()));
441
2
        } else if (is_string_type(primitive_type)) {
442
1
            *result = Field::create_field<TYPE_STRING>(binary_storage->back());
443
1
        } else {
444
0
            return Status::InvalidArgument(
445
0
                    "Iceberg field '{}' marks its initial default as Base64, but Doris type '{}' "
446
0
                    "cannot contain binary data",
447
0
                    field.name, value_type->get_name());
448
0
        }
449
3
        return Status::OK();
450
3
    }
451
452
26
    if (parse_non_finite_default(primitive_type, field.initial_default_value, result)) {
453
1
        return Status::OK();
454
1
    }
455
25
    RETURN_IF_ERROR(value_type->get_serde()->from_fe_string(field.initial_default_value, *result));
456
25
    return Status::OK();
457
25
}
458
459
} // namespace detail
460
461
// Builds an owned one-row column for an Iceberg field that is absent from an old data file.
462
// Complex values follow Iceberg's JSON single-value encoding. Struct members omitted from the
463
// encoded value are recursively populated from the child field's own initial default.
464
inline Status create_initial_default_column(const schema::external::TField& field,
465
37
                                            const DataTypePtr& data_type, ColumnPtr* result) {
466
37
    DORIS_CHECK(data_type != nullptr);
467
37
    DORIS_CHECK(result != nullptr);
468
469
37
    auto column = data_type->create_column();
470
37
    std::deque<std::string> binary_storage;
471
37
    Field value;
472
37
    RETURN_IF_ERROR(detail::build_initial_default_field(field, data_type, &binary_storage, &value));
473
    // The column copies every String/StringView leaf before binary_storage is destroyed.
474
36
    column->insert(value);
475
476
36
    *result = std::move(column);
477
36
    return Status::OK();
478
37
}
479
480
16
inline ColumnPtr repeat_initial_default_column(const ColumnPtr& default_column, size_t rows) {
481
16
    DORIS_CHECK(default_column);
482
16
    DORIS_CHECK_EQ(default_column->size(), 1);
483
484
16
    auto repeated_column = default_column->clone_empty();
485
16
    repeated_column->insert_many_from(*default_column, 0, rows);
486
16
    return repeated_column;
487
16
}
488
489
inline Status append_initial_default(
490
        const schema::external::TField& field, const DataTypePtr& data_type, size_t rows,
491
        std::unordered_map<int32_t, std::pair<DataTypePtr, ColumnPtr>>* prepared_values,
492
9
        ColumnPtr* destination) {
493
9
    DORIS_CHECK(data_type != nullptr);
494
9
    DORIS_CHECK(prepared_values != nullptr);
495
9
    DORIS_CHECK(destination != nullptr);
496
9
    DORIS_CHECK(field.__isset.id);
497
498
9
    auto prepared_value = prepared_values->find(field.id);
499
9
    if (prepared_value == prepared_values->end()) {
500
8
        ColumnPtr default_column;
501
8
        RETURN_IF_ERROR(create_initial_default_column(field, data_type, &default_column));
502
8
        prepared_value =
503
8
                prepared_values
504
8
                        ->emplace(field.id, std::make_pair(data_type, std::move(default_column)))
505
8
                        .first;
506
8
    } else {
507
        // One Iceberg field ID resolves to one query type. Hold the first DataTypePtr so equivalent
508
        // complex types reconstructed for later Blocks reuse the same prepared value.
509
1
        DORIS_CHECK(prepared_value->second.first->equals(*data_type));
510
1
    }
511
512
9
    auto mutable_destination = IColumn::mutate(std::move(*destination));
513
9
    mutable_destination->insert_many_from(*prepared_value->second.second, 0, rows);
514
9
    *destination = std::move(mutable_destination);
515
9
    return Status::OK();
516
9
}
517
518
} // namespace doris::iceberg