array lookup-table implemented + some compiler-warnings fixed

This commit is contained in:
Janmejay Singh 2015-11-02 17:32:04 +05:30
parent 991b2af65d
commit 71740b1683
14 changed files with 270 additions and 44 deletions

View File

@ -1195,7 +1195,7 @@ done:
static long long
var2Number(struct var *r, int *bSuccess)
{
long long n;
long long n = 0;
if(r->datatype == 'S') {
n = es_str2num(r->d.estr, bSuccess);
} else {
@ -1589,7 +1589,7 @@ doRandomGen(struct var *__restrict__ const sourceVal) {
}
long int x = randomNumber();
if (max > MAX_RANDOM_NUMBER) {
dbgprintf("rainerscript: desired random-number range [0 - %ld) is wider than supported limit of [0 - %ld)", max, MAX_RANDOM_NUMBER);
dbgprintf("rainerscript: desired random-number range [0 - %lld) is wider than supported limit of [0 - %d)", max, MAX_RANDOM_NUMBER);
}
return x % max;
}
@ -1614,6 +1614,8 @@ doFuncCall(struct cnffunc *__restrict__ const func, struct var *__restrict__ con
struct funcData_prifilt *pPrifilt;
rsRetVal localRet;
lookup_key_t key;
uint8_t lookup_key_type;
lookup_t *lookup_table;
dbgprintf("rainerscript: executing function id %d\n", func->fID);
switch(func->fID) {
@ -1782,10 +1784,20 @@ doFuncCall(struct cnffunc *__restrict__ const func, struct var *__restrict__ con
break;
}
cnfexprEval(func->expr[1], &r[1], usrptr);
str = (char*) var2CString(&r[1], &bMustFree);
key.k_str = (uchar*)str;
ret->d.estr = lookupKey((lookup_ref_t*)func->funcdata, key);
if(bMustFree) free(str);
lookup_table = ((lookup_ref_t*)func->funcdata)->self;
if (lookup_table != NULL) {
lookup_key_type = lookup_table->key_type;
if (lookup_key_type == LOOKUP_KEY_TYPE_STRING) {
key.k_str = (uchar*) var2CString(&r[1], &bMustFree);
} else if (lookup_key_type == LOOKUP_KEY_TYPE_UINT) {
key.k_uint = var2Number(&r[1], NULL);
bMustFree = 0;
}
ret->d.estr = lookupKey((lookup_ref_t*)func->funcdata, key);
if(bMustFree) free(key.k_str);
} else {
ret->d.estr = es_newStrFromCStr("", 1);
}
varFreeMembers(&r[1]);
break;
default:
@ -3730,7 +3742,7 @@ funcName2ID(es_str_t *fname, unsigned short nParams)
"but is %d.");
} else if(FUNC_NAME("wrap")) {
GENERATE_FUNC_WITH_NARG_RANGE("wrap", 2, 3, CNFFUNC_WRAP,
"number of parameters for wrap() must either be "
"number of parameters for %s() must either be "
"two (operand_string, wrapper) or"
"three (operand_string, wrapper, wrapper_escape_str)"
"but is %d.");

View File

@ -44,7 +44,7 @@ DEFobjCurrIf(errmsg)
DEFobjCurrIf(glbl)
/* forward definitions */
static rsRetVal lookupReadFile(lookup_t *pThis);
static rsRetVal lookupReadFile(lookup_t *pThis, const uchar* name, const uchar* filename);
static void lookupDestruct(lookup_t *pThis);
/* static data */
@ -59,6 +59,11 @@ static struct cnfparamblk modpblk =
modpdescr
};
/* internal data-types */
typedef struct uint32_index_val_s {
uint32_t index;
uchar *val;
} uint32_index_val_t;
/* create a new lookup table object AND include it in our list of
* lookup tables.
@ -98,6 +103,8 @@ lookupRefDestruct(lookup_ref_t *pThis)
{
pthread_rwlock_destroy(&pThis->rwlock);
lookupDestruct(pThis->self);
free(pThis->name);
free(pThis->filename);
free(pThis);
}
@ -115,7 +122,8 @@ destructTable_str(lookup_t *pThis) {
static void
destructTable_arr(lookup_t *pThis) {
free(pThis->table.arr->interned_val_refs);
free(pThis->table.arr);
}
static void
@ -126,6 +134,9 @@ destructTable_sparseArr(lookup_t *pThis) {
static void
lookupDestruct(lookup_t *pThis) {
int i;
if (pThis == NULL) return;
if (pThis->type == STRING_LOOKUP_TABLE) {
destructTable_str(pThis);
} else if (pThis->type == ARRAY_LOOKUP_TABLE) {
@ -135,8 +146,6 @@ lookupDestruct(lookup_t *pThis) {
} else {
assert(0);//destructor is missing for a new lookup-table type
}
free(pThis->name);
free(pThis->filename);
for (i = 0; i < pThis->interned_val_count; i++) {
free(pThis->interned_vals[i]);
}
@ -175,6 +184,13 @@ qs_arrcmp_ustrs(const void *s1, const void *s2)
return ustrcmp(*(uchar**)s1, *(uchar**)s2);
}
static int
qs_arrcmp_uint32_index_val(const void *s1, const void *s2)
{
return ((uint32_index_val_t*)s1)->index - ((uint32_index_val_t*)s2)->index;
}
/* comparison function for bsearch() and string array compare
* this is for the string lookup table type
*/
@ -190,6 +206,11 @@ bs_arrcmp_str(const void *s1, const void *s2)
return strcmp((uchar*)s1, *(uchar**)s2);
}
static inline const char*
defaultVal(lookup_t *pThis) {
return (pThis->nomatch == NULL) ? "" : (const char*) pThis->nomatch;
}
/* lookup_fn for different types of tables */
static es_str_t*
lookupKey_str(lookup_t *pThis, lookup_key_t key) {
@ -197,13 +218,26 @@ lookupKey_str(lookup_t *pThis, lookup_key_t key) {
const char *r;
entry = bsearch(key.k_str, pThis->table.str->entries, pThis->nmemb, sizeof(lookup_string_tab_entry_t), bs_arrcmp_strtab);
if(entry == NULL) {
r = (pThis->nomatch == NULL) ? "" : (const char*) pThis->nomatch;
r = defaultVal(pThis);
} else {
r = (const char*)entry->interned_val_ref;
}
return es_newStrFromCStr(r, strlen(r));
}
static es_str_t*
lookupKey_arr(lookup_t *pThis, lookup_key_t key) {
const char *r;
uint32_t uint_key = key.k_uint;
if (pThis->table.arr->first_key + pThis->nmemb <= uint_key) {
r = defaultVal(pThis);
} else {
r = (char*) pThis->table.arr->interned_val_refs[uint_key - pThis->table.arr->first_key];
}
return es_newStrFromCStr(r, strlen(r));
}
/* builders for different table-types */
static inline rsRetVal
build_StringTable(lookup_t *pThis, struct json_object *jtab) {
@ -229,15 +263,59 @@ build_StringTable(lookup_t *pThis, struct json_object *jtab) {
qsort(pThis->table.str->entries, pThis->nmemb, sizeof(lookup_string_tab_entry_t), qs_arrcmp_strtab);
pThis->lookup = lookupKey_str;
pThis->key_type = LOOKUP_KEY_TYPE_STRING;
finalize_it:
RETiRet;
}
static inline rsRetVal
build_ArrayTable(lookup_t *pThis, struct json_object *jtab) {
build_ArrayTable(lookup_t *pThis, struct json_object *jtab, const uchar *name) {
uint32_t i;
struct json_object *jrow, *jindex, *jvalue;
uchar *value, *canonicalValueRef;
uint32_t prev_index, index;
uint8_t prev_index_set;
uint32_index_val_t *indexes = NULL;
DEFiRet;
prev_index_set = 0;
pThis->table.arr = NULL;
CHKmalloc(indexes = calloc(pThis->nmemb, sizeof(uint32_index_val_t)));
CHKmalloc(pThis->table.arr = calloc(1, sizeof(lookup_array_tab_t)));
CHKmalloc(pThis->table.arr->interned_val_refs = calloc(pThis->nmemb, sizeof(uchar*)));
for(i = 0; i < pThis->nmemb; i++) {
jrow = json_object_array_get_idx(jtab, i);
jindex = json_object_object_get(jrow, "index");
jvalue = json_object_object_get(jrow, "value");
indexes[i].index = (uint32_t) json_object_get_int(jindex);
indexes[i].val = (uchar*) json_object_get_string(jvalue);
}
qsort(indexes, pThis->nmemb, sizeof(uint32_index_val_t), qs_arrcmp_uint32_index_val);
for(i = 0; i < pThis->nmemb; i++) {
index = indexes[i].index;
if (prev_index_set == 0) {
prev_index = index;
prev_index_set = 1;
} else {
if (index != ++prev_index) {
errmsg.LogError(0, RS_RET_INVALID_VALUE, "'array' lookup table name: '%s' has non-contigious values between index '%d' and '%d'",
name, prev_index, index);
ABORT_FINALIZE(RS_RET_INVALID_VALUE);
}
}
canonicalValueRef = *(uchar**) bsearch(indexes[i].val, pThis->interned_vals, pThis->interned_val_count, sizeof(uchar*), bs_arrcmp_str);
assert(canonicalValueRef != NULL);
pThis->table.arr->interned_val_refs[i] = canonicalValueRef;
}
pThis->lookup = lookupKey_arr;
pThis->key_type = LOOKUP_KEY_TYPE_UINT;
finalize_it:
free(indexes);
RETiRet;
}
@ -249,7 +327,7 @@ finalize_it:
}
rsRetVal
lookupBuildTable(lookup_t *pThis, struct json_object *jroot)
lookupBuildTable(lookup_t *pThis, struct json_object *jroot, const uchar* name)
{
struct json_object *jversion, *jnomatch, *jtype, *jtab;
struct json_object *jrow, *jindex, *jvalue;
@ -310,7 +388,7 @@ lookupBuildTable(lookup_t *pThis, struct json_object *jroot)
if (strcmp(table_type, "array") == 0) {
pThis->type = ARRAY_LOOKUP_TABLE;
CHKiRet(build_ArrayTable(pThis, jtab));
CHKiRet(build_ArrayTable(pThis, jtab, name));
} else if (strcmp(table_type, "sparseArray") == 0) {
pThis->type = SPARSE_ARRAY_LOOKUP_TABLE;
CHKiRet(build_SparseArrayTable(pThis, jtab));
@ -336,7 +414,7 @@ lookupFindTable(uchar *name)
lookup_ref_t *curr;
for(curr = loadConf->lu_tabs.root ; curr != NULL ; curr = curr->next) {
if(!ustrcmp(curr->self->name, name))
if(!ustrcmp(curr->name, name))
break;
}
return curr;
@ -359,11 +437,9 @@ lookupReload(lookup_ref_t *pThis)
oldlu = pThis->self;
newlu = NULL;
DBGPRINTF("reload requested for lookup table '%s'\n", oldlu->name);
DBGPRINTF("reload requested for lookup table '%s'\n", pThis->name);
CHKmalloc(newlu = calloc(1, sizeof(lookup_t)));
CHKmalloc(newlu->name = ustrdup(oldlu->name));
CHKmalloc(newlu->filename = ustrdup(oldlu->filename));
CHKiRet(lookupReadFile(newlu));
CHKiRet(lookupReadFile(newlu, pThis->name, pThis->filename));
/* all went well, copy over data members */
pthread_rwlock_wrlock(&pThis->rwlock);
pThis->self = newlu;
@ -372,11 +448,11 @@ finalize_it:
if (iRet != RS_RET_OK) {
errmsg.LogError(0, RS_RET_INTERNAL_ERROR,
"lookup table '%s' could not be reloaded from file '%s'",
oldlu->name, oldlu->filename);
pThis->name, pThis->filename);
lookupDestruct(newlu);
} else {
errmsg.LogError(0, RS_RET_OK, "lookup table '%s' reloaded from file '%s'",
oldlu->name, oldlu->filename);
pThis->name, pThis->filename);
lookupDestruct(oldlu);
}
RETiRet;
@ -420,7 +496,7 @@ lookupKey(lookup_ref_t *pThis, lookup_key_t key)
* will probably have other issues as well...).
*/
static rsRetVal
lookupReadFile(lookup_t *pThis)
lookupReadFile(lookup_t *pThis, const uchar *name, const uchar *filename)
{
struct json_tokener *tokener = NULL;
struct json_object *json = NULL;
@ -433,21 +509,21 @@ lookupReadFile(lookup_t *pThis)
DEFiRet;
if(stat((char*)pThis->filename, &sb) == -1) {
if(stat((char*)filename, &sb) == -1) {
eno = errno;
errmsg.LogError(0, RS_RET_FILE_NOT_FOUND,
"lookup table file '%s' stat failed: %s",
pThis->filename, rs_strerror_r(eno, errStr, sizeof(errStr)));
filename, rs_strerror_r(eno, errStr, sizeof(errStr)));
ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
}
CHKmalloc(iobuf = malloc(sb.st_size));
if((fd = open((const char*) pThis->filename, O_RDONLY)) == -1) {
if((fd = open((const char*) filename, O_RDONLY)) == -1) {
eno = errno;
errmsg.LogError(0, RS_RET_FILE_NOT_FOUND,
"lookup table file '%s' could not be opened: %s",
pThis->filename, rs_strerror_r(eno, errStr, sizeof(errStr)));
filename, rs_strerror_r(eno, errStr, sizeof(errStr)));
ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
}
@ -457,7 +533,7 @@ lookupReadFile(lookup_t *pThis)
eno = errno;
errmsg.LogError(0, RS_RET_READ_ERR,
"lookup table file '%s' read error: %s",
pThis->filename, rs_strerror_r(eno, errStr, sizeof(errStr)));
filename, rs_strerror_r(eno, errStr, sizeof(errStr)));
ABORT_FINALIZE(RS_RET_READ_ERR);
}
@ -465,14 +541,14 @@ lookupReadFile(lookup_t *pThis)
if(json == NULL) {
errmsg.LogError(0, RS_RET_JSON_PARSE_ERR,
"lookup table file '%s' json parsing error",
pThis->filename);
filename);
ABORT_FINALIZE(RS_RET_JSON_PARSE_ERR);
}
free(iobuf); /* early free to sever resources*/
iobuf = NULL; /* make sure no double-free */
/* got json object, now populate our own in-memory structure */
CHKiRet(lookupBuildTable(pThis, json));
CHKiRet(lookupBuildTable(pThis, json, name));
finalize_it:
free(iobuf);
@ -506,22 +582,23 @@ lookupProcessCnf(struct cnfobj *o)
if(!pvals[i].bUsed)
continue;
if(!strcmp(modpblk.descr[i].name, "file")) {
CHKmalloc(lu->self->filename = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
CHKmalloc(lu->filename = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
} else if(!strcmp(modpblk.descr[i].name, "name")) {
CHKmalloc(lu->self->name = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
CHKmalloc(lu->name = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
} else {
dbgprintf("lookup_table: program error, non-handled "
"param '%s'\n", modpblk.descr[i].name);
}
}
CHKiRet(lookupReadFile(lu->self));
DBGPRINTF("lookup table '%s' loaded from file '%s'\n", lu->self->name, lu->self->filename);
CHKiRet(lookupReadFile(lu->self, lu->name, lu->filename));
DBGPRINTF("lookup table '%s' loaded from file '%s'\n", lu->name, lu->filename);
finalize_it:
cnfparamvalsDestruct(pvals, &modpblk);
if (iRet != RS_RET_OK) {
if (lu != NULL) {
lookupRefDestruct(lu);
lookupDestruct(lu->self);
lu->self = NULL;
}
}
RETiRet;

View File

@ -26,6 +26,9 @@
#define ARRAY_LOOKUP_TABLE 2
#define SPARSE_ARRAY_LOOKUP_TABLE 3
#define LOOKUP_KEY_TYPE_STRING 1
#define LOOKUP_KEY_TYPE_UINT 2
struct lookup_tables_s {
lookup_ref_t *root; /* the root of the template list */
lookup_ref_t *last; /* points to the last element of the template list */
@ -33,8 +36,7 @@ struct lookup_tables_s {
struct lookup_array_tab_s {
int first_key;
int table_length;
uchar *interned_val_refs;
uchar **interned_val_refs;
};
struct lookup_sparseArray_tab_entry_s {
@ -58,6 +60,8 @@ struct lookup_string_tab_s {
struct lookup_ref_s {
pthread_rwlock_t rwlock; /* protect us in case of dynamic reloads */
uchar *name;
uchar *filename;
lookup_t *self;
lookup_ref_t *next;
};
@ -66,10 +70,9 @@ typedef es_str_t* (lookup_fn_t)(lookup_t*, lookup_key_t);
/* a single lookup table */
struct lookup_s {
uchar *name;
uchar *filename;
uint32_t nmemb;
uint8_t type;
uint8_t key_type;
union {
lookup_string_tab_t *str;
lookup_array_tab_t *arr;

View File

@ -131,8 +131,8 @@ TESTS += \
incltest_dir_empty_wildcard.sh \
linkedlistqueue.sh \
lookup_table.sh \
key_dereference_on_uninitialized_variable_space.sh
key_dereference_on_uninitialized_variable_space.sh \
array_lookup_table.sh
if HAVE_VALGRIND
TESTS += \
@ -146,7 +146,9 @@ TESTS += \
udp-msgreduc-orgmsg-vg.sh \
tcp-msgreduc-vg.sh \
unused_lookup_table.sh \
lookup_table-vg.sh
lookup_table-vg.sh \
array_lookup_table-vg.sh \
array_lookup_table_misuse-vg.sh
endif # HAVE_VALGRIND
@ -870,6 +872,15 @@ EXTRA_DIST= \
unused_lookup_table.sh \
lookup_table-vg.sh \
testsuites/unused_lookup_table.conf \
array_lookup_table.sh \
array_lookup_table-vg.sh \
array_lookup_table_misuse-vg.sh \
testsuites/array_lookup_table.conf \
testsuites/xlate_array.lkp_tbl \
testsuites/xlate_array_more.lkp_tbl \
testsuites/xlate_array_misuse.lkp_tbl \
testsuites/xlate_array_more_misuse.lkp_tbl \
testsuites/xlate_array_more.lkp_tbl \
cfg.sh
# TODO: re-enable

24
tests/array_lookup_table-vg.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
# added 2015-10-30 by singh.janmejay
# This file is part of the rsyslog project, released under ASL 2.0
echo ===============================================================================
echo \[array_lookup_table-vg.sh\]: test cleanup for array lookup-table and HUP based reloading of it
. $srcdir/diag.sh init
cp $srcdir/testsuites/xlate_array.lkp_tbl $srcdir/xlate_array.lkp_tbl
. $srcdir/diag.sh startup-vg array_lookup_table.conf
. $srcdir/diag.sh injectmsg 0 3
. $srcdir/diag.sh wait-queueempty
. $srcdir/diag.sh content-check "msgnum:00000000: foo_old"
. $srcdir/diag.sh content-check "msgnum:00000001: bar_old"
. $srcdir/diag.sh assert-content-missing "baz"
cp $srcdir/testsuites/xlate_array_more.lkp_tbl $srcdir/xlate_array.lkp_tbl
. $srcdir/diag.sh issue-HUP
. $srcdir/diag.sh injectmsg 0 3
echo doing shutdown
. $srcdir/diag.sh shutdown-when-empty
echo wait on shutdown
. $srcdir/diag.sh wait-shutdown
. $srcdir/diag.sh content-check "msgnum:00000000: foo_new"
. $srcdir/diag.sh content-check "msgnum:00000001: bar_new"
. $srcdir/diag.sh content-check "msgnum:00000002: baz"
. $srcdir/diag.sh exit

24
tests/array_lookup_table.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
# added 2015-10-30 by singh.janmejay
# This file is part of the rsyslog project, released under ASL 2.0
echo ===============================================================================
echo \[array_lookup_table.sh\]: test for array lookup-table and HUP based reloading of it
. $srcdir/diag.sh init
cp $srcdir/testsuites/xlate_array.lkp_tbl $srcdir/xlate_array.lkp_tbl
. $srcdir/diag.sh startup array_lookup_table.conf
. $srcdir/diag.sh injectmsg 0 3
. $srcdir/diag.sh wait-queueempty
. $srcdir/diag.sh content-check "msgnum:00000000: foo_old"
. $srcdir/diag.sh content-check "msgnum:00000001: bar_old"
. $srcdir/diag.sh assert-content-missing "baz"
cp $srcdir/testsuites/xlate_array_more.lkp_tbl $srcdir/xlate_array.lkp_tbl
. $srcdir/diag.sh issue-HUP
. $srcdir/diag.sh injectmsg 0 3
echo doing shutdown
. $srcdir/diag.sh shutdown-when-empty
echo wait on shutdown
. $srcdir/diag.sh wait-shutdown
. $srcdir/diag.sh content-check "msgnum:00000000: foo_new"
. $srcdir/diag.sh content-check "msgnum:00000001: bar_new"
. $srcdir/diag.sh content-check "msgnum:00000002: baz"
. $srcdir/diag.sh exit

View File

@ -0,0 +1,31 @@
#!/bin/bash
# added 2015-10-30 by singh.janmejay
# This file is part of the rsyslog project, released under ASL 2.0
echo ===============================================================================
echo \[array_lookup_table-vg.sh\]: test cleanup for array lookup-table and HUP based reloading of it
. $srcdir/diag.sh init
cp $srcdir/testsuites/xlate_array_misuse.lkp_tbl $srcdir/xlate_array.lkp_tbl
. $srcdir/diag.sh startup-vg array_lookup_table.conf
. $srcdir/diag.sh injectmsg 0 3
. $srcdir/diag.sh wait-queueempty
. $srcdir/diag.sh assert-content-missing "foo"
. $srcdir/diag.sh assert-content-missing "bar"
. $srcdir/diag.sh assert-content-missing "baz"
cp $srcdir/testsuites/xlate_array_more_misuse.lkp_tbl $srcdir/xlate_array.lkp_tbl
. $srcdir/diag.sh issue-HUP
. $srcdir/diag.sh injectmsg 0 3
. $srcdir/diag.sh wait-queueempty
. $srcdir/diag.sh assert-content-missing "foo"
. $srcdir/diag.sh assert-content-missing "bar"
. $srcdir/diag.sh assert-content-missing "baz"
cp $srcdir/testsuites/xlate_array_more.lkp_tbl $srcdir/xlate_array.lkp_tbl
. $srcdir/diag.sh issue-HUP
. $srcdir/diag.sh injectmsg 0 3
echo doing shutdown
. $srcdir/diag.sh shutdown-when-empty
echo wait on shutdown
. $srcdir/diag.sh wait-shutdown
. $srcdir/diag.sh content-check "msgnum:00000000: foo_new"
. $srcdir/diag.sh content-check "msgnum:00000001: bar_new"
. $srcdir/diag.sh content-check "msgnum:00000002: baz"
. $srcdir/diag.sh exit

View File

@ -0,0 +1,11 @@
$IncludeConfig diag-common.conf
lookup_table(name="xlate" file="xlate_array.lkp_tbl")
template(name="outfmt" type="string" string="%msg% %$.lkp%\n")
set $.num = field($msg, 58, 2);
set $.lkp = lookup("xlate", $.num);
action(type="omfile" file="./rsyslog.out.log" template="outfmt")

View File

@ -1,5 +1,5 @@
{
"table":[
{"index":" msgnum:00000000:", "value":"foo_old" },
{"index":" msgnum:00000001:", "value":"bar_old" }]
{"index":" msgnum:00000001:", "value":"bar_old" },
{"index":" msgnum:00000000:", "value":"foo_old" }]
}

View File

@ -0,0 +1,6 @@
{
"type" : "array",
"table":[
{"index": 1, "value":"bar_old" },
{"index": 0, "value":"foo_old" }]
}

View File

@ -0,0 +1,6 @@
{
"type" : "array",
"table":[
{"index": 3, "value":"bar_old" },
{"index": 1, "value":"foo_old" }]
}

View File

@ -0,0 +1,7 @@
{
"type" : "array",
"table":[
{"index": 2, "value":"baz" },
{"index": 0, "value":"foo_new" },
{"index": 1, "value":"bar_new" }]
}

View File

@ -0,0 +1,7 @@
{
"type" : "array",
"table":[
{"index": 2, "value":"baz" },
{"index": 1, "value":"foo_new" },
{"index": 1, "value":"bar_new" }]
}

View File

@ -0,0 +1,7 @@
{
"type" : "array",
"table":[
{"index": 2, "value":"baz" },
{"index": 0, "value":"foo_new" },
{"index": 1, "value":"bar_new" }]
}