Skip to content

Commit

Permalink
Merge pull request #576 from aerospike/CLIENT-1983
Browse files Browse the repository at this point in the history
Added Bin class and support for Javascript Maps and the new Bin class…
  • Loading branch information
DomPeliniAerospike authored Sep 19, 2023
2 parents e61fbc5 + be39267 commit 16594cf
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 19 deletions.
8 changes: 8 additions & 0 deletions lib/aerospike.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@ exports.Key = require('./key')
*/
exports.Record = require('./record')

/**
* In the Aerospike database, each record (similar to a row in a relational database) stores
* data using one or more bins (like columns in a relational database).
*
* @summary {@link Bin} class
*/
exports.Bin = require('./bin')

// ========================================================================
// Enumerations
// ========================================================================
Expand Down
60 changes: 60 additions & 0 deletions lib/bin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// *****************************************************************************
// Copyright 2023 Aerospike, Inc.
//
// Licensed 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
//
// http://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.
// *****************************************************************************

'use strict'

/**
* @class Bin
* @classdesc Aerospike Bin
*
* In the Aerospike database, each record (similar to a row in a relational database) stores
* data using one or more bins (like columns in a relational database). The major difference
* between bins and RDBMS columns is that you don't need to define a schema. Each record can
* have multiple bins. Bins accept the data types listed {@link https://docs.aerospike.com/apidocs/nodejs/#toc4__anchor|here}.
*
* For information about these data types and how bins support them, see {@link https://docs.aerospike.com/server/guide/data-types/scalar-data-types|this}.
*
* Although the bin for a given record or object must be typed, bins in different rows do not
* have to be the same type. There are some internal performance optimizations for single-bin namespaces.
*
* @summary Construct a new Aerospike Bin instance.
*
*/
class Bin {
/** @private */
constructor (name, value, mapOrder) {
/**
* Bin name.
*
* @member {String} Bin#name
*/
this.name = name

/**
* Bin value.
*
* @member {Any} Bin#value
*/
if(mapOrder === 1){
this.value = new Map(Object.entries(value))
}
else{
this.value = value
}
}
}

module.exports = Bin
2 changes: 2 additions & 0 deletions src/include/conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ int list_from_jsarray(as_list **list, v8::Local<v8::Array> array,
const LogInfo *log);
int map_from_jsobject(as_map **map, v8::Local<v8::Object> obj,
const LogInfo *log);
int map_from_jsmap(as_map **map, v8::Local<v8::Map> obj,
const LogInfo *log);
int asval_from_jsvalue(as_val **value, v8::Local<v8::Value> v8value,
const LogInfo *log);
int string_from_jsarray(char*** roles, int roles_size, v8::Local<v8::Array> role_array, const LogInfo *log);
Expand Down
9 changes: 8 additions & 1 deletion src/main/map_operations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,14 @@ bool add_map_put_items_op(as_operations *ops, const char *bin,
as_v8_error(log, "Type error: items property should be an Object");
return false;
}
if (map_from_jsobject(&items, v8items.As<Object>(), log) !=
if(v8items->IsMap()){
if (map_from_jsmap(&items, v8items.As<Map>(), log) !=
AS_NODE_PARAM_OK) {
as_v8_error(log, "Type error: items property should be an Object");
return false;
}
}
else if (map_from_jsobject(&items, v8items.As<Object>(), log) !=
AS_NODE_PARAM_OK) {
as_v8_error(log, "Type error: items property should be an Object");
return false;
Expand Down
79 changes: 65 additions & 14 deletions src/main/util/conversions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ extern "C" {
#include <aerospike/as_arraylist_iterator.h>
#include <aerospike/as_boolean.h>
#include <aerospike/as_geojson.h>
#include <aerospike/as_hashmap.h>
#include <aerospike/as_hashmap_iterator.h>
#include <aerospike/as_orderedmap.h>
#include <aerospike/as_pair.h>
#include <aerospike/as_scan.h>
#include <aerospike/as_map.h>
Expand All @@ -61,6 +60,7 @@ using namespace v8;

const char *DoubleType = "Double";
const char *GeoJSONType = "GeoJSON";
const char *BinType = "Bin";

const int64_t MIN_SAFE_INTEGER = -1 * (std::pow(2, 53) - 1);
const int64_t MAX_SAFE_INTEGER = std::pow(2, 53) - 1;
Expand Down Expand Up @@ -620,19 +620,20 @@ as_val *asval_clone(const as_val *val, const LogInfo *log)
break;
}
case AS_MAP: {
as_hashmap *map = (as_hashmap *)as_map_fromval(val);
as_orderedmap *map = (as_orderedmap *)as_map_fromval(val);
clone_val =
as_map_toval((as_map *)as_hashmap_new(as_hashmap_size(map)));
as_hashmap_iterator it;
as_hashmap_iterator_init(&it, map);
while (as_hashmap_iterator_has_next(&it)) {
as_pair *pair = (as_pair *)as_hashmap_iterator_next(&it);
as_map_toval((as_map *)as_orderedmap_new(as_orderedmap_size(map)));
as_orderedmap_iterator it;
as_orderedmap_iterator_init(&it, map);
while (as_orderedmap_iterator_has_next(&it)) {
as_pair *pair = (as_pair *)as_orderedmap_iterator_next(&it);
as_val *orig_key = as_pair_1(pair);
as_val *orig_val = as_pair_2(pair);
as_val *clone_key = asval_clone(orig_key, log);
as_val *clone_mapval = asval_clone(orig_val, log);
as_hashmap_set((as_hashmap *)clone_val, clone_key, clone_mapval);
as_orderedmap_set((as_orderedmap *)clone_val, clone_key, clone_mapval);
}
as_orderedmap_set_flags((as_orderedmap *)clone_val, (as_orderedmap *)val->_.flags);
as_v8_detail(log, "Cloning a map SUCCESS");
break;
}
Expand Down Expand Up @@ -860,12 +861,12 @@ Local<Value> val_to_jsvalue(as_val *val, const LogInfo *log)
}
case AS_MAP: {
Local<Object> jsobj = Nan::New<Object>();
as_hashmap *map = (as_hashmap *)as_map_fromval(val);
as_hashmap_iterator it;
as_hashmap_iterator_init(&it, map);
as_orderedmap *map = (as_orderedmap *)as_map_fromval(val);
as_orderedmap_iterator it;
as_orderedmap_iterator_init(&it, map);

while (as_hashmap_iterator_has_next(&it)) {
as_pair *p = (as_pair *)as_hashmap_iterator_next(&it);
while (as_orderedmap_iterator_has_next(&it)) {
as_pair *p = (as_pair *)as_orderedmap_iterator_next(&it);
as_val *key = as_pair_1(p);
as_val *val = as_pair_2(p);
Nan::Set(jsobj, val_to_jsvalue(key, log), val_to_jsvalue(val, log));
Expand Down Expand Up @@ -1026,6 +1027,11 @@ bool is_geojson_value(Local<Value> value)
return instanceof (value, GeoJSONType);
}

bool is_bin_value(Local<Value> value)
{
return instanceof (value, BinType);
}

char *geojson_as_string(Local<Value> value)
{
Local<Value> strval =
Expand Down Expand Up @@ -1081,6 +1087,31 @@ int map_from_jsobject(as_map **map, Local<Object> obj, const LogInfo *log)
return AS_NODE_PARAM_OK;
}

int map_from_jsmap(as_map **map, Local<Map> obj, const LogInfo *log)
{
const Local<Array> data = obj->AsArray();
const uint32_t capacity = data->Length();
as_v8_detail(log, "Creating new as_orderedmap with capacity %d", capacity);
as_orderedmap *orderedmap = as_orderedmap_new(capacity);
if (orderedmap == NULL) {
as_v8_error(log, "Map allocation failed");
Nan::ThrowError("Map allocation failed");
return AS_NODE_PARAM_ERR;
}
*map = (as_map *)orderedmap;

for (uint32_t i = 0; i < capacity; i = i + 2) {
const Local<Value> name = Nan::Get(data, i).ToLocalChecked();
const Local<Value> value = Nan::Get(data, i+1).ToLocalChecked();
as_val *val = NULL;
if (asval_from_jsvalue(&val, value, log) != AS_NODE_PARAM_OK) {
return AS_NODE_PARAM_ERR;
}
as_stringmap_set(*map, *Nan::Utf8String(name), val);
}
return AS_NODE_PARAM_OK;
}

int asval_from_jsvalue(as_val **value, Local<Value> v8value, const LogInfo *log)
{
if (v8value->IsNull()) {
Expand Down Expand Up @@ -1145,6 +1176,12 @@ int asval_from_jsvalue(as_val **value, Local<Value> v8value, const LogInfo *log)
return AS_NODE_PARAM_ERR;
}
}
else if (v8value->IsMap()) {
if (map_from_jsmap((as_map **)value, v8value.As<Map>(), log) !=
AS_NODE_PARAM_OK) {
return AS_NODE_PARAM_ERR;
}
}
else if (is_geojson_value(v8value)) {
char *jsonstr = geojson_as_string(v8value);
*value = (as_val *)as_geojson_new(jsonstr, true);
Expand Down Expand Up @@ -1242,6 +1279,11 @@ int privileges_from_jsarray(as_privilege*** privileges, int privileges_size, Loc
int recordbins_from_jsobject(as_record *rec, Local<Object> obj,
const LogInfo *log)
{
if (is_bin_value(obj)) {
Local<Object> jsobj = Nan::New<Object>();
Nan::Set(jsobj, Nan::Get(obj, Nan::New("name").ToLocalChecked()).ToLocalChecked(), Nan::Get(obj, Nan::New("value").ToLocalChecked()).ToLocalChecked());
obj = jsobj;
}
const Local<Array> props = Nan::GetOwnPropertyNames(obj).ToLocalChecked();
const uint32_t count = props->Length();
as_record_init(rec, count);
Expand Down Expand Up @@ -1319,6 +1361,15 @@ int recordbins_from_jsobject(as_record *rec, Local<Object> obj,
as_record_set_list(rec, *n, list);
continue;
}
if (value->IsMap()) {
as_map *map;
if (map_from_jsmap(&map, value.As<Map>(), log) !=
AS_NODE_PARAM_OK) {
return AS_NODE_PARAM_ERR;
}
as_record_set_map(rec, *n, map);
continue;
}
if (value->IsObject()) {
as_map *map;
if (map_from_jsobject(&map, value.As<Object>(), log) !=
Expand Down
10 changes: 10 additions & 0 deletions test/maps.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,16 @@ describe('client.operate() - CDT Map operations', function () {
.then(cleanup())
})

it('adds each item from the Map class to the map and returns the size of the map', function () {
console.log(maps.putItems('map', new Map([ ['e', 150,], ['d', 100], ['c', 99]])))
return initState()
.then(createRecord({ map: { a: 1, b: 2, c: 3 } }))
.then(operate(maps.putItems('map', new Map([ ['e', 150], ['d', 100], ['c', 99]]))))
.then(assertResultEql({ map: 4 }))
.then(assertRecordEql({ map: { a: 1, b: 2, c: 99, d: 100 } }))
.then(cleanup())
})

context('with update-only flag', function () {
helper.skipUnlessVersion('>= 4.3.0', this)

Expand Down
38 changes: 34 additions & 4 deletions test/put.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,12 @@ describe('client.put()', function () {
putGetVerify(record, expected, done)
})

it.skip('writes bin with Map value as map and reads it back', function (done) {
it('writes bin with Map value as map and reads it back as an ordered object', function (done) {
const record = {
map: new Map([['a', 1], ['b', 'foo'], ['c', 1.23],
map: new Map([['g', [1, 2, 3]], ['h', { a: 1, b: 2 }], ['j', new Map([['b', 'foo'], ['a', 1]])],
['d', new Double(3.14)], ['e', Buffer.from('bar')], ['f', GeoJSON.Point(103.8, 1.283)],
['g', [1, 2, 3]], ['h', { a: 1, b: 2 }]])
['a', 1], ['b', 'foo'], ['c', 1.23]]
)
}
const expected = {
map: {
Expand All @@ -255,7 +256,36 @@ describe('client.put()', function () {
e: Buffer.from('bar'),
f: '{"type":"Point","coordinates":[103.8,1.283]}',
g: [1, 2, 3],
h: { a: 1, b: 2 }
h: { a: 1, b: 2 },
j: { a: 1, b: 'foo'}
}
}
putGetVerify(record, expected, done)
})

it('writes bin with the Bin class and reads it back as an object', function (done) {
const record = new Aerospike.Bin('map', {
g: [1, 2, 3],
h: { a: 1, b: 2 },
j: new Map([['b', 'foo'], ['a', 1]]),
e: Buffer.from('bar'),
f: '{"type":"Point","coordinates":[103.8,1.283]}',
a: 1,
b: 'foo',
c: 1.23,
d: 3.14
})
const expected = {
map: {
a: 1,
b: 'foo',
c: 1.23,
d: 3.14,
e: Buffer.from('bar'),
f: '{"type":"Point","coordinates":[103.8,1.283]}',
g: [1, 2, 3],
h: { a: 1, b: 2 },
j: { a: 1, b: 'foo'}
}
}
putGetVerify(record, expected, done)
Expand Down

0 comments on commit 16594cf

Please sign in to comment.