Skip to content

Commit

Permalink
AVRO-2773: [C] Add get_decimal and set_decimal
Browse files Browse the repository at this point in the history
This patch adds "api level" support for decimal objects: these functions allow
manipulating decimal objects at a higher-level than the underlying bytes and
fixed objects.

Signed-off-by: Sahil Kang <sahil.kang@asilaycomputing.com>
Signed-off-by: Sahil Kang <sahilkang@google.com>
  • Loading branch information
SahilKang committed May 6, 2024
1 parent 3841397 commit 753ee32
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 16 deletions.
23 changes: 23 additions & 0 deletions lang/c/src/avro/value.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,24 @@ struct avro_value_iface {
int (*set_branch)(const avro_value_iface_t *iface,
void *self, int discriminant,
avro_value_t *branch);

/*
* Returns the unscaled value, along with the values to the left and
* right hand sides of the decimal point. Any of unscaled, lhs, and rhs
* may be NULL.
*/
int (*get_decimal)(const avro_value_iface_t *iface,
const void *self, int64_t *unscaled,
int64_t *lhs, uint64_t *rhs);

/*
* Sets the decimal value from either unscaled or lhs and rhs. If
* unscaled is non-NULL, lhs and rhs are ignored. Otherwise, lhs and
* rhs must be non-NULL.
*/
int (*set_decimal)(const avro_value_iface_t *iface,
void *self, const int64_t *unscaled,
const int64_t *lhs, const uint64_t *rhs);
};


Expand Down Expand Up @@ -494,5 +512,10 @@ avro_value_to_json(const avro_value_t *value,
#define avro_value_set_branch(value, discriminant, branch) \
avro_value_call(value, set_branch, EINVAL, discriminant, branch)

#define avro_value_get_decimal(value, unscaled, lhs, rhs) \
avro_value_call(value, get_decimal, EINVAL, unscaled, lhs, rhs)
#define avro_value_set_decimal(value, unscaled, lhs, rhs) \
avro_value_call(value, set_decimal, EINVAL, unscaled, lhs, rhs)

CLOSE_EXTERN
#endif
234 changes: 218 additions & 16 deletions lang/c/src/generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,26 @@ avro_generic_link_set_branch(const avro_value_iface_t *iface,
return avro_value_set_branch(self, discriminant, branch);
}

static int
avro_generic_link_get_decimal(const avro_value_iface_t *iface,
const void *vself, int64_t *unscaled,
int64_t *lhs, uint64_t *rhs)
{
AVRO_UNUSED(iface);
const avro_value_t *self = (const avro_value_t *) vself;
return avro_value_get_decimal(self, unscaled, lhs, rhs);
}

static int
avro_generic_link_set_decimal(const avro_value_iface_t *iface,
void *vself, const int64_t *unscaled,
const int64_t *lhs, const uint64_t *rhs)
{
AVRO_UNUSED(iface);
avro_value_t *self = (avro_value_t *) vself;
return avro_value_set_decimal(self, unscaled, lhs, rhs);
}

static size_t
avro_generic_link_instance_size(const avro_value_iface_t *viface)
{
Expand Down Expand Up @@ -639,7 +659,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_LINK_CLASS =
/* compound setters */
avro_generic_link_append,
avro_generic_link_add,
avro_generic_link_set_branch
avro_generic_link_set_branch,
avro_generic_link_get_decimal,
avro_generic_link_set_decimal,
},
avro_generic_link_instance_size,
avro_generic_link_init,
Expand Down Expand Up @@ -790,7 +812,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_BOOLEAN_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_boolean_instance_size,
avro_generic_boolean_init,
Expand Down Expand Up @@ -959,7 +983,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_BYTES_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_bytes_instance_size,
avro_generic_bytes_init,
Expand Down Expand Up @@ -1102,7 +1128,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_DOUBLE_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_double_instance_size,
avro_generic_double_init,
Expand Down Expand Up @@ -1245,7 +1273,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_FLOAT_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_float_instance_size,
avro_generic_float_init,
Expand Down Expand Up @@ -1388,7 +1418,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_INT_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_int_instance_size,
avro_generic_int_init,
Expand Down Expand Up @@ -1531,7 +1563,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_LONG_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_long_instance_size,
avro_generic_long_init,
Expand Down Expand Up @@ -1670,7 +1704,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_NULL_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_null_instance_size,
avro_generic_null_init,
Expand Down Expand Up @@ -1869,7 +1905,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_STRING_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_string_instance_size,
avro_generic_string_init,
Expand Down Expand Up @@ -2112,7 +2150,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_ARRAY_CLASS =
/* compound setters */
avro_generic_array_append,
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_array_instance_size,
avro_generic_array_init,
Expand Down Expand Up @@ -2306,7 +2346,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_ENUM_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_enum_instance_size,
avro_generic_enum_init,
Expand Down Expand Up @@ -2510,7 +2552,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_FIXED_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_fixed_instance_size,
avro_generic_fixed_init,
Expand Down Expand Up @@ -2768,7 +2812,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_MAP_CLASS =
/* compound setters */
NULL, /* append */
avro_generic_map_add,
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_map_instance_size,
avro_generic_map_init,
Expand Down Expand Up @@ -3081,7 +3127,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_RECORD_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_record_instance_size,
avro_generic_record_init,
Expand Down Expand Up @@ -3474,7 +3522,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_UNION_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
avro_generic_union_set_branch
avro_generic_union_set_branch,
NULL, /* get_decimal */
NULL, /* set_decimal */
},
avro_generic_union_instance_size,
avro_generic_union_init,
Expand Down Expand Up @@ -3722,6 +3772,156 @@ avro_generic_decimal_give(const avro_value_iface_t *viface, void *vself,
return 0;
}

static int64_t bytes2unscaled(const uint8_t *bytes, size_t size)
{
/* At this point, bytes has been validated to fit within int64_t. */
uint64_t unsigned_unscaled = 0;
if ((bytes[0] & 0x80) != 0) {
unsigned_unscaled = UINT64_MAX;
}
for (size_t i = 0; i < size; ++i) {
size_t shift = (size - i - 1) * 8;
unsigned_unscaled &= ~(((uint64_t) 0xFF) << shift);
unsigned_unscaled |= ((uint64_t) bytes[i]) << shift;
}

int64_t signed_unscaled;
if ((bytes[0] & 0x80) != 0) {
signed_unscaled = (~unsigned_unscaled + 1) * -1;
} else {
signed_unscaled = unsigned_unscaled;
}

return signed_unscaled;
}

static void unscaled2bytes(int64_t signed_unscaled, uint8_t *bytes,
size_t size)
{
/* At this point, signed_unscaled has been verified to fit in bytes. */
uint64_t unsigned_unscaled;
if (signed_unscaled < 0) {
unsigned_unscaled = ~(signed_unscaled * -1) + 1;
} else {
unsigned_unscaled = signed_unscaled;
}

for (size_t i = 0; i < size; ++i) {
size_t shift = (size - i - 1) * 8;
bytes[i] = unsigned_unscaled >> shift;
}
}

static size_t uint_pow(size_t base, size_t exponent)
{
size_t value = 1;
while (exponent != 0) {
if (exponent % 2 != 0) {
value *= base;
}

exponent /= 2;
base *= base;
}

return value;
}

static int
avro_generic_decimal_get_decimal(const avro_value_iface_t *viface,
const void *vself, int64_t *unscaled,
int64_t *lhs, uint64_t *rhs)
{
const uint8_t *buf = NULL;
size_t size = 0;
int rval = avro_generic_decimal_get(viface, vself,
(const void **) &buf, &size);
if (rval != 0) {
return rval;
}

if (size > 8) {
return EOVERFLOW;
}

int64_t signed_unscaled = bytes2unscaled(buf, size);
if (unscaled != NULL) {
*unscaled = signed_unscaled;
}

const avro_generic_decimal_value_iface_t *iface =
container_of(viface, avro_generic_decimal_value_iface_t, parent);
size_t scale = avro_schema_decimal_scale(iface->schema);
int64_t denominator = uint_pow(10, scale);

if (lhs != NULL) {
*lhs = signed_unscaled / denominator;
}

if (rhs != NULL) {
int sign = signed_unscaled < 0 ? -1 : 1;
*rhs = (sign * signed_unscaled) % denominator;
}

return 0;
}

static int
avro_generic_decimal_set_decimal(const avro_value_iface_t *viface,
void *vself, const int64_t *unscaled,
const int64_t *lhs, const uint64_t *rhs)
{
const avro_generic_decimal_value_iface_t *iface =
container_of(viface, avro_generic_decimal_value_iface_t, parent);
const avro_schema_t underlying =
avro_schema_logical_underlying(iface->schema);
size_t scale = avro_schema_decimal_scale(iface->schema);
int64_t denominator = uint_pow(10, scale);

int64_t signed_unscaled;
if (unscaled != NULL) {
signed_unscaled = *unscaled;
} else if (lhs != NULL && rhs != NULL) {
signed_unscaled = *lhs * denominator;
if (*lhs < 0) {
signed_unscaled -= *rhs;
} else {
signed_unscaled += *rhs;
}

int sign = *lhs < 0 ? -1 : 1;
if (*lhs != signed_unscaled / denominator) {
return EOVERFLOW;
}
if (*rhs != (sign * signed_unscaled) % denominator) {
return EOVERFLOW;
}
} else {
return EINVAL;
}

size_t min_size = 8;
if (is_avro_fixed(underlying)) {
size_t size = avro_schema_fixed_size(underlying);
if (size < min_size) {
min_size = size;
}
}

uint64_t boundary = 1 << (8 * min_size - 1);
if (signed_unscaled > 0 && signed_unscaled > (boundary - 1)) {
return ERANGE;
}

if (signed_unscaled < 0 && (-1 * signed_unscaled) > boundary) {
return ERANGE;
}

uint8_t buf[8];
unscaled2bytes(signed_unscaled, buf, min_size);
return avro_generic_decimal_set(viface, vself, buf, min_size);
}

static size_t
avro_generic_decimal_instance_size(const avro_value_iface_t *viface)
{
Expand Down Expand Up @@ -3816,7 +4016,9 @@ static avro_generic_value_iface_t AVRO_GENERIC_DECIMAL_CLASS =
/* compound setters */
NULL, /* append */
NULL, /* add */
NULL /* set_branch */
NULL, /* set_branch */
avro_generic_decimal_get_decimal, /* get_decimal */
avro_generic_decimal_set_decimal, /* set_decimal */
},
avro_generic_decimal_instance_size,
avro_generic_decimal_init,
Expand Down

0 comments on commit 753ee32

Please sign in to comment.