diff --git a/common/Makefile.am b/common/Makefile.am index 724805e60..196fd6a5c 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -79,6 +79,7 @@ common_libswsscommon_la_SOURCES = \ common/c-api/zmqserver.cpp \ common/c-api/zmqconsumerstatetable.cpp \ common/c-api/zmqproducerstatetable.cpp \ + common/c-api/table.cpp \ common/performancetimer.cpp common_libswsscommon_la_CXXFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(LIBNL_CFLAGS) $(CODE_COVERAGE_CXXFLAGS) diff --git a/common/c-api/table.cpp b/common/c-api/table.cpp new file mode 100644 index 000000000..af08cbaa8 --- /dev/null +++ b/common/c-api/table.cpp @@ -0,0 +1,71 @@ +#include +#include + +#include "../dbconnector.h" +#include "../table.h" +#include "table.h" +#include "util.h" + +using namespace swss; +using namespace std; + +SWSSTable SWSSTable_new(SWSSDBConnector db, const char *tableName) { + SWSSTry(return (SWSSTable) new Table((DBConnector *)db, string(tableName))); +} + +void SWSSTable_free(SWSSTable tbl) { + SWSSTry(delete (Table *)tbl); +} + +int8_t SWSSTable_get(SWSSTable tbl, const char *key, SWSSFieldValueArray *outValues) { + SWSSTry({ + vector fvs; + bool exists = ((Table *)tbl)->get(string(key), fvs); + if (exists) { + *outValues = makeFieldValueArray(fvs); + return 1; + } else { + return 0; + } + }); +} + +int8_t SWSSTable_hget(SWSSTable tbl, const char *key, const char *field, SWSSString *outValue) { + SWSSTry({ + string s; + bool exists = ((Table *)tbl)->hget(string(key), string(field), s); + if (exists) { + *outValue = makeString(move(s)); + return 1; + } else { + return 0; + } + }); +} + +void SWSSTable_set(SWSSTable tbl, const char *key, SWSSFieldValueArray values) { + SWSSTry({ + vector fvs = takeFieldValueArray(values); + ((Table *)tbl)->set(string(key), fvs); + }); +} + +void SWSSTable_hset(SWSSTable tbl, const char *key, const char *field, SWSSStrRef value) { + SWSSTry({ ((Table *)tbl)->hset(string(key), string(field), takeStrRef(value)); }); +} + +void SWSSTable_del(SWSSTable tbl, const char *key) { + SWSSTry({ ((Table *)tbl)->del(string(key)); }); +} + +void SWSSTable_hdel(SWSSTable tbl, const char *key, const char *field) { + SWSSTry({ ((Table *)tbl)->hdel(string(key), string(field)); }); +} + +SWSSStringArray SWSSTable_getKeys(SWSSTable tbl) { + SWSSTry({ + vector keys; + ((Table *)tbl)->getKeys(keys); + return makeStringArray(move(keys)); + }) +} diff --git a/common/c-api/table.h b/common/c-api/table.h new file mode 100644 index 000000000..0d06e1e7d --- /dev/null +++ b/common/c-api/table.h @@ -0,0 +1,41 @@ +#ifndef SWSS_COMMON_C_API_TABLE_H +#define SWSS_COMMON_C_API_TABLE_H + +#include "dbconnector.h" +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct SWSSTableOpaque *SWSSTable; + +SWSSTable SWSSTable_new(SWSSDBConnector db, const char *tableName); + +void SWSSTable_free(SWSSTable tbl); + +// If the key exists, populates outValues with the table's values and returns 1. +// If the key doesn't exist, returns 0. +int8_t SWSSTable_get(SWSSTable tbl, const char *key, SWSSFieldValueArray *outValues); + +// If the key and field exist, populates outValue with the field's value and returns 1. +// If the key doesn't exist, returns 0. +int8_t SWSSTable_hget(SWSSTable tbl, const char *key, const char *field, SWSSString *outValue); + +void SWSSTable_set(SWSSTable tbl, const char *key, SWSSFieldValueArray values); + +void SWSSTable_hset(SWSSTable tbl, const char *key, const char *field, SWSSStrRef value); + +void SWSSTable_del(SWSSTable tbl, const char *key); + +void SWSSTable_hdel(SWSSTable tbl, const char *key, const char *field); + +SWSSStringArray SWSSTable_getKeys(SWSSTable tbl); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/c-api/util.cpp b/common/c-api/util.cpp index 1dc6cd451..26cc1b0f4 100644 --- a/common/c-api/util.cpp +++ b/common/c-api/util.cpp @@ -31,3 +31,7 @@ void SWSSFieldValueArray_free(SWSSFieldValueArray arr) { void SWSSKeyOpFieldValuesArray_free(SWSSKeyOpFieldValuesArray kfvs) { SWSSTry(delete[] kfvs.data); } + +void SWSSStringArray_free(SWSSStringArray arr) { + SWSSTry(delete[] arr.data); +} diff --git a/common/c-api/util.h b/common/c-api/util.h index 06aeac15e..aa0d66fbe 100644 --- a/common/c-api/util.h +++ b/common/c-api/util.h @@ -19,6 +19,7 @@ typedef struct SWSSStringOpaque *SWSSString; typedef struct SWSSStrRefOpaque *SWSSStrRef; // FFI version of swss::FieldValueTuple +// field should be freed with libc's free() typedef struct { const char *field; SWSSString value; @@ -59,6 +60,13 @@ typedef enum { SWSSSelectResult_SIGNAL = 2, } SWSSSelectResult; +// FFI version of std::vector +// data strings should be freed with libc's free() +typedef struct { + uint64_t len; + const char **data; +} SWSSStringArray; + // data should not include a null terminator SWSSString SWSSString_new(const char *data, uint64_t length); @@ -85,6 +93,10 @@ void SWSSFieldValueArray_free(SWSSFieldValueArray arr); // grained control of ownership). void SWSSKeyOpFieldValuesArray_free(SWSSKeyOpFieldValuesArray kfvs); +// arr.data may be null. This is not recursive - elements must be freed separately (for finer +// grained control of ownership). +void SWSSStringArray_free(SWSSStringArray arr); + #ifdef __cplusplus } #endif @@ -214,6 +226,19 @@ template static inline SWSSKeyOpFieldValuesArray makeKeyOpFieldValuesA return out; } +static inline SWSSStringArray makeStringArray(std::vector &&in) { + const char **data = new const char*[in.size()]; + + size_t i = 0; + for (std::string &s : in) + data[i++] = strdup(s.c_str()); + + SWSSStringArray out; + out.len = (uint64_t)in.size(); + out.data = data; + return out; +} + static inline std::string takeString(SWSSString s) { return std::string(std::move(*((std::string *)s))); } diff --git a/tests/c_api_ut.cpp b/tests/c_api_ut.cpp index ed814607e..90fffdaa2 100644 --- a/tests/c_api_ut.cpp +++ b/tests/c_api_ut.cpp @@ -5,6 +5,7 @@ #include "common/c-api/dbconnector.h" #include "common/c-api/producerstatetable.h" #include "common/c-api/subscriberstatetable.h" +#include "common/c-api/table.h" #include "common/c-api/util.h" #include "common/c-api/zmqclient.h" #include "common/c-api/zmqconsumerstatetable.h" @@ -40,14 +41,18 @@ static void sortKfvs(vector &kfvs) { #define free(x) std::free(const_cast(reinterpret_cast(x))); +static void freeFieldValuesArray(SWSSFieldValueArray arr) { + for (uint64_t i = 0; i < arr.len; i++) { + free(arr.data[i].field); + SWSSString_free(arr.data[i].value); + } + SWSSFieldValueArray_free(arr); +} + static void freeKeyOpFieldValuesArray(SWSSKeyOpFieldValuesArray arr) { for (uint64_t i = 0; i < arr.len; i++) { free(arr.data[i].key); - for (uint64_t j = 0; j < arr.data[i].fieldValues.len; j++) { - free(arr.data[i].fieldValues.data[j].field); - SWSSString_free(arr.data[i].fieldValues.data[j].value); - } - SWSSFieldValueArray_free(arr.data[i].fieldValues); + freeFieldValuesArray(arr.data[i].fieldValues); } SWSSKeyOpFieldValuesArray_free(arr); } @@ -105,6 +110,57 @@ TEST(c_api, DBConnector) { SWSSDBConnector_free(db); } +TEST(c_api, Table) { + clearDB(); + SWSSStringManager sm; + + SWSSDBConnector db = SWSSDBConnector_new_named("TEST_DB", 1000, true); + SWSSTable tbl = SWSSTable_new(db, "mytable"); + + SWSSFieldValueArray fvs; + SWSSString ss; + EXPECT_FALSE(SWSSTable_get(tbl, "mykey", &fvs)); + EXPECT_FALSE(SWSSTable_hget(tbl, "mykey", "myfield", &ss)); + + SWSSStringArray keys = SWSSTable_getKeys(tbl); + EXPECT_EQ(keys.len, 0); + SWSSStringArray_free(keys); + + SWSSTable_hset(tbl, "mykey", "myfield", sm.makeStrRef("myvalue")); + keys = SWSSTable_getKeys(tbl); + ASSERT_EQ(keys.len, 1); + EXPECT_STREQ(keys.data[0], "mykey"); + free(keys.data[0]); + SWSSStringArray_free(keys); + + ASSERT_TRUE(SWSSTable_hget(tbl, "mykey", "myfield", &ss)); + EXPECT_STREQ(SWSSStrRef_c_str((SWSSStrRef)ss), "myvalue"); + SWSSString_free(ss); + + SWSSTable_hdel(tbl, "mykey", "myfield"); + EXPECT_FALSE(SWSSTable_hget(tbl, "mykey", "myfield", &ss)); + + SWSSFieldValueTuple data[2] = {{.field = "myfield1", .value = sm.makeString("myvalue1")}, + {.field = "myfield2", .value = sm.makeString("myvalue2")}}; + fvs.len = 2; + fvs.data = data; + SWSSTable_set(tbl, "mykey", fvs); + + ASSERT_TRUE(SWSSTable_get(tbl, "mykey", &fvs)); + EXPECT_EQ(fvs.len, 2); + EXPECT_STREQ(data[0].field, fvs.data[0].field); + EXPECT_STREQ(data[1].field, fvs.data[1].field); + freeFieldValuesArray(fvs); + + SWSSTable_del(tbl, "mykey"); + keys = SWSSTable_getKeys(tbl); + EXPECT_EQ(keys.len, 0); + SWSSStringArray_free(keys); + + SWSSTable_free(tbl); + SWSSDBConnector_free(db); +} + TEST(c_api, ConsumerProducerStateTables) { clearDB(); SWSSStringManager sm; @@ -119,9 +175,8 @@ TEST(c_api, ConsumerProducerStateTables) { ASSERT_EQ(arr.len, 0); freeKeyOpFieldValuesArray(arr); - SWSSFieldValueTuple data[2] = { - {.field = "myfield1", .value = sm.makeString("myvalue1")}, - {.field = "myfield2", .value = sm.makeString("myvalue2")}}; + SWSSFieldValueTuple data[2] = {{.field = "myfield1", .value = sm.makeString("myvalue1")}, + {.field = "myfield2", .value = sm.makeString("myvalue2")}}; SWSSFieldValueArray values = { .len = 2, .data = data, @@ -154,7 +209,7 @@ TEST(c_api, ConsumerProducerStateTables) { ASSERT_EQ(fieldValues1.size(), 1); EXPECT_EQ(fieldValues1[0].first, "myfield3"); EXPECT_EQ(fieldValues1[0].second, "myvalue3"); - + arr = SWSSConsumerStateTable_pops(cst); EXPECT_EQ(arr.len, 0); freeKeyOpFieldValuesArray(arr); @@ -244,14 +299,16 @@ TEST(c_api, ZmqConsumerProducerStateTable) { // On flag = 0, we use the ZmqProducerStateTable // On flag = 1, we use the ZmqClient directly for (int flag = 0; flag < 2; flag++) { - SWSSFieldValueTuple values_key1_data[2] = {{.field = "myfield1", .value = sm.makeString("myvalue1")}, - {.field = "myfield2", .value = sm.makeString("myvalue2")}}; + SWSSFieldValueTuple values_key1_data[2] = { + {.field = "myfield1", .value = sm.makeString("myvalue1")}, + {.field = "myfield2", .value = sm.makeString("myvalue2")}}; SWSSFieldValueArray values_key1 = { .len = 2, .data = values_key1_data, }; - SWSSFieldValueTuple values_key2_data[1] = {{.field = "myfield3", .value = sm.makeString("myvalue3")}}; + SWSSFieldValueTuple values_key2_data[1] = { + {.field = "myfield3", .value = sm.makeString("myvalue3")}}; SWSSFieldValueArray values_key2 = { .len = 1, .data = values_key2_data,