From 7a4749fec00b8f160b3d791c90abca2605649f5e Mon Sep 17 00:00:00 2001 From: flpereir Date: Thu, 8 Oct 2026 15:13:54 +0200 Subject: [PATCH] AVRO-4359: [C] Keep field and enum defaults in parsed schemas --- lang/c/src/schema.c | 80 +++++++++++++++++- lang/c/src/schema.h | 9 +- lang/c/tests/CMakeLists.txt | 1 + .../fail/enum_default_not_a_symbol | 4 + lang/c/tests/test_avro_4359.c | 84 +++++++++++++++++++ 5 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 lang/c/tests/schema_tests/fail/enum_default_not_a_symbol create mode 100644 lang/c/tests/test_avro_4359.c diff --git a/lang/c/src/schema.c b/lang/c/src/schema.c index a4d8e9f898a..0df612248a3 100644 --- a/lang/c/src/schema.c +++ b/lang/c/src/schema.c @@ -92,6 +92,9 @@ static int record_free_foreach(int i, struct avro_record_field_t *field, avro_str_free(field->name); avro_schema_decref(field->type); + if (field->default_value) { + json_decref(field->default_value); + } avro_freet(struct avro_record_field_t, field); return ST_DELETE; } @@ -527,6 +530,7 @@ avro_schema_t avro_schema_enum_ns(const char *name, const char *space) avro_freet(struct avro_enum_schema_t, enump); return NULL; } + enump->default_index = -1; avro_schema_init(&enump->obj, AVRO_ENUM); return &enump->obj; } @@ -620,6 +624,7 @@ avro_schema_record_field_append(const avro_schema_t record_schema, new_field->index = record->fields->num_entries; new_field->name = avro_strdup(field_name); new_field->type = avro_schema_incref(field_schema); + new_field->default_value = NULL; st_insert(record->fields, record->fields->num_entries, (st_data_t) new_field); st_insert(record->fields_byname, (st_data_t) new_field->name, @@ -732,6 +737,17 @@ avro_schema_t avro_schema_record_field_get_by_index return val.field->type; } +static struct avro_record_field_t * +record_field_at(const avro_schema_t record, int index) +{ + union { + st_data_t data; + struct avro_record_field_t *field; + } val; + st_lookup(avro_schema_to_record(record)->fields, index, &val.data); + return val.field; +} + avro_schema_t avro_schema_link(avro_schema_t to) { if (!is_avro_named_type(to)) { @@ -1013,6 +1029,15 @@ avro_schema_from_json_t(json_t *json, avro_schema_t *schema, avro_schema_decref(*schema); return field_rval; } + + json_t *json_field_default = + json_object_get(json_field, "default"); + if (json_field_default) { + struct avro_record_field_t *field = + record_field_at(*schema, i); + field->default_value = + json_incref(json_field_default); + } } } break; @@ -1086,6 +1111,25 @@ avro_schema_from_json_t(json_t *json, avro_schema_t *schema, return enum_rval; } } + + json_t *json_default = json_object_get(json, "default"); + if (json_default) { + const char *symbol = + json_string_value(json_default); + int index = -1; + if (symbol) { + index = avro_schema_enum_get_by_name + (*schema, symbol); + } + if (index < 0) { + avro_set_error("Enum default must be " + "one of its symbols"); + avro_schema_decref(*schema); + return EINVAL; + } + avro_schema_to_enum(*schema)->default_index = + index; + } } break; @@ -1330,6 +1374,12 @@ avro_schema_t avro_schema_copy_root(avro_schema_t schema, st_table *named_schema val.field->name, type_copy); avro_schema_decref(type_copy); + if (val.field->default_value) { + struct avro_record_field_t *copy = + record_field_at(new_schema, i); + copy->default_value = json_incref + (val.field->default_value); + } } } break; @@ -1353,6 +1403,8 @@ avro_schema_t avro_schema_copy_root(avro_schema_t schema, st_table *named_schema avro_schema_enum_symbol_append(new_schema, val.sym); } + avro_schema_to_enum(new_schema)->default_index = + enum_schema->default_index; } break; @@ -1685,6 +1737,24 @@ static int write_field(avro_writer_t out, const struct avro_record_field_t *fiel check(rval, avro_write_str(out, field->name)); check(rval, avro_write_str(out, "\",\"type\":")); check(rval, avro_schema_to_json2(field->type, out, parent_namespace)); + if (field->default_value) { + char *default_json = + json_dumps(field->default_value, + JSON_ENCODE_ANY | JSON_COMPACT | + JSON_PRESERVE_ORDER); + if (!default_json) { + avro_set_error("Cannot encode default value as JSON"); + return ENOMEM; + } + rval = avro_write_str(out, ",\"default\":"); + if (rval == 0) { + rval = avro_write_str(out, default_json); + } + free(default_json); + if (rval) { + return rval; + } + } return avro_write_str(out, "}"); } @@ -1749,7 +1819,15 @@ static int write_enum(avro_writer_t out, const struct avro_enum_schema_t *enump, check(rval, avro_write_str(out, val.sym)); check(rval, avro_write_str(out, "\"")); } - return avro_write_str(out, "]}"); + check(rval, avro_write_str(out, "]")); + if (enump->default_index >= 0) { + check(rval, avro_write_str(out, ",\"default\":\"")); + check(rval, avro_write_str(out, + avro_schema_enum_get((avro_schema_t) &enump->obj, + enump->default_index))); + check(rval, avro_write_str(out, "\"")); + } + return avro_write_str(out, "}"); } static int write_fixed(avro_writer_t out, const struct avro_fixed_schema_t *fixed, diff --git a/lang/c/src/schema.h b/lang/c/src/schema.h index 3c99ee630c4..2520e0433e3 100644 --- a/lang/c/src/schema.h +++ b/lang/c/src/schema.h @@ -22,14 +22,15 @@ #include "avro/schema.h" #include "avro_private.h" #include "st.h" +#include "jansson.h" struct avro_record_field_t { int index; char *name; avro_schema_t type; - /* - * TODO: default values - */ + /* Raw JSON "default", or NULL. + * Currently only propagated but unused. */ + json_t *default_value; }; struct avro_record_schema_t { @@ -46,6 +47,8 @@ struct avro_enum_schema_t { char *space; st_table *symbols; st_table *symbols_byname; + /* Index of the "default" symbol, or -1 */ + int default_index; }; struct avro_array_schema_t { diff --git a/lang/c/tests/CMakeLists.txt b/lang/c/tests/CMakeLists.txt index 6b4164fa740..98e67cfb052 100644 --- a/lang/c/tests/CMakeLists.txt +++ b/lang/c/tests/CMakeLists.txt @@ -88,3 +88,4 @@ add_avro_test_checkmem(test_avro_1691) add_avro_test_checkmem(test_avro_1906) add_avro_test_checkmem(test_avro_1904) add_avro_test_checkmem(test_avro_4246) +add_avro_test_checkmem(test_avro_4359) diff --git a/lang/c/tests/schema_tests/fail/enum_default_not_a_symbol b/lang/c/tests/schema_tests/fail/enum_default_not_a_symbol new file mode 100644 index 00000000000..5b8aeae6bd2 --- /dev/null +++ b/lang/c/tests/schema_tests/fail/enum_default_not_a_symbol @@ -0,0 +1,4 @@ +{"type": "enum", + "name": "Suit", + "symbols": ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"], + "default": "JOKER"} diff --git a/lang/c/tests/test_avro_4359.c b/lang/c/tests/test_avro_4359.c new file mode 100644 index 00000000000..f3b70ab9b92 --- /dev/null +++ b/lang/c/tests/test_avro_4359.c @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include + +#include "avro.h" +#include "jansson.h" + +#define try(call, msg) \ + do { \ + if (call) { \ + fprintf(stderr, msg ":\n %s\n", avro_strerror()); \ + return EXIT_FAILURE; \ + } \ + } while (0) + +static const char SCHEMA_JSON[] = +"{ \"type\": \"record\", \"name\": \"R\"," +" \"fields\": [" +" { \"name\": \"i\", \"type\": \"int\", \"default\": 42 }," +" { \"name\": \"e\", \"type\": { \"type\": \"enum\", \"name\": \"E\"," +" \"symbols\": [\"A\", \"B\"], \"default\": \"B\" } }" +" ]" +"}"; + +/* Checks that the defaults are in the JSON the schema is written as */ +static int +check_defaults(avro_schema_t schema) +{ + char buf[1024] = {0}; + avro_writer_t writer = avro_writer_memory(buf, sizeof(buf)); + try(avro_schema_to_json(schema, writer), "Cannot write schema"); + try(avro_write(writer, (void *) "", 1), "Cannot terminate schema"); + avro_writer_free(writer); + + json_t *json = json_loads(buf, 0, NULL); + json_t *fields = json_object_get(json, "fields"); + json_t *field_default = + json_object_get(json_array_get(fields, 0), "default"); + json_t *enum_default = json_object_get + (json_object_get(json_array_get(fields, 1), "type"), "default"); + + int ok = json_integer_value(field_default) == 42 && + json_is_string(enum_default) && + strcmp(json_string_value(enum_default), "B") == 0; + json_decref(json); + if (!ok) { + fprintf(stderr, "Defaults are missing from %s\n", buf); + return EXIT_FAILURE; + } + return 0; +} + +int main(void) +{ + avro_schema_t schema; + try(avro_schema_from_json_literal(SCHEMA_JSON, &schema), + "Cannot parse schema"); + avro_schema_t copy = avro_schema_copy(schema); + + if (check_defaults(schema) || check_defaults(copy)) { + return EXIT_FAILURE; + } + + avro_schema_decref(copy); + avro_schema_decref(schema); + return EXIT_SUCCESS; +}