Skip to content

Commit 7f9d5a2

Browse files
Add support for empty objects in config
1 parent 08088ef commit 7f9d5a2

5 files changed

Lines changed: 204 additions & 7 deletions

File tree

configtest/bin/configtest.c

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,111 @@ int main(int argc, char **argv) {
899899
GGL_ERR_OK
900900
);
901901

902+
// Test to ensure an empty map can be written and read
903+
test_insert(
904+
GGL_LIST(GGL_OBJ_BUF(GGL_STR("component15"))),
905+
GGL_OBJ_MAP(GGL_MAP()),
906+
-1,
907+
GGL_ERR_OK
908+
);
909+
test_get(
910+
GGL_LIST(GGL_OBJ_BUF(GGL_STR("component15"))),
911+
GGL_OBJ_MAP(GGL_MAP()),
912+
GGL_ERR_OK
913+
);
914+
915+
// Test to ensure an empty map can be merged into an existing empty map
916+
test_insert(
917+
GGL_LIST(
918+
GGL_OBJ_BUF(GGL_STR("component16")), GGL_OBJ_BUF(GGL_STR("foo"))
919+
),
920+
GGL_OBJ_MAP(GGL_MAP()),
921+
-1,
922+
GGL_ERR_OK
923+
);
924+
test_insert(
925+
GGL_LIST(
926+
GGL_OBJ_BUF(GGL_STR("component16")), GGL_OBJ_BUF(GGL_STR("foo"))
927+
),
928+
GGL_OBJ_MAP(GGL_MAP()),
929+
-1,
930+
GGL_ERR_OK
931+
);
932+
test_get(
933+
GGL_LIST(
934+
GGL_OBJ_BUF(GGL_STR("component16")), GGL_OBJ_BUF(GGL_STR("foo"))
935+
),
936+
GGL_OBJ_MAP(GGL_MAP()),
937+
GGL_ERR_OK
938+
);
939+
940+
// Test to ensure an empty map can be merged into an existing populated map
941+
test_insert(
942+
GGL_LIST(
943+
GGL_OBJ_BUF(GGL_STR("component17")), GGL_OBJ_BUF(GGL_STR("foo"))
944+
),
945+
GGL_OBJ_MAP(GGL_MAP({ GGL_STR("key"), GGL_OBJ_NULL() })),
946+
-1,
947+
GGL_ERR_OK
948+
);
949+
test_insert(
950+
GGL_LIST(
951+
GGL_OBJ_BUF(GGL_STR("component17")), GGL_OBJ_BUF(GGL_STR("foo"))
952+
),
953+
GGL_OBJ_MAP(GGL_MAP()),
954+
-1,
955+
GGL_ERR_OK
956+
);
957+
test_get(
958+
GGL_LIST(
959+
GGL_OBJ_BUF(GGL_STR("component17")), GGL_OBJ_BUF(GGL_STR("foo"))
960+
),
961+
GGL_OBJ_MAP(GGL_MAP({ GGL_STR("key"), GGL_OBJ_NULL() })),
962+
GGL_ERR_OK
963+
);
964+
965+
// Test to ensure an empty map can not be merged into an existing value
966+
test_insert(
967+
GGL_LIST(
968+
GGL_OBJ_BUF(GGL_STR("component18")), GGL_OBJ_BUF(GGL_STR("foo"))
969+
),
970+
GGL_OBJ_MAP(GGL_MAP({ GGL_STR("key"), GGL_OBJ_NULL() })),
971+
-1,
972+
GGL_ERR_OK
973+
);
974+
test_insert(
975+
GGL_LIST(
976+
GGL_OBJ_BUF(GGL_STR("component18")),
977+
GGL_OBJ_BUF(GGL_STR("foo")),
978+
GGL_OBJ_BUF(GGL_STR("key"))
979+
),
980+
GGL_OBJ_MAP(GGL_MAP()),
981+
-1,
982+
GGL_ERR_FAILURE
983+
);
984+
985+
// Test to ensure an value can not be merged into an existing empty map
986+
test_insert(
987+
GGL_LIST(
988+
GGL_OBJ_BUF(GGL_STR("component19")),
989+
GGL_OBJ_BUF(GGL_STR("foo")),
990+
GGL_OBJ_BUF(GGL_STR("key"))
991+
),
992+
GGL_OBJ_MAP(GGL_MAP()),
993+
-1,
994+
GGL_ERR_OK
995+
);
996+
test_insert(
997+
GGL_LIST(
998+
GGL_OBJ_BUF(GGL_STR("component19")),
999+
GGL_OBJ_BUF(GGL_STR("foo")),
1000+
GGL_OBJ_BUF(GGL_STR("key"))
1001+
),
1002+
GGL_OBJ_NULL(),
1003+
-1,
1004+
GGL_ERR_FAILURE
1005+
);
1006+
9021007
// test_insert(
9031008
// GGL_LIST(GGL_OBJ_BUF(GGL_STR("component")),
9041009
// GGL_OBJ_BUF(GGL_STR("bar"))), GGL_OBJ_MAP(GGL_MAP({ GGL_STR("foo"),

docs/design/ggconfigd.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,16 @@ the future).
232232
non-existent keys or maintained for keys which become non-existent.
233233
- B. Have a temp table for pending subscriptions in addition to the active
234234
subscriptions
235+
236+
### Storing empty maps
237+
238+
Empty maps (or empty objects) are valid JSON, so we need to support them. This
239+
is represented in the database when a key has neither a value nor any children.
240+
In other words, if a key doesn't have a value, it is a map/object, and the
241+
number of children is how many fields are in that map/object, which can be zero.
242+
243+
You can't write/merge a value onto an existing empty map/object or vice versa,
244+
just like you can't write/merge a value onto an existing map/object.
245+
246+
Writing an empty map doesn't trigger any update subscription callbacks, because
247+
no values were written to notify on.

ggconfigd/include/ggconfigd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
GglError ggconfig_write_value_at_key(
2020
GglList *key_path, GglBuffer *value, int64_t timestamp
2121
);
22+
GglError ggconfig_write_empty_map(GglList *key_path);
2223
GglError ggconfig_get_value_from_key(GglList *key_path, GglObject *value);
2324
GglError ggconfig_get_key_notification(GglList *key_path, uint32_t handle);
2425
GglError ggconfig_open(void);

ggconfigd/src/db_corebus.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ GglError process_nonmap(
206206
// NOLINTNEXTLINE(misc-no-recursion)
207207
GglError process_map(GglObjVec *key_path, GglMap *the_map, int64_t timestamp) {
208208
GglError error = GGL_ERR_OK;
209+
if (the_map->len == 0) {
210+
GGL_LOGT("Map is empty, merging in.");
211+
return ggconfig_write_empty_map(&key_path->list);
212+
}
209213
for (size_t x = 0; x < the_map->len; x++) {
210214
GglKV *kv = &the_map->pairs[x];
211215
GGL_LOGT("Preparing %zu, %.*s", x, (int) kv->key.len, kv->key.data);

ggconfigd/src/db_interface.c

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,66 @@ static GglError notify_nested_key(GglList *key_path, GglObjVec key_ids) {
649649
return return_err;
650650
}
651651

652+
GglError ggconfig_write_empty_map(GglList *key_path) {
653+
if (config_initialized == false) {
654+
GGL_LOGE("Database not initialized");
655+
return GGL_ERR_FAILURE;
656+
}
657+
658+
sqlite3_exec(config_database, "BEGIN TRANSACTION", NULL, NULL, NULL);
659+
GGL_LOGT(
660+
"Starting transaction to write an empty map to key %s",
661+
print_key_path(key_path)
662+
);
663+
664+
GglObject ids_array[GGL_MAX_OBJECT_DEPTH];
665+
GglObjVec ids = { .list = { .items = ids_array, .len = 0 },
666+
.capacity = GGL_MAX_OBJECT_DEPTH };
667+
int64_t last_key_id;
668+
GglError err = get_key_ids(key_path, &ids);
669+
if (err == GGL_ERR_NOENTRY) {
670+
ids.list.len = 0; // Reset the ids vector to be populated fresh
671+
err = create_key_path(key_path, &ids);
672+
if (err != GGL_ERR_OK) {
673+
sqlite3_exec(config_database, "ROLLBACK", NULL, NULL, NULL);
674+
return err;
675+
}
676+
sqlite3_exec(config_database, "END TRANSACTION", NULL, NULL, NULL);
677+
return GGL_ERR_OK;
678+
}
679+
if (err != GGL_ERR_OK) {
680+
GGL_LOGE(
681+
"Failed to get key ids for key path %s with error %s",
682+
print_key_path(key_path),
683+
ggl_strerror(err)
684+
);
685+
sqlite3_exec(config_database, "ROLLBACK", NULL, NULL, NULL);
686+
return err;
687+
}
688+
689+
last_key_id = ids.list.items[ids.list.len - 1].i64;
690+
691+
bool value_is_present;
692+
err = value_is_present_for_key(last_key_id, &value_is_present);
693+
if (err != GGL_ERR_OK) {
694+
sqlite3_exec(config_database, "ROLLBACK", NULL, NULL, NULL);
695+
return err;
696+
}
697+
if (value_is_present) {
698+
GGL_LOGW(
699+
"Value already present for key %s with id %" PRId64
700+
", so an empty map can not be merged. Failing request.",
701+
print_key_path(key_path),
702+
last_key_id
703+
);
704+
sqlite3_exec(config_database, "ROLLBACK", NULL, NULL, NULL);
705+
return GGL_ERR_FAILURE;
706+
}
707+
708+
sqlite3_exec(config_database, "END TRANSACTION", NULL, NULL, NULL);
709+
return GGL_ERR_OK;
710+
}
711+
652712
GglError ggconfig_write_value_at_key(
653713
GglList *key_path, GglBuffer *value, int64_t timestamp
654714
) {
@@ -729,8 +789,20 @@ GglError ggconfig_write_value_at_key(
729789
return GGL_ERR_FAILURE;
730790
}
731791

732-
// we now know that the key already exists and does not have a child.
733-
// Therefore, it stores a value currently.
792+
bool value_is_present;
793+
err = value_is_present_for_key(last_key_id, &value_is_present);
794+
if (err != GGL_ERR_OK) {
795+
return err;
796+
}
797+
if (!value_is_present) {
798+
GGL_LOGW(
799+
"Key %s with id %" PRId64 " is an empty map, so it can not have a "
800+
"value written to it. Failing request.",
801+
print_key_path(key_path),
802+
last_key_id
803+
);
804+
return GGL_ERR_FAILURE;
805+
}
734806

735807
int64_t existing_timestamp;
736808
err = value_get_timestamp(last_key_id, &existing_timestamp);
@@ -859,15 +931,17 @@ static GglError read_key_recursive(
859931
while (sqlite3_step(read_children_stmt) == SQLITE_ROW) {
860932
children_count++;
861933
}
862-
if (children_count == 0) {
863-
GGL_LOGE("no value or children keys found for key id %" PRId64, key_id);
864-
return GGL_ERR_FAILURE;
865-
}
866-
GGL_LOGD(
934+
GGL_LOGT(
867935
"the number of children keys for key id %" PRId64 " is %zd",
868936
key_id,
869937
children_count
870938
);
939+
if (children_count == 0) {
940+
value->type = GGL_TYPE_MAP;
941+
value->map.len = 0;
942+
GGL_LOGT("value read: empty map for key id %" PRId64, key_id);
943+
return GGL_ERR_OK;
944+
}
871945

872946
// create the kvs for the children
873947
GglKV *kv_buffer = GGL_ALLOCN(alloc, GglKV, children_count);

0 commit comments

Comments
 (0)