<commit_before>/*-------------------------------------------------------------------------
 *
 * tuple_transformer.cpp
 * file description
 *
 * Copyright(c) 2015, CMU
 *
 * /n-store/src/bridge/tuple_transformer.cpp
 *
 *-------------------------------------------------------------------------
 */

#include <iostream>

#include "backend/bridge/dml/tuple/tuple_transformer.h"
#include "backend/common/logger.h"
#include "backend/common/value_peeker.h"
#include "backend/storage/tuple.h"
#include "backend/common/types.h"
#include "backend/bridge/ddl/ddl.h"

#include "access/htup_details.h"
#include "nodes/print.h"

namespace peloton {
namespace bridge {

/**
 * @brief Convert from Datum to Value.
 * @return converted Value.
 */
Value TupleTransformer::GetValue(Datum datum, Oid atttypid) {
  Value value;

  switch (atttypid) {
    case 21:
    {
      int16_t smallint = DatumGetInt16(datum);
      LOG_INFO("%d\n", smallint);
      value = ValueFactory::GetSmallIntValue(smallint);
    }
    break;

    case 23:
    {
      int32_t integer = DatumGetInt32(datum);
      LOG_INFO("%d\n", integer);
      value = ValueFactory::GetIntegerValue(integer);
    }
    break;

    case 20:
    {
      int64_t bigint = DatumGetInt64(datum);
      LOG_INFO("%ld\n", bigint);
      value = ValueFactory::GetBigIntValue(bigint);
    }
    break;

    case 1042:
    {
      char *character = DatumGetCString(datum);
      Pool *data_pool = nullptr;
      LOG_INFO("%s\n", character);
      value = ValueFactory::GetStringValue(character, data_pool);
    }
    break;

    case 1043:
    {
      char * varlen_character = DatumGetCString(datum);
      Pool *data_pool = nullptr;
      LOG_INFO("%s\n", varlen_character);
      value = ValueFactory::GetStringValue(varlen_character,
                                           data_pool);
    }
    break;

    case 1114:
    {
      long int timestamp = DatumGetInt64(datum);
      char *timestamp_cstring = DatumGetCString(datum);
      LOG_INFO("%s\n", timestamp_cstring);
      value = ValueFactory::GetTimestampValue(timestamp);
    }
    break;

    default:
      LOG_ERROR("Unknown atttypeid : %u \n", atttypid);
      break;
  }

  return value;
}

/**
 * @brief Convert from Value to Datum.
 * @return converted Datum.
 */
Datum TupleTransformer::GetDatum(Value value) {
  ValueType value_type;
  Datum datum;

  value_type = value.GetValueType();
  switch (value_type) {

    case 4:
    {
      int16_t smallint = ValuePeeker::PeekSmallInt(value);
      LOG_INFO("%d\n", smallint);
      datum = Int16GetDatum(smallint);
    }
    break;

    case 5:
    {
      int32_t integer = ValuePeeker::PeekInteger(value);
      LOG_INFO("%d\n", integer);
      datum = Int32GetDatum(integer);
    }
    break;

    case 6:
    {
      int64_t bigint = ValuePeeker::PeekBigInt(value);
      LOG_INFO("%ld\n", bigint);
      datum = Int64GetDatum(bigint);
    }
    break;

    case 8:
    {
      double double_precision = ValuePeeker::PeekDouble(value);
      LOG_INFO("%f\n", double_precision);
      datum = Float8GetDatum(double_precision);
    }
    break;

    case 9:
    {
      char *variable_character = (char *) ValuePeeker::PeekObjectValue(value);
      LOG_INFO("%s\n", variable_character);
      datum = CStringGetDatum(variable_character);
    }
    break;

    case 11:
    {
      long int timestamp = ValuePeeker::PeekTimestamp(value);
      datum = Int64GetDatum(timestamp);
      LOG_INFO("%s\n",DatumGetCString(timestamp));
    }
    break;

    default:
      datum = PointerGetDatum(nullptr);
      LOG_INFO("Unrecognized value type : %u\n", value_type);
      break;
  }

  return datum;
}

/**
 * @brief Convert a Postgres tuple into Peloton tuple
 * @param slot Postgres tuple
 * @param schema Peloton scheme of the table to which the tuple belongs
 * @return a Peloton tuple
 */
storage::Tuple *TupleTransformer::GetPelotonTuple(TupleTableSlot *slot,
                                                 const catalog::Schema *schema) {
  assert(slot);

  TupleDesc tuple_desc = slot->tts_tupleDescriptor;
  int natts = tuple_desc->natts;
  bool isnull;

  // Allocate space for a new tuple with given schema
  storage::Tuple *tuple = new storage::Tuple(schema, true);

  // Go over each attribute and convert Datum to Value
  for (oid_t att_itr = 0; att_itr < natts; ++att_itr) {
    Datum attr = slot_getattr(slot, att_itr + 1, &isnull);
    if (isnull)
      continue;

    Form_pg_attribute attribute_info = tuple_desc->attrs[att_itr];
    Oid attribute_type_id = attribute_info->atttypid;

    Value value = GetValue(attr, attribute_type_id);
    tuple->SetValue(att_itr++, value);
  }

  return tuple;
}

/**
 * @brief Convert a Peloton tuple into Postgres tuple slot
 * @param tuple Peloton tuple
 * @return a Postgres tuple
 */
TupleTableSlot *TupleTransformer::GetPostgresTuple(storage::Tuple *tuple,
                                                   TupleDesc tuple_desc) {
  assert(tuple);

  TupleTableSlot *slot = NULL;
  HeapTuple heap_tuple;
  int natts = tuple_desc->natts;
  Datum *datums;
  bool *nulls;

  assert(tuple->GetColumnCount() == natts);

  // Allocate space for datums
  datums = (Datum *) palloc(natts * sizeof(Datum));
  nulls = (bool *) palloc(natts * sizeof(bool));

  // Go over each attribute and convert Value to Datum
  for (oid_t att_itr = 0; att_itr < natts; ++att_itr) {
    Value value = tuple->GetValue(att_itr);
    Datum datum = GetDatum(value);

    datums[att_itr] = datum;
    nulls[att_itr] = tuple->IsNull(att_itr) ? true : false;
  }

  // Construct tuple
  heap_tuple = heap_form_tuple(tuple_desc, datums, nulls);

  // Construct slot
  slot = MakeSingleTupleTableSlot(tuple_desc);

  // Store tuple in slot
  ExecStoreTuple(heap_tuple, slot, InvalidBuffer, true);

  // Clean up
  pfree(datums);
  pfree(nulls);

  return slot;
}

} // namespace bridge
} // namespace peloton
<commit_msg>change tuple transformer to make use enums<commit_after>/*-------------------------------------------------------------------------
 *
 * tuple_transformer.cpp
 * file description
 *
 * Copyright(c) 2015, CMU
 *
 * /n-store/src/bridge/tuple_transformer.cpp
 *
 *-------------------------------------------------------------------------
 */

#include <iostream>

#include "backend/bridge/dml/tuple/tuple_transformer.h"
#include "backend/common/logger.h"
#include "backend/common/value_peeker.h"
#include "backend/storage/tuple.h"
#include "backend/common/types.h"
#include "backend/bridge/ddl/ddl.h"

#include "access/htup_details.h"
#include "nodes/print.h"

namespace peloton {
namespace bridge {

/**
 * @brief Convert from Datum to Value.
 * @return converted Value.
 */
Value TupleTransformer::GetValue(Datum datum, Oid atttypid) {
  Value value;

  switch (atttypid) {
    case POSTGRES_VALUE_TYPE_SMALLINT:
    {
      int16_t smallint = DatumGetInt16(datum);
      LOG_INFO("%d\n", smallint);
      value = ValueFactory::GetSmallIntValue(smallint);
    }
    break;

    case POSTGRES_VALUE_TYPE_INTEGER:
    {
      int32_t integer = DatumGetInt32(datum);
      LOG_INFO("%d\n", integer);
      value = ValueFactory::GetIntegerValue(integer);
    }
    break;

    case POSTGRES_VALUE_TYPE_BIGINT:
    {
      int64_t bigint = DatumGetInt64(datum);
      LOG_INFO("%ld\n", bigint);
      value = ValueFactory::GetBigIntValue(bigint);
    }
    break;

    case POSTGRES_VALUE_TYPE_BPCHAR:
    {
      char *character = DatumGetCString(datum);
      Pool *data_pool = nullptr;
      LOG_INFO("%s\n", character);
      value = ValueFactory::GetStringValue(character, data_pool);
    }
    break;

    case POSTGRES_VALUE_TYPE_VARCHAR2:
    {
      char * varlen_character = DatumGetCString(datum);
      Pool *data_pool = nullptr;
      LOG_INFO("%s\n", varlen_character);
      value = ValueFactory::GetStringValue(varlen_character,
                                           data_pool);
    }
    break;

    case POSTGRES_VALUE_TYPE_TEXT:
    {

    }
    break;

    case POSTGRES_VALUE_TYPE_TIMESTAMPS:
    {
      long int timestamp = DatumGetInt64(datum);
      char *timestamp_cstring = DatumGetCString(datum);
      LOG_INFO("%s\n", timestamp_cstring);
      value = ValueFactory::GetTimestampValue(timestamp);
    }
    break;

    default:
      LOG_ERROR("Unknown atttypeid : %u \n", atttypid);
      break;
  }

  return value;
}

/**
 * @brief Convert from Value to Datum.
 * @return converted Datum.
 */
Datum TupleTransformer::GetDatum(Value value) {
  ValueType value_type;
  Datum datum;

  value_type = value.GetValueType();
  switch (value_type) {

    case VALUE_TYPE_SMALLINT:
    {
      int16_t smallint = ValuePeeker::PeekSmallInt(value);
      LOG_INFO("%d\n", smallint);
      datum = Int16GetDatum(smallint);
    }
    break;

    case VALUE_TYPE_INTEGER:
    {
      int32_t integer = ValuePeeker::PeekInteger(value);
      LOG_INFO("%d\n", integer);
      datum = Int32GetDatum(integer);
    }
    break;

    case VALUE_TYPE_BIGINT:
    {
      int64_t bigint = ValuePeeker::PeekBigInt(value);
      LOG_INFO("%ld\n", bigint);
      datum = Int64GetDatum(bigint);
    }
    break;

    case VALUE_TYPE_DOUBLE:
    {
      double double_precision = ValuePeeker::PeekDouble(value);
      LOG_INFO("%f\n", double_precision);
      datum = Float8GetDatum(double_precision);
    }
    break;

    case VALUE_TYPE_VARCHAR:
    {
      char *variable_character = (char *) ValuePeeker::PeekObjectValue(value);
      LOG_INFO("%s\n", variable_character);
      datum = CStringGetDatum(variable_character);
    }
    break;

    case VALUE_TYPE_TIMESTAMP:
    {
      long int timestamp = ValuePeeker::PeekTimestamp(value);
      datum = Int64GetDatum(timestamp);
      LOG_INFO("%s\n",DatumGetCString(timestamp));
    }
    break;

    default:
      datum = PointerGetDatum(nullptr);
      LOG_INFO("Unrecognized value type : %u\n", value_type);
      break;
  }

  return datum;
}

/**
 * @brief Convert a Postgres tuple into Peloton tuple
 * @param slot Postgres tuple
 * @param schema Peloton scheme of the table to which the tuple belongs
 * @return a Peloton tuple
 */
storage::Tuple *TupleTransformer::GetPelotonTuple(TupleTableSlot *slot,
                                                 const catalog::Schema *schema) {
  assert(slot);

  TupleDesc tuple_desc = slot->tts_tupleDescriptor;
  int natts = tuple_desc->natts;
  bool isnull;

  // Allocate space for a new tuple with given schema
  storage::Tuple *tuple = new storage::Tuple(schema, true);

  // Go over each attribute and convert Datum to Value
  for (oid_t att_itr = 0; att_itr < natts; ++att_itr) {
    Datum attr = slot_getattr(slot, att_itr + 1, &isnull);
    if (isnull)
      continue;

    Form_pg_attribute attribute_info = tuple_desc->attrs[att_itr];
    Oid attribute_type_id = attribute_info->atttypid;

    Value value = GetValue(attr, attribute_type_id);
    tuple->SetValue(att_itr++, value);
  }

  return tuple;
}

/**
 * @brief Convert a Peloton tuple into Postgres tuple slot
 * @param tuple Peloton tuple
 * @return a Postgres tuple
 */
TupleTableSlot *TupleTransformer::GetPostgresTuple(storage::Tuple *tuple,
                                                   TupleDesc tuple_desc) {
  assert(tuple);

  TupleTableSlot *slot = NULL;
  HeapTuple heap_tuple;
  int natts = tuple_desc->natts;
  Datum *datums;
  bool *nulls;

  assert(tuple->GetColumnCount() == natts);

  // Allocate space for datums
  datums = (Datum *) palloc(natts * sizeof(Datum));
  nulls = (bool *) palloc(natts * sizeof(bool));

  // Go over each attribute and convert Value to Datum
  for (oid_t att_itr = 0; att_itr < natts; ++att_itr) {
    Value value = tuple->GetValue(att_itr);
    Datum datum = GetDatum(value);

    datums[att_itr] = datum;
    nulls[att_itr] = tuple->IsNull(att_itr) ? true : false;
  }

  // Construct tuple
  heap_tuple = heap_form_tuple(tuple_desc, datums, nulls);

  // Construct slot
  slot = MakeSingleTupleTableSlot(tuple_desc);

  // Store tuple in slot
  ExecStoreTuple(heap_tuple, slot, InvalidBuffer, true);

  // Clean up
  pfree(datums);
  pfree(nulls);

  return slot;
}

} // namespace bridge
} // namespace peloton
<|endoftext|>


<commit_before>// Copyright 2010-2012 RethinkDB, all rights reserved.
#ifndef CLUSTERING_GENERIC_MULTI_THROTTLING_SERVER_HPP_
#define CLUSTERING_GENERIC_MULTI_THROTTLING_SERVER_HPP_

#include <algorithm>
#include <map>

#include "arch/timing.hpp"
#include "containers/priority_queue.hpp"
#include "clustering/generic/multi_throttling_metadata.hpp"
#include "clustering/generic/registrar.hpp"
#include "rpc/mailbox/typed.hpp"

template <class request_type, class inner_client_business_card_type, class user_data_type, class registrant_type>
class multi_throttling_server_t :
        private repeating_timer_callback_t {
private:
    typedef typename multi_throttling_business_card_t<request_type, inner_client_business_card_type>::server_business_card_t server_business_card_t;
    typedef typename multi_throttling_business_card_t<request_type, inner_client_business_card_type>::client_business_card_t client_business_card_t;

public:
    multi_throttling_server_t(
            mailbox_manager_t *mm,
            user_data_type ud,
            int capacity) :
        mailbox_manager(mm),
        user_data(ud),
        goal_capacity(capacity), total_tickets(capacity), free_tickets(capacity),
        reallocate_timer(reallocate_interval_ms, this),
        registrar(mailbox_manager, this)
        { }

    multi_throttling_business_card_t<request_type, inner_client_business_card_type> get_business_card() {
        return multi_throttling_business_card_t<request_type, inner_client_business_card_type>(
            registrar.get_business_card());
    }

private:
    static const int reallocate_interval_ms = 1000;
    static const int fair_fraction_denom = 5;

    class client_t :
            public intrusive_list_node_t<client_t>,
            private repeating_timer_callback_t {
    public:
        client_t(multi_throttling_server_t *p,
                const client_business_card_t &client_bc) :
            parent(p),
            target_tickets(0), held_tickets(0), in_use_tickets(0),

            time_of_last_qps_sample(get_ticks()),
            requests_since_last_qps_sample(0),
            running_qps_estimate(0),
            qps_sample_timer(reallocate_interval_ms, this),

            give_tickets_addr(client_bc.give_tickets_addr),
            reclaim_tickets_addr(client_bc.reclaim_tickets_addr),

            registrant(p->user_data, client_bc.inner_client_business_card),

            drainer(new auto_drainer_t),

            request_mailbox(new mailbox_t<void(request_type)>(parent->mailbox_manager,
                boost::bind(&client_t::on_request, this, _1))),
            relinquish_tickets_mailbox(new mailbox_t<void(int)>(parent->mailbox_manager,
                boost::bind(&client_t::on_relinquish_tickets, this, _1)))
        {
            send(parent->mailbox_manager, client_bc.intro_addr,
                 server_business_card_t(request_mailbox->get_address(),
                                        relinquish_tickets_mailbox->get_address()));
            parent->clients.push_back(this);
            parent->adjust_total_tickets();
            parent->recompute_allocations();
        }

        ~client_t() {
            parent->clients.remove(this);
            parent->adjust_total_tickets();
            parent->recompute_allocations();
            request_mailbox.reset();
            relinquish_tickets_mailbox.reset();
            drainer.reset();
            guarantee(in_use_tickets == 0);
            parent->return_tickets(held_tickets);
        }

        void give_tickets(int tickets) {
            ASSERT_FINITE_CORO_WAITING;
            held_tickets += tickets;
            coro_t::spawn_sometime(boost::bind(
                &client_t::give_tickets_blocking, this,
                tickets,
                auto_drainer_t::lock_t(drainer.get())));
        }

        void set_target_tickets(int new_target) {
            if (target_tickets > new_target) {
                coro_t::spawn_sometime(boost::bind(
                    &client_t::reclaim_tickets_blocking, this,
                    target_tickets - new_target,
                    auto_drainer_t::lock_t(drainer.get())));
            }
            target_tickets = new_target;
        }

        int get_target_tickets() {
            return target_tickets;
        }

        int get_current_tickets() {
            return held_tickets + in_use_tickets;
        }

        int estimate_qps() {
            ticks_t time_span = std::max<ticks_t>(get_ticks() - time_of_last_qps_sample, 1);
            return running_qps_estimate / 2 +
                requests_since_last_qps_sample * secs_to_ticks(1) / time_span;
        }

    private:
        void on_request(const request_type &request) {
            guarantee(held_tickets > 0);
            held_tickets--;
            in_use_tickets++;
            coro_t::spawn_sometime(boost::bind(
                &client_t::perform_request, this,
                request,
                auto_drainer_t::lock_t(drainer.get())));
        }

        void perform_request(const request_type &request, auto_drainer_t::lock_t keepalive) {
            requests_since_last_qps_sample++;
            try {
                registrant.perform_request(request, keepalive.get_drain_signal());
            } catch (const interrupted_exc_t &) {
                /* ignore */
            }
            in_use_tickets--;
            parent->return_tickets(1);
        }

        void on_relinquish_tickets(int tickets) {
            held_tickets -= tickets;
            parent->return_tickets(tickets);
        }

        void give_tickets_blocking(int tickets, auto_drainer_t::lock_t) {
            send(parent->mailbox_manager, give_tickets_addr, tickets);
        }

        void reclaim_tickets_blocking(int tickets, auto_drainer_t::lock_t) {
            send(parent->mailbox_manager, reclaim_tickets_addr, tickets);
        }

        void on_ring() {
            /* Take a sample of the recent average QPS */
            running_qps_estimate = estimate_qps();
            time_of_last_qps_sample = get_ticks();
            requests_since_last_qps_sample = 0;
        }

        multi_throttling_server_t *parent;

        int target_tickets, held_tickets, in_use_tickets;

        ticks_t time_of_last_qps_sample;
        int requests_since_last_qps_sample;
        int running_qps_estimate;
        repeating_timer_t qps_sample_timer;

        mailbox_addr_t<void(int)> give_tickets_addr;
        mailbox_addr_t<void(int)> reclaim_tickets_addr;

        registrant_type registrant;

        scoped_ptr_t<auto_drainer_t> drainer;

        scoped_ptr_t<mailbox_t<void(request_type)> > request_mailbox;
        scoped_ptr_t<mailbox_t<void(int)> > relinquish_tickets_mailbox;
    };

    void on_ring() {
        recompute_allocations();
    }

    void recompute_allocations() {
        /* We divide the total number of tickets into two pools. The first pool
        is distributed evenly among all the clients. The second pool is
        distributed in proportion to the clients' QPS. */
        int fair_tickets = std::max(static_cast<int>(clients.size()),
                total_tickets / fair_fraction_denom);
        int qps_tickets = total_tickets - fair_tickets;
        int total_qps = 0;
        for (client_t *c = clients.head(); c; c = clients.next(c)) {
            total_qps += c->estimate_qps();
        }
        if (clients.size() == 0) {
            return;
        }
        if (total_qps == 0) {
            /* None of the clients did any queries recently. Not all of the
            tickets will be distributed, but that's OK. */
            total_qps = 1;
        }
        for (client_t *c = clients.head(); c; c = clients.next(c)) {
            /* This math isn't exact, but it's OK if the target tickets of all
            the clients don't add up to `total_tickets`. */
            c->set_target_tickets(fair_tickets / clients.size() +
                                  qps_tickets * c->estimate_qps() / total_qps);
        }
        redistribute_tickets();
    }

    void adjust_total_tickets() {
        /* If new clients connect, we adapt the total_tickets number, rather than
           just leaving it at goal_capacity.
           This serves two purposes:
           1. It makes sure that when a new client connect, we always have some
           free_ticket available to give to that client (note that clients here mean
           cluster nodes, not application clients).
           Otherwise new clients would have to wait until we send a relinquish_tickets
           message to one of the existing clients, then wait until that other client
           returns some of its tickets to us, which we could only then pass on to the
           newly connected client. The result would be a delay until a new client
           could actually process any query which we would like to avoid.
           2. If we have more clients than total_tickets/fair_fraction_denom, we would
           end up assigning 0 tickets to some clients. Those clients could never
           process any query. */

        /* So fair_tickets in recompute_allocations() is at least 1 per client. */
        int per_client_capacity = fair_fraction_denom;
        int new_total_tickets = goal_capacity + clients.size() * per_client_capacity;
        /* Note: This can temporarily make free_tickets negative */
        int diff = new_total_tickets - total_tickets;
        free_tickets += diff;
        total_tickets = new_total_tickets;
    }

    void return_tickets(int tickets) {
        free_tickets += tickets;
        redistribute_tickets();
    }

    void redistribute_tickets() {
        if (free_tickets <= 0 || clients.empty()) {
            return;
        }

        static const int min_chunk_size = ceil_divide(100, static_cast<int>(clients.size()));
        static const int min_reasonable_tickets = 10;

        {
            /* We cannot risk a client disconnecting while we are in here. That would
               invalidate the pointers in tickets_to_give. */
            ASSERT_NO_CORO_WAITING;
            std::map<client_t *, int> tickets_to_give;

            /* First, look for clients with a critically low number of tickets.
               They get priority in tickets. This prevents starvation. */
            std::vector<client_t *> critical_clients;
            critical_clients.reserve(clients.size());
            for (client_t *c = clients.head(); c; c = clients.next(c)) {
                if (c->get_current_tickets() < min_reasonable_tickets
                    && c->get_current_tickets() < c->get_target_tickets()) {
                    critical_clients.push_back(c);
                }
            }
            /* Distribute the available tickets among critical clients, up to a
               gift size of `min_reasonable_tickets`. As a consequence of the
               `ceil_divide()` in here we still set gift_size to 1 even if we don't
               have enough free tickets to give at least 1 to every critical client.
               That way we will at least give something to the first couple
               of clients.*/
            if (!critical_clients.empty()) {
                int gift_size_for_critical_clients = std::min(min_reasonable_tickets,
                        ceil_divide(free_tickets, critical_clients.size()));
                for (auto itr = critical_clients.begin(); itr != critical_clients.end(); ++itr) {
                    int tickets_client_actually_wants =
                        (*itr)->get_target_tickets() - (*itr)->get_current_tickets();
                    int gift_size = std::min(free_tickets,
                        std::min(tickets_client_actually_wants, gift_size_for_critical_clients));
                    free_tickets -= gift_size;
                    tickets_to_give[*itr] += gift_size;
                }
            }

            /* Next, look for clients with a large difference between their target
               number of tickets and their current number of tickets. But if the
               difference is less than `min_chunk_size`, don't send any tickets at all
               to avoid flooding the network with many small ticket updates. */
            priority_queue_t<std::pair<int, client_t *> > needy_clients;
            for (client_t *c = clients.head(); c; c = clients.next(c)) {
                int need_size = c->get_target_tickets() - c->get_current_tickets();
                if (need_size >= min_chunk_size) {
                    needy_clients.push(std::pair<int, client_t *>(need_size, c));
                }
            }
            while (free_tickets >= min_chunk_size && !needy_clients.empty()) {
                std::pair<int, client_t *> neediest = needy_clients.pop();
                free_tickets -= min_chunk_size;
                tickets_to_give[neediest.second] += min_chunk_size;
                neediest.first -= min_chunk_size;
                if (neediest.first >= min_chunk_size) {
                    /* Re-insert the client so it gets more tickets later */
                    needy_clients.push(neediest);
                }
            }

            /* Now actually send the tickets to the clients */
            for (auto itr = tickets_to_give.begin(); itr != tickets_to_give.end(); ++itr) {
                itr->first->give_tickets(itr->second);
            }
        }
    }

    mailbox_manager_t *const mailbox_manager;
    user_data_type user_data;

    intrusive_list_t<client_t> clients;
    int goal_capacity, total_tickets, free_tickets;

    repeating_timer_t reallocate_timer;

    registrar_t<client_business_card_t, multi_throttling_server_t *, client_t> registrar;

private:
    DISABLE_COPYING(multi_throttling_server_t);
};

#endif /* CLUSTERING_GENERIC_MULTI_THROTTLING_SERVER_HPP_ */
<commit_msg>Bug fixes in ticket distribution<commit_after>// Copyright 2010-2012 RethinkDB, all rights reserved.
#ifndef CLUSTERING_GENERIC_MULTI_THROTTLING_SERVER_HPP_
#define CLUSTERING_GENERIC_MULTI_THROTTLING_SERVER_HPP_

#include <algorithm>
#include <map>

#include "arch/timing.hpp"
#include "containers/priority_queue.hpp"
#include "clustering/generic/multi_throttling_metadata.hpp"
#include "clustering/generic/registrar.hpp"
#include "rpc/mailbox/typed.hpp"

template <class request_type, class inner_client_business_card_type, class user_data_type, class registrant_type>
class multi_throttling_server_t :
        private repeating_timer_callback_t {
private:
    typedef typename multi_throttling_business_card_t<request_type, inner_client_business_card_type>::server_business_card_t server_business_card_t;
    typedef typename multi_throttling_business_card_t<request_type, inner_client_business_card_type>::client_business_card_t client_business_card_t;

public:
    multi_throttling_server_t(
            mailbox_manager_t *mm,
            user_data_type ud,
            int capacity) :
        mailbox_manager(mm),
        user_data(ud),
        goal_capacity(capacity), total_tickets(capacity), free_tickets(capacity),
        reallocate_timer(reallocate_interval_ms, this),
        registrar(mailbox_manager, this)
        { }

    multi_throttling_business_card_t<request_type, inner_client_business_card_type> get_business_card() {
        return multi_throttling_business_card_t<request_type, inner_client_business_card_type>(
            registrar.get_business_card());
    }

private:
    static const int reallocate_interval_ms = 1000;
    static const int fair_fraction_denom = 5;

    class client_t :
            public intrusive_list_node_t<client_t>,
            private repeating_timer_callback_t {
    public:
        client_t(multi_throttling_server_t *p,
                const client_business_card_t &client_bc) :
            parent(p),
            target_tickets(0), held_tickets(0), in_use_tickets(0),

            time_of_last_qps_sample(get_ticks()),
            requests_since_last_qps_sample(0),
            running_qps_estimate(0),
            qps_sample_timer(reallocate_interval_ms, this),

            give_tickets_addr(client_bc.give_tickets_addr),
            reclaim_tickets_addr(client_bc.reclaim_tickets_addr),

            registrant(p->user_data, client_bc.inner_client_business_card),

            drainer(new auto_drainer_t),

            request_mailbox(new mailbox_t<void(request_type)>(parent->mailbox_manager,
                boost::bind(&client_t::on_request, this, _1))),
            relinquish_tickets_mailbox(new mailbox_t<void(int)>(parent->mailbox_manager,
                boost::bind(&client_t::on_relinquish_tickets, this, _1)))
        {
            send(parent->mailbox_manager, client_bc.intro_addr,
                 server_business_card_t(request_mailbox->get_address(),
                                        relinquish_tickets_mailbox->get_address()));
            parent->clients.push_back(this);
            parent->adjust_total_tickets();
            parent->recompute_allocations();
        }

        ~client_t() {
            parent->clients.remove(this);
            parent->adjust_total_tickets();
            parent->recompute_allocations();
            request_mailbox.reset();
            relinquish_tickets_mailbox.reset();
            drainer.reset();
            guarantee(in_use_tickets == 0);
            parent->return_tickets(held_tickets);
        }

        void give_tickets(int tickets) {
            ASSERT_FINITE_CORO_WAITING;
            held_tickets += tickets;
            coro_t::spawn_sometime(boost::bind(
                &client_t::give_tickets_blocking, this,
                tickets,
                auto_drainer_t::lock_t(drainer.get())));
        }

        void set_target_tickets(int new_target) {
            if (target_tickets > new_target) {
                coro_t::spawn_sometime(boost::bind(
                    &client_t::reclaim_tickets_blocking, this,
                    target_tickets - new_target,
                    auto_drainer_t::lock_t(drainer.get())));
            }
            target_tickets = new_target;
        }

        int get_target_tickets() {
            return target_tickets;
        }

        int get_current_tickets() {
            return held_tickets + in_use_tickets;
        }

        int estimate_qps() {
            ticks_t time_span = std::max<ticks_t>(get_ticks() - time_of_last_qps_sample, 1);
            return running_qps_estimate / 2 +
                requests_since_last_qps_sample * secs_to_ticks(1) / time_span;
        }

    private:
        void on_request(const request_type &request) {
            guarantee(held_tickets > 0);
            held_tickets--;
            in_use_tickets++;
            coro_t::spawn_sometime(boost::bind(
                &client_t::perform_request, this,
                request,
                auto_drainer_t::lock_t(drainer.get())));
        }

        void perform_request(const request_type &request, auto_drainer_t::lock_t keepalive) {
            requests_since_last_qps_sample++;
            try {
                registrant.perform_request(request, keepalive.get_drain_signal());
            } catch (const interrupted_exc_t &) {
                /* ignore */
            }
            in_use_tickets--;
            parent->return_tickets(1);
        }

        void on_relinquish_tickets(int tickets) {
            held_tickets -= tickets;
            parent->return_tickets(tickets);
        }

        void give_tickets_blocking(int tickets, auto_drainer_t::lock_t) {
            send(parent->mailbox_manager, give_tickets_addr, tickets);
        }

        void reclaim_tickets_blocking(int tickets, auto_drainer_t::lock_t) {
            send(parent->mailbox_manager, reclaim_tickets_addr, tickets);
        }

        void on_ring() {
            /* Take a sample of the recent average QPS */
            running_qps_estimate = estimate_qps();
            time_of_last_qps_sample = get_ticks();
            requests_since_last_qps_sample = 0;
        }

        multi_throttling_server_t *parent;

        int target_tickets, held_tickets, in_use_tickets;

        ticks_t time_of_last_qps_sample;
        int requests_since_last_qps_sample;
        int running_qps_estimate;
        repeating_timer_t qps_sample_timer;

        mailbox_addr_t<void(int)> give_tickets_addr;
        mailbox_addr_t<void(int)> reclaim_tickets_addr;

        registrant_type registrant;

        scoped_ptr_t<auto_drainer_t> drainer;

        scoped_ptr_t<mailbox_t<void(request_type)> > request_mailbox;
        scoped_ptr_t<mailbox_t<void(int)> > relinquish_tickets_mailbox;
    };

    void on_ring() {
        recompute_allocations();
    }

    void recompute_allocations() {
        /* We divide the total number of tickets into two pools. The first pool
        is distributed evenly among all the clients. The second pool is
        distributed in proportion to the clients' QPS. */
        int fair_tickets = std::max(static_cast<int>(clients.size()),
                total_tickets / fair_fraction_denom);
        int qps_tickets = total_tickets - fair_tickets;
        int total_qps = 0;
        for (client_t *c = clients.head(); c; c = clients.next(c)) {
            total_qps += c->estimate_qps();
        }
        if (clients.size() == 0) {
            return;
        }
        if (total_qps == 0) {
            /* None of the clients did any queries recently. Not all of the
            tickets will be distributed, but that's OK. */
            total_qps = 1;
        }
        for (client_t *c = clients.head(); c; c = clients.next(c)) {
            /* This math isn't exact, but it's OK if the target tickets of all
            the clients don't add up to `total_tickets`. */
            c->set_target_tickets(fair_tickets / clients.size() +
                                  qps_tickets * c->estimate_qps() / total_qps);
        }
        redistribute_tickets();
    }

    void adjust_total_tickets() {
        /* If new clients connect, we adapt the total_tickets number, rather than
           just leaving it at goal_capacity.
           This serves two purposes:
           1. It makes sure that when a new client connect, we always have some
           free_ticket available to give to that client (note that clients here mean
           cluster nodes, not application clients).
           Otherwise new clients would have to wait until we send a relinquish_tickets
           message to one of the existing clients, then wait until that other client
           returns some of its tickets to us, which we could only then pass on to the
           newly connected client. The result would be a delay until a new client
           could actually process any query which we would like to avoid.
           2. If we have more clients than total_tickets/fair_fraction_denom, we would
           end up assigning 0 tickets to some clients. Those clients could never
           process any query. */

        /* So fair_tickets in recompute_allocations() is at least 1 per client. */
        int per_client_capacity = fair_fraction_denom;
        int new_total_tickets = goal_capacity + clients.size() * per_client_capacity;
        /* Note: This can temporarily make free_tickets negative */
        int diff = new_total_tickets - total_tickets;
        free_tickets += diff;
        total_tickets = new_total_tickets;
    }

    void return_tickets(int tickets) {
        free_tickets += tickets;
        redistribute_tickets();
    }

    void redistribute_tickets() {
        if (free_tickets <= 0 || clients.empty()) {
            return;
        }

        const int min_chunk_size = ceil_divide(100, static_cast<int>(clients.size()));
        static const int min_reasonable_tickets = 10;

        {
            /* We cannot risk a client disconnecting while we are in here. That would
               invalidate the pointers in tickets_to_give. */
            ASSERT_NO_CORO_WAITING;
            std::map<client_t *, int> tickets_to_give;

            /* First, look for clients with a critically low number of tickets.
               They get priority in tickets. This prevents starvation. */
            std::vector<client_t *> critical_clients;
            critical_clients.reserve(clients.size());
            for (client_t *c = clients.head(); c; c = clients.next(c)) {
                if (c->get_current_tickets() < min_reasonable_tickets
                    && c->get_current_tickets() < c->get_target_tickets()) {
                    critical_clients.push_back(c);
                }
            }
            /* Distribute the available tickets among critical clients, up to a
               gift size of `min_reasonable_tickets`. As a consequence of the
               `ceil_divide()` in here we still set gift_size to 1 even if we don't
               have enough free tickets to give at least 1 to every critical client.
               That way we will at least give something to the first couple
               of clients.*/
            if (!critical_clients.empty()) {
                int gift_size_for_critical_clients = std::min(min_reasonable_tickets,
                        ceil_divide(free_tickets, critical_clients.size()));
                for (auto itr = critical_clients.begin(); itr != critical_clients.end(); ++itr) {
                    int tickets_client_actually_wants = std::max(0,
                        (*itr)->get_target_tickets() - (*itr)->get_current_tickets());
                    int gift_size = std::min(free_tickets,
                        std::min(tickets_client_actually_wants, gift_size_for_critical_clients));
                    free_tickets -= gift_size;
                    tickets_to_give[*itr] += gift_size;
                }
            }

            /* Next, look for clients with a large difference between their target
               number of tickets and their current number of tickets. But if the
               difference is less than `min_chunk_size`, don't send any tickets at all
               to avoid flooding the network with many small ticket updates. */
            priority_queue_t<std::pair<int, client_t *> > needy_clients;
            for (client_t *c = clients.head(); c; c = clients.next(c)) {
                int need_size = c->get_target_tickets()
                        - c->get_current_tickets()
                        - tickets_to_give[c];
                if (need_size >= min_chunk_size) {
                    needy_clients.push(std::pair<int, client_t *>(need_size, c));
                }
            }
            while (free_tickets >= min_chunk_size && !needy_clients.empty()) {
                std::pair<int, client_t *> neediest = needy_clients.pop();
                free_tickets -= min_chunk_size;
                tickets_to_give[neediest.second] += min_chunk_size;
                neediest.first -= min_chunk_size;
                if (neediest.first >= min_chunk_size) {
                    /* Re-insert the client so it gets more tickets later */
                    needy_clients.push(neediest);
                }
            }

            /* Now actually send the tickets to the clients */
            for (auto itr = tickets_to_give.begin(); itr != tickets_to_give.end(); ++itr) {
                if (itr->second > 0) {
                    itr->first->give_tickets(itr->second);
                }
            }
        }
    }

    mailbox_manager_t *const mailbox_manager;
    user_data_type user_data;

    intrusive_list_t<client_t> clients;
    int goal_capacity, total_tickets, free_tickets;

    repeating_timer_t reallocate_timer;

    registrar_t<client_business_card_t, multi_throttling_server_t *, client_t> registrar;

private:
    DISABLE_COPYING(multi_throttling_server_t);
};

#endif /* CLUSTERING_GENERIC_MULTI_THROTTLING_SERVER_HPP_ */
<|endoftext|>


<commit_before>#include "catch.hpp"

#include "libirc/atom.h"

#include "libirc/periodic_table.h"

#include <iostream>
#include <stdexcept>

#ifdef HAVE_ARMA
#include <armadillo>
using arma::vec3;
#elif HAVE_EIGEN3
#include <Eigen3/Eigen/Dense>
using vec3 = Eigen::Vector3d;
#else
#error
#endif

using namespace irc;

TEST_CASE("Test atom and periodic table lookup functions", "[atom]") {

  using namespace atom;

  SECTION("invalid atom") {
    CHECK_THROWS_AS(Atom<vec3>(periodic_table::pt_size + 1), std::logic_error);
  } // SECTION

  SECTION("atom from atomic number") {
    for (size_t i{1}; i < periodic_table::pt_size; i++) {
      REQUIRE(periodic_table::valid_atomic_number(i));

      // Define atom from atomic number
      Atom<vec3> a{i};

      REQUIRE(symbol(a.atomic_number) == periodic_table::pt_symbols[i]);
      REQUIRE(symbol(i) == periodic_table::pt_symbols[i]);
      REQUIRE(mass(a.atomic_number) == Approx(periodic_table::pt_masses[i]));
      REQUIRE(mass(i) == Approx(periodic_table::pt_masses[i]));
      REQUIRE(covalent_radius(a.atomic_number) == Approx(periodic_table::pt_covalent_radii[i]));
      REQUIRE(covalent_radius(i) == Approx(periodic_table::pt_covalent_radii[i]));
      REQUIRE(vdw_radius(a.atomic_number) == Approx(periodic_table::pt_vdv_radii[i]));
      REQUIRE(vdw_radius(i) == Approx(periodic_table::pt_vdv_radii[i]));

      if (i == 1) {
        REQUIRE(is_H(a.atomic_number));
        REQUIRE(is_H(i));
      } else if (i == 7 or i == 8 or i == 9 or i == 15 or i == 16 or
                 i == 17) {
        REQUIRE(is_NOFPSCl(a.atomic_number));
        REQUIRE(is_NOFPSCl(i));
      }
    }
  } // SECTION

  SECTION("atom from atomic symbol") {
    for (size_t i{1}; i < periodic_table::pt_size; i++) {

      REQUIRE(periodic_table::valid_atomic_number(i));

      // Define atom from atomic number
      Atom<vec3> a{periodic_table::pt_symbols[i]};

      REQUIRE(symbol(a.atomic_number) == periodic_table::pt_symbols[i]);
      REQUIRE(mass(a.atomic_number) == Approx(periodic_table::pt_masses[i]));
      REQUIRE(covalent_radius(a.atomic_number) == Approx(periodic_table::pt_covalent_radii[i]));
      REQUIRE(vdw_radius(a.atomic_number) == Approx(periodic_table::pt_vdv_radii[i]));

      // Atom in H-bond
      if (i == 1) {
        REQUIRE(is_H(a.atomic_number));
      } else if (i == 7 or i == 8 or i == 9 or i == 15 or i == 16 or
                 i == 17) {
        REQUIRE(is_NOFPSCl(a.atomic_number));
      }
    }
  } // SECTION
} // TEST_CASE
<commit_msg>Extend atom tests<commit_after>#include "catch.hpp"

#include "libirc/atom.h"

#include "libirc/periodic_table.h"

#include <iostream>
#include <stdexcept>

#ifdef HAVE_ARMA
#include <armadillo>
using arma::vec3;
#elif HAVE_EIGEN3
#include <Eigen3/Eigen/Dense>
using vec3 = Eigen::Vector3d;
#else
#error
#endif

using namespace irc;

TEST_CASE("Test atom and periodic table lookup functions", "[atom]") {

  using namespace atom;

  SECTION("invalid atom") {
    CHECK_THROWS_AS(Atom<vec3>(periodic_table::pt_size + 1), std::logic_error);
  }

  SECTION("atom from atomic number") {
    for (size_t i{1}; i < periodic_table::pt_size; i++) {
      REQUIRE(periodic_table::valid_atomic_number(i));

      const auto a = Atom<vec3>{i};

      REQUIRE(symbol(a.atomic_number) == periodic_table::pt_symbols[i]);
      REQUIRE(symbol(i) == periodic_table::pt_symbols[i]);

      REQUIRE(mass(a.atomic_number) == Approx(periodic_table::pt_masses[i]));
      REQUIRE(mass(i) == Approx(periodic_table::pt_masses[i]));

      REQUIRE(covalent_radius(a.atomic_number) == Approx(periodic_table::pt_covalent_radii[i]));
      REQUIRE(covalent_radius(i) == Approx(periodic_table::pt_covalent_radii[i]));

      REQUIRE(vdw_radius(a.atomic_number) == Approx(periodic_table::pt_vdv_radii[i]));
      REQUIRE(vdw_radius(i) == Approx(periodic_table::pt_vdv_radii[i]));

      if (i == 1) {
        REQUIRE(is_H(a.atomic_number));
        REQUIRE(is_H(i));
      } else if (i == 7 or i == 8 or i == 9 or i == 15 or i == 16 or
                 i == 17) {
        REQUIRE(is_NOFPSCl(a.atomic_number));
        REQUIRE(is_NOFPSCl(i));
      }
    }
  }

  SECTION("atom from atomic symbol") {
    for (size_t i{1}; i < periodic_table::pt_size; i++) {

      REQUIRE(periodic_table::valid_atomic_number(i));

      const auto a = Atom<vec3>{periodic_table::pt_symbols[i]};

      REQUIRE(symbol(a.atomic_number) == periodic_table::pt_symbols[i]);
      REQUIRE(symbol(AtomicNumber(periodic_table::pt_symbols[i])) == periodic_table::pt_symbols[i]);

      REQUIRE(mass(a.atomic_number) == Approx(periodic_table::pt_masses[i]));
      REQUIRE(mass(AtomicNumber(periodic_table::pt_symbols[i])) == Approx(periodic_table::pt_masses[i]));

      REQUIRE(covalent_radius(a.atomic_number) == Approx(periodic_table::pt_covalent_radii[i]));
      REQUIRE(covalent_radius(AtomicNumber(periodic_table::pt_symbols[i])) == Approx(periodic_table::pt_covalent_radii[i]));

      REQUIRE(vdw_radius(a.atomic_number) == Approx(periodic_table::pt_vdv_radii[i]));
      REQUIRE(vdw_radius(AtomicNumber(periodic_table::pt_symbols[i])) == Approx(periodic_table::pt_vdv_radii[i]));

      // Atom in H-bond
      if (i == 1) {
        REQUIRE(is_H(a.atomic_number));
      } else if (i == 7 or i == 8 or i == 9 or i == 15 or i == 16 or
                 i == 17) {
        REQUIRE(is_NOFPSCl(a.atomic_number));
      }
    }
  }
}
<|endoftext|>


<commit_before>#include "editor.hh"

#include "exception.hh"
#include "utils.hh"
#include "register.hh"
#include "register_manager.hh"

#include "utf8_iterator.hh"

#include <array>

namespace Kakoune
{

Editor::Editor(Buffer& buffer)
    : m_buffer(&buffer),
      m_edition_level(0),
      m_selections(buffer)
{
    m_selections.push_back(Selection(buffer.begin(), buffer.begin()));
}

void Editor::erase()
{
    scoped_edition edition(*this);
    for (auto& sel : m_selections)
    {
        m_buffer->erase(sel.begin(), sel.end());
        sel.avoid_eol();
    }
}

static BufferIterator prepare_insert(Buffer& buffer, const Selection& sel,
                                     InsertMode mode)
{
    switch (mode)
    {
    case InsertMode::Insert:
    case InsertMode::Replace:
        return sel.begin();
    case InsertMode::Append:
    {
        // special case for end of lines, append to current line instead
        auto pos = std::max(sel.first(), sel.last());
        if (pos.column() == buffer.line_length(pos.line()) - 1)
            return pos;
        else
            return pos+1;
    }
    case InsertMode::InsertAtLineBegin:
        return buffer.iterator_at_line_begin(sel.begin());
    case InsertMode::AppendAtLineEnd:
        return buffer.iterator_at_line_end(sel.end()-1)-1;
    case InsertMode::InsertAtNextLineBegin:
        return buffer.iterator_at_line_end(sel.end()-1);
    case InsertMode::OpenLineBelow:
    {
        LineCount line = (sel.end() - 1).line();
        buffer.insert(buffer.iterator_at_line_end(line), "\n");
        return buffer.iterator_at_line_begin(line + 1);
    }
    case InsertMode::OpenLineAbove:
    {
        auto pos = buffer.iterator_at_line_begin(sel.begin());
        buffer.insert(pos, "\n");
        return pos;
    }
    }
    assert(false);
    return BufferIterator{};
}

void Editor::insert(const String& string, InsertMode mode)
{
    scoped_edition edition(*this);

    if (mode == InsertMode::Replace)
    {
        // do not call Editor::erase as we do not want to avoid end of lines
        for (auto& sel : m_selections)
            m_buffer->erase(sel.begin(), sel.end());
    }

    for (auto& sel : m_selections)
    {
        BufferIterator pos = prepare_insert(*m_buffer, sel, mode);
        m_buffer->insert(pos, string);
        if (mode == InsertMode::Replace)
        {
            sel.first() = pos;
            sel.last()  = pos + (string.empty() ? 0 : string.length() - 1 );
        }
        sel.avoid_eol();
    }
}

void Editor::insert(const memoryview<String>& strings, InsertMode mode)
{
    scoped_edition edition(*this);
    if (mode == InsertMode::Replace)
    {
        // do not call Editor::erase as we do not want to avoid end of lines
        for (auto& sel : m_selections)
            m_buffer->erase(sel.begin(), sel.end());
    }
    if (strings.empty())
        return;

    for (size_t i = 0; i < selections().size(); ++i)
    {
        auto& sel = m_selections[i];
        BufferIterator pos = prepare_insert(*m_buffer, sel, mode);
        const String& str = strings[std::min(i, strings.size()-1)];
        m_buffer->insert(pos, str);
        if (mode == InsertMode::Replace)
        {
            sel.first() = pos;
            sel.last()  = pos + (str.empty() ? 0 : str.length() - 1);
        }
        sel.avoid_eol();
    }
}

std::vector<String> Editor::selections_content() const
{
    std::vector<String> contents;
    for (auto& sel : m_selections)
        contents.push_back(m_buffer->string(sel.begin(),
                                           sel.end()));
    return contents;
}

static void merge_overlapping(SelectionList& selections)
{
    assert(std::is_sorted(selections.begin(), selections.end(),
                          [](const Selection& lhs, const Selection& rhs)
                          { return lhs.begin() < rhs.begin(); }));
    for (size_t i = 0; i < selections.size()-1;)
    {
        if (overlaps(selections[i], selections[i+1]))
        {
            selections[i].merge_with(selections[i+1]);
            selections.erase(selections.begin() + i + 1);
        }
        else
           ++i;
    }
}

void Editor::move_selections(CharCount offset, SelectMode mode)
{
    assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
    for (auto& sel : m_selections)
    {
        auto last = sel.last();
        auto limit = offset < 0 ? buffer().iterator_at_line_begin(last)
                                : utf8::previous(buffer().iterator_at_line_end(last));
        last = utf8::advance(last, limit, offset);
        sel = Selection(mode == SelectMode::Extend ? sel.first() : last, last);
        sel.avoid_eol();
    }
    merge_overlapping(m_selections);
}

void Editor::move_selections(LineCount offset, SelectMode mode)
{
    assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
    for (auto& sel : m_selections)
    {
        BufferCoord pos = sel.last().coord();
        pos.line += offset;
        BufferIterator last = utf8::finish(m_buffer->iterator_at(pos, true));
        sel = Selection(mode == SelectMode::Extend ? sel.first() : last, last);
        sel.avoid_eol();
    }
    merge_overlapping(m_selections);
}

void Editor::clear_selections()
{
    check_invariant();
    BufferIterator pos = m_selections.back().last();

    if (*pos == '\n' and not pos.is_begin() and *utf8::previous(pos) != '\n')
        pos = utf8::previous(pos);

    Selection sel = Selection(pos, pos);
    m_selections = SelectionList{ std::move(sel) };
}

void Editor::flip_selections()
{
    check_invariant();
    for (auto& sel : m_selections)
        std::swap(sel.first(), sel.last());
}

void Editor::keep_selection(int index)
{
    check_invariant();

    if (index < m_selections.size())
        m_selections = SelectionList{ std::move(m_selections[index]) };
}

void Editor::remove_selection(int index)
{
    check_invariant();

    if (m_selections.size() > 1 and index < m_selections.size())
        m_selections.erase(m_selections.begin() + index);
}

void Editor::select(const BufferIterator& iterator, SelectMode mode)
{
    if (mode == SelectMode::Replace)
        m_selections = SelectionList{ {iterator, iterator} };
    else if (mode == SelectMode::Extend)
    {
        for (auto& sel : m_selections)
            sel.last() = iterator;
        merge_overlapping(m_selections);
    }
    else if (mode == SelectMode::Append)
        assert(false);
}

void Editor::select(SelectionList selections)
{
    if (selections.empty())
        throw runtime_error("no selections");
    m_selections = std::move(selections);
}

void Editor::select(const Selector& selector, SelectMode mode)
{
    check_invariant();

    if (mode == SelectMode::Append)
    {
        auto& sel = m_selections.back();
        Selection res = selector(sel);
        if (res.captures().empty())
            res.captures() = sel.captures();
        m_selections.push_back(res);
    }
    else
    {
        for (auto& sel : m_selections)
        {
            Selection res = selector(sel);
            if (mode == SelectMode::Extend)
                sel.merge_with(res);
            else
                sel = std::move(res);

            if (not res.captures().empty())
                sel.captures() = std::move(res.captures());
        }
    }
    merge_overlapping(m_selections);
}

struct nothing_selected : public runtime_error
{
    nothing_selected() : runtime_error("nothing was selected") {}
};

void Editor::multi_select(const MultiSelector& selector)
{
    check_invariant();

    SelectionList new_selections;
    for (auto& sel : m_selections)
    {
        SelectionList res = selector(sel);
        for (auto& new_sel : res)
        {
            // preserve captures when selectors captures nothing.
            if (new_sel.captures().empty())
                new_selections.emplace_back(new_sel.first(), new_sel.last(),
                                            sel.captures());
            else
                new_selections.push_back(std::move(new_sel));
        }
    }
    if (new_selections.empty())
        throw nothing_selected();
    merge_overlapping(new_selections);
    m_selections = std::move(new_selections);
}

class LastModifiedRangeListener : public BufferChangeListener
{
public:
    LastModifiedRangeListener(Buffer& buffer)
       : m_buffer(buffer)
    { m_buffer.add_change_listener(*this); }

    ~LastModifiedRangeListener()
    { m_buffer.remove_change_listener(*this); }

    void on_insert(const BufferIterator& begin, const BufferIterator& end)
    {
        m_first = begin;
        m_last = utf8::previous(end);
    }

    void on_erase(const BufferIterator& begin, const BufferIterator& end)
    {
        m_first = begin;
        if (m_first >= m_buffer.end())
            m_first = utf8::previous(m_buffer.end());
        m_last = m_first;
    }

    const BufferIterator& first() const { return m_first; }
    const BufferIterator& last() const { return m_last; }

private:
    BufferIterator m_first;
    BufferIterator m_last;
    Buffer& m_buffer;
};

bool Editor::undo()
{
    LastModifiedRangeListener listener(buffer());
    bool res = m_buffer->undo();
    if (res)
        m_selections = SelectionList{ {listener.first(), listener.last()} };
    return res;
}

bool Editor::redo()
{
    LastModifiedRangeListener listener(buffer());
    bool res = m_buffer->redo();
    if (res)
        m_selections = SelectionList{ {listener.first(), listener.last()} };
    return res;
}

void Editor::check_invariant() const
{
    assert(not m_selections.empty());
    m_selections.check_invariant();
}

void Editor::begin_edition()
{
    ++m_edition_level;

    if (m_edition_level == 1)
        m_buffer->begin_undo_group();
}

void Editor::end_edition()
{
    assert(m_edition_level > 0);
    if (m_edition_level == 1)
        m_buffer->end_undo_group();

    --m_edition_level;
}

using utf8_it = utf8::utf8_iterator<BufferIterator, utf8::InvalidBytePolicy::Pass>;

IncrementalInserter::IncrementalInserter(Editor& editor, InsertMode mode)
    : m_editor(editor), m_edition(editor), m_mode(mode)
{
    m_editor.on_incremental_insertion_begin();
    Buffer& buffer = *editor.m_buffer;

    if (mode == InsertMode::Replace)
    {
        for (auto& sel : editor.m_selections)
            buffer.erase(sel.begin(), sel.end());
    }

    for (auto& sel : m_editor.m_selections)
    {
        utf8_it first, last;
        switch (mode)
        {
        case InsertMode::Insert:  first = utf8_it(sel.end()) - 1; last = sel.begin(); break;
        case InsertMode::Replace: first = utf8_it(sel.end()) - 1; last = sel.begin(); break;
        case InsertMode::Append:
        {
            first = sel.begin();
            last  = std::max(sel.first(), sel.last());
            // special case for end of lines, append to current line instead
            auto coord = last.underlying_iterator().coord();
            if (coord.column != buffer.line_length(coord.line) - 1)
                ++last;
            break;
        }

        case InsertMode::OpenLineBelow:
        case InsertMode::AppendAtLineEnd:
            first = utf8_it(buffer.iterator_at_line_end(utf8::previous(sel.end()))) - 1;
            last  = first;
            break;

        case InsertMode::OpenLineAbove:
        case InsertMode::InsertAtLineBegin:
            first = buffer.iterator_at_line_begin(sel.begin());
            if (mode == InsertMode::OpenLineAbove)
                --first;
            else
            {
                auto first_non_blank = first;
                while (*first_non_blank == ' ' or *first_non_blank == '\t')
                    ++first_non_blank;
                if (*first_non_blank != '\n')
                    first = first_non_blank;
            }
            last = first;
            break;
        }
        if (first.underlying_iterator().is_end())
           --first;
        if (last.underlying_iterator().is_end())
           --last;
        sel.first() = first.underlying_iterator();
        sel.last()  = last.underlying_iterator();
    }
    if (mode == InsertMode::OpenLineBelow or mode == InsertMode::OpenLineAbove)
    {
        insert("\n");
        if (mode == InsertMode::OpenLineAbove)
        {
            for (auto& sel : m_editor.m_selections)
            {
                // special case, the --first line above did nothing, so we need to compensate now
                if (sel.first() == utf8::next(buffer.begin()))
                    sel.first() = sel.last() = buffer.begin();
            }
        }
    }
}

IncrementalInserter::~IncrementalInserter()
{
    for (auto& sel : m_editor.m_selections)
    {
        if (m_mode == InsertMode::Append and sel.last().column() > 0)
            sel.last() = utf8::previous(sel.last());
         sel.avoid_eol();
    }

    m_editor.on_incremental_insertion_end();
}

void IncrementalInserter::insert(String content)
{
    Buffer& buffer = m_editor.buffer();
    for (auto& sel : m_editor.m_selections)
    {
        m_editor.filters()(buffer, sel, content);
        buffer.insert(sel.last(), content);
    }
}

void IncrementalInserter::insert(const memoryview<String>& strings)
{
    for (size_t i = 0; i < m_editor.m_selections.size(); ++i)
    {
        size_t index = std::min(i, strings.size()-1);
        m_editor.buffer().insert(m_editor.m_selections[i].last(),
                                 strings[index]);
    }
}

void IncrementalInserter::erase()
{
    for (auto& sel : m_editor.m_selections)
    {
        BufferIterator pos = sel.last();
        m_editor.buffer().erase(utf8::previous(pos), pos);
    }
}

void IncrementalInserter::move_cursors(CharCount move)
{
    m_editor.move_selections(move, SelectMode::Replace);
}

void IncrementalInserter::move_cursors(LineCount move)
{
    m_editor.move_selections(move, SelectMode::Replace);
}

}
<commit_msg>Editor: merge_overlapping -> sort_and_merge_overlapping<commit_after>#include "editor.hh"

#include "exception.hh"
#include "utils.hh"
#include "register.hh"
#include "register_manager.hh"

#include "utf8_iterator.hh"

#include <array>

namespace Kakoune
{

Editor::Editor(Buffer& buffer)
    : m_buffer(&buffer),
      m_edition_level(0),
      m_selections(buffer)
{
    m_selections.push_back(Selection(buffer.begin(), buffer.begin()));
}

void Editor::erase()
{
    scoped_edition edition(*this);
    for (auto& sel : m_selections)
    {
        m_buffer->erase(sel.begin(), sel.end());
        sel.avoid_eol();
    }
}

static BufferIterator prepare_insert(Buffer& buffer, const Selection& sel,
                                     InsertMode mode)
{
    switch (mode)
    {
    case InsertMode::Insert:
    case InsertMode::Replace:
        return sel.begin();
    case InsertMode::Append:
    {
        // special case for end of lines, append to current line instead
        auto pos = std::max(sel.first(), sel.last());
        if (pos.column() == buffer.line_length(pos.line()) - 1)
            return pos;
        else
            return pos+1;
    }
    case InsertMode::InsertAtLineBegin:
        return buffer.iterator_at_line_begin(sel.begin());
    case InsertMode::AppendAtLineEnd:
        return buffer.iterator_at_line_end(sel.end()-1)-1;
    case InsertMode::InsertAtNextLineBegin:
        return buffer.iterator_at_line_end(sel.end()-1);
    case InsertMode::OpenLineBelow:
    {
        LineCount line = (sel.end() - 1).line();
        buffer.insert(buffer.iterator_at_line_end(line), "\n");
        return buffer.iterator_at_line_begin(line + 1);
    }
    case InsertMode::OpenLineAbove:
    {
        auto pos = buffer.iterator_at_line_begin(sel.begin());
        buffer.insert(pos, "\n");
        return pos;
    }
    }
    assert(false);
    return BufferIterator{};
}

void Editor::insert(const String& string, InsertMode mode)
{
    scoped_edition edition(*this);

    if (mode == InsertMode::Replace)
    {
        // do not call Editor::erase as we do not want to avoid end of lines
        for (auto& sel : m_selections)
            m_buffer->erase(sel.begin(), sel.end());
    }

    for (auto& sel : m_selections)
    {
        BufferIterator pos = prepare_insert(*m_buffer, sel, mode);
        m_buffer->insert(pos, string);
        if (mode == InsertMode::Replace)
        {
            sel.first() = pos;
            sel.last()  = pos + (string.empty() ? 0 : string.length() - 1 );
        }
        sel.avoid_eol();
    }
}

void Editor::insert(const memoryview<String>& strings, InsertMode mode)
{
    scoped_edition edition(*this);
    if (mode == InsertMode::Replace)
    {
        // do not call Editor::erase as we do not want to avoid end of lines
        for (auto& sel : m_selections)
            m_buffer->erase(sel.begin(), sel.end());
    }
    if (strings.empty())
        return;

    for (size_t i = 0; i < selections().size(); ++i)
    {
        auto& sel = m_selections[i];
        BufferIterator pos = prepare_insert(*m_buffer, sel, mode);
        const String& str = strings[std::min(i, strings.size()-1)];
        m_buffer->insert(pos, str);
        if (mode == InsertMode::Replace)
        {
            sel.first() = pos;
            sel.last()  = pos + (str.empty() ? 0 : str.length() - 1);
        }
        sel.avoid_eol();
    }
}

std::vector<String> Editor::selections_content() const
{
    std::vector<String> contents;
    for (auto& sel : m_selections)
        contents.push_back(m_buffer->string(sel.begin(),
                                           sel.end()));
    return contents;
}

static void sort_and_merge_overlapping(SelectionList& selections)
{
    std::sort(selections.begin(), selections.end(),
              [](const Selection& lhs, const Selection& rhs)
              { return lhs.begin() < rhs.begin(); });
    for (size_t i = 0; i < selections.size()-1;)
    {
        if (overlaps(selections[i], selections[i+1]))
        {
            selections[i].merge_with(selections[i+1]);
            selections.erase(selections.begin() + i + 1);
        }
        else
           ++i;
    }
}

void Editor::move_selections(CharCount offset, SelectMode mode)
{
    assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
    for (auto& sel : m_selections)
    {
        auto last = sel.last();
        auto limit = offset < 0 ? buffer().iterator_at_line_begin(last)
                                : utf8::previous(buffer().iterator_at_line_end(last));
        last = utf8::advance(last, limit, offset);
        sel = Selection(mode == SelectMode::Extend ? sel.first() : last, last);
        sel.avoid_eol();
    }
    sort_and_merge_overlapping(m_selections);
}

void Editor::move_selections(LineCount offset, SelectMode mode)
{
    assert(mode == SelectMode::Replace or mode == SelectMode::Extend);
    for (auto& sel : m_selections)
    {
        BufferCoord pos = sel.last().coord();
        pos.line += offset;
        BufferIterator last = utf8::finish(m_buffer->iterator_at(pos, true));
        sel = Selection(mode == SelectMode::Extend ? sel.first() : last, last);
        sel.avoid_eol();
    }
    sort_and_merge_overlapping(m_selections);
}

void Editor::clear_selections()
{
    check_invariant();
    BufferIterator pos = m_selections.back().last();

    if (*pos == '\n' and not pos.is_begin() and *utf8::previous(pos) != '\n')
        pos = utf8::previous(pos);

    Selection sel = Selection(pos, pos);
    m_selections = SelectionList{ std::move(sel) };
}

void Editor::flip_selections()
{
    check_invariant();
    for (auto& sel : m_selections)
        std::swap(sel.first(), sel.last());
}

void Editor::keep_selection(int index)
{
    check_invariant();

    if (index < m_selections.size())
        m_selections = SelectionList{ std::move(m_selections[index]) };
}

void Editor::remove_selection(int index)
{
    check_invariant();

    if (m_selections.size() > 1 and index < m_selections.size())
        m_selections.erase(m_selections.begin() + index);
}

void Editor::select(const BufferIterator& iterator, SelectMode mode)
{
    if (mode == SelectMode::Replace)
        m_selections = SelectionList{ {iterator, iterator} };
    else if (mode == SelectMode::Extend)
    {
        for (auto& sel : m_selections)
            sel.last() = iterator;
        sort_and_merge_overlapping(m_selections);
    }
    else if (mode == SelectMode::Append)
        assert(false);
}

void Editor::select(SelectionList selections)
{
    if (selections.empty())
        throw runtime_error("no selections");
    m_selections = std::move(selections);
}

void Editor::select(const Selector& selector, SelectMode mode)
{
    check_invariant();

    if (mode == SelectMode::Append)
    {
        auto& sel = m_selections.back();
        Selection res = selector(sel);
        if (res.captures().empty())
            res.captures() = sel.captures();
        m_selections.push_back(res);
    }
    else
    {
        for (auto& sel : m_selections)
        {
            Selection res = selector(sel);
            if (mode == SelectMode::Extend)
                sel.merge_with(res);
            else
                sel = std::move(res);

            if (not res.captures().empty())
                sel.captures() = std::move(res.captures());
        }
    }
    sort_and_merge_overlapping(m_selections);
}

struct nothing_selected : public runtime_error
{
    nothing_selected() : runtime_error("nothing was selected") {}
};

void Editor::multi_select(const MultiSelector& selector)
{
    check_invariant();

    SelectionList new_selections;
    for (auto& sel : m_selections)
    {
        SelectionList res = selector(sel);
        for (auto& new_sel : res)
        {
            // preserve captures when selectors captures nothing.
            if (new_sel.captures().empty())
                new_selections.emplace_back(new_sel.first(), new_sel.last(),
                                            sel.captures());
            else
                new_selections.push_back(std::move(new_sel));
        }
    }
    if (new_selections.empty())
        throw nothing_selected();
    sort_and_merge_overlapping(new_selections);
    m_selections = std::move(new_selections);
}

class LastModifiedRangeListener : public BufferChangeListener
{
public:
    LastModifiedRangeListener(Buffer& buffer)
       : m_buffer(buffer)
    { m_buffer.add_change_listener(*this); }

    ~LastModifiedRangeListener()
    { m_buffer.remove_change_listener(*this); }

    void on_insert(const BufferIterator& begin, const BufferIterator& end)
    {
        m_first = begin;
        m_last = utf8::previous(end);
    }

    void on_erase(const BufferIterator& begin, const BufferIterator& end)
    {
        m_first = begin;
        if (m_first >= m_buffer.end())
            m_first = utf8::previous(m_buffer.end());
        m_last = m_first;
    }

    const BufferIterator& first() const { return m_first; }
    const BufferIterator& last() const { return m_last; }

private:
    BufferIterator m_first;
    BufferIterator m_last;
    Buffer& m_buffer;
};

bool Editor::undo()
{
    LastModifiedRangeListener listener(buffer());
    bool res = m_buffer->undo();
    if (res)
        m_selections = SelectionList{ {listener.first(), listener.last()} };
    return res;
}

bool Editor::redo()
{
    LastModifiedRangeListener listener(buffer());
    bool res = m_buffer->redo();
    if (res)
        m_selections = SelectionList{ {listener.first(), listener.last()} };
    return res;
}

void Editor::check_invariant() const
{
    assert(not m_selections.empty());
    m_selections.check_invariant();
}

void Editor::begin_edition()
{
    ++m_edition_level;

    if (m_edition_level == 1)
        m_buffer->begin_undo_group();
}

void Editor::end_edition()
{
    assert(m_edition_level > 0);
    if (m_edition_level == 1)
        m_buffer->end_undo_group();

    --m_edition_level;
}

using utf8_it = utf8::utf8_iterator<BufferIterator, utf8::InvalidBytePolicy::Pass>;

IncrementalInserter::IncrementalInserter(Editor& editor, InsertMode mode)
    : m_editor(editor), m_edition(editor), m_mode(mode)
{
    m_editor.on_incremental_insertion_begin();
    Buffer& buffer = *editor.m_buffer;

    if (mode == InsertMode::Replace)
    {
        for (auto& sel : editor.m_selections)
            buffer.erase(sel.begin(), sel.end());
    }

    for (auto& sel : m_editor.m_selections)
    {
        utf8_it first, last;
        switch (mode)
        {
        case InsertMode::Insert:  first = utf8_it(sel.end()) - 1; last = sel.begin(); break;
        case InsertMode::Replace: first = utf8_it(sel.end()) - 1; last = sel.begin(); break;
        case InsertMode::Append:
        {
            first = sel.begin();
            last  = std::max(sel.first(), sel.last());
            // special case for end of lines, append to current line instead
            auto coord = last.underlying_iterator().coord();
            if (coord.column != buffer.line_length(coord.line) - 1)
                ++last;
            break;
        }

        case InsertMode::OpenLineBelow:
        case InsertMode::AppendAtLineEnd:
            first = utf8_it(buffer.iterator_at_line_end(utf8::previous(sel.end()))) - 1;
            last  = first;
            break;

        case InsertMode::OpenLineAbove:
        case InsertMode::InsertAtLineBegin:
            first = buffer.iterator_at_line_begin(sel.begin());
            if (mode == InsertMode::OpenLineAbove)
                --first;
            else
            {
                auto first_non_blank = first;
                while (*first_non_blank == ' ' or *first_non_blank == '\t')
                    ++first_non_blank;
                if (*first_non_blank != '\n')
                    first = first_non_blank;
            }
            last = first;
            break;
        }
        if (first.underlying_iterator().is_end())
           --first;
        if (last.underlying_iterator().is_end())
           --last;
        sel.first() = first.underlying_iterator();
        sel.last()  = last.underlying_iterator();
    }
    if (mode == InsertMode::OpenLineBelow or mode == InsertMode::OpenLineAbove)
    {
        insert("\n");
        if (mode == InsertMode::OpenLineAbove)
        {
            for (auto& sel : m_editor.m_selections)
            {
                // special case, the --first line above did nothing, so we need to compensate now
                if (sel.first() == utf8::next(buffer.begin()))
                    sel.first() = sel.last() = buffer.begin();
            }
        }
    }
}

IncrementalInserter::~IncrementalInserter()
{
    for (auto& sel : m_editor.m_selections)
    {
        if (m_mode == InsertMode::Append and sel.last().column() > 0)
            sel.last() = utf8::previous(sel.last());
         sel.avoid_eol();
    }

    m_editor.on_incremental_insertion_end();
}

void IncrementalInserter::insert(String content)
{
    Buffer& buffer = m_editor.buffer();
    for (auto& sel : m_editor.m_selections)
    {
        m_editor.filters()(buffer, sel, content);
        buffer.insert(sel.last(), content);
    }
}

void IncrementalInserter::insert(const memoryview<String>& strings)
{
    for (size_t i = 0; i < m_editor.m_selections.size(); ++i)
    {
        size_t index = std::min(i, strings.size()-1);
        m_editor.buffer().insert(m_editor.m_selections[i].last(),
                                 strings[index]);
    }
}

void IncrementalInserter::erase()
{
    for (auto& sel : m_editor.m_selections)
    {
        BufferIterator pos = sel.last();
        m_editor.buffer().erase(utf8::previous(pos), pos);
    }
}

void IncrementalInserter::move_cursors(CharCount move)
{
    m_editor.move_selections(move, SelectMode::Replace);
}

void IncrementalInserter::move_cursors(LineCount move)
{
    m_editor.move_selections(move, SelectMode::Replace);
}

}
<|endoftext|>


<commit_before>#pragma once

#include "utilities.hpp"
#include <qdb/client.h>

namespace quasardb
{

class Error : public node::ObjectWrap
{
public:
    explicit Error(qdb_error_t e) : _error(e)
    {
    }

    virtual ~Error(void)
    {
    }

private:
    static void AddErrorOrigin(v8::Local<v8::Object> exports, const char * name, qdb_error_origin_t origin)
    {
        v8::Isolate * isolate = exports->GetIsolate();
        detail::AddConstantProperty(isolate, exports, name, v8::Int32::New(isolate, origin));
    }

    static void AddErrorSeverity(v8::Local<v8::Object> exports, const char * name, qdb_error_severity_t severity)
    {
        v8::Isolate * isolate = exports->GetIsolate();
        detail::AddConstantProperty(isolate, exports, name, v8::Int32::New(isolate, severity));
    }

    static void AddErrorCode(v8::Local<v8::Object> exports, const char * name, qdb_error_t err)
    {
        static const int code_mask = 0xFFFF;

        v8::Isolate * isolate = exports->GetIsolate();
        detail::AddConstantProperty(isolate, exports, name, v8::Int32::New(isolate, err & code_mask));
    }

public:
    static void Init(v8::Local<v8::Object> exports)
    {
        v8::Isolate * isolate = exports->GetIsolate();

        // Prepare constructor template
        v8::Local<v8::FunctionTemplate> tpl = v8::FunctionTemplate::New(isolate, New);
        tpl->SetClassName(v8::String::NewFromUtf8(isolate, "Error"));
        tpl->InstanceTemplate()->SetInternalFieldCount(1);

        v8::HandleScope handle_scope(isolate);
        v8::Local<v8::Signature> s = v8::Signature::New(isolate, tpl);

        auto proto = tpl->PrototypeTemplate();

        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "informational"),
                                   v8::FunctionTemplate::New(isolate, Error::informational, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);
        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "origin"),
                                   v8::FunctionTemplate::New(isolate, Error::origin, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);
        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "severity"),
                                   v8::FunctionTemplate::New(isolate, Error::severity, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);
        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "message"),
                                   v8::FunctionTemplate::New(isolate, Error::message, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);
        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "code"),
                                   v8::FunctionTemplate::New(isolate, Error::code, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);

        AddErrorOrigin(exports, "E_ORIGIN_SYSTEM_REMOTE", qdb_e_origin_system_remote);
        AddErrorOrigin(exports, "E_ORIGIN_SYSTEM_LOCAL", qdb_e_origin_system_local);
        AddErrorOrigin(exports, "E_ORIGIN_CONNECTION", qdb_e_origin_connection);
        AddErrorOrigin(exports, "E_ORIGIN_INPUT", qdb_e_origin_input);
        AddErrorOrigin(exports, "E_ORIGIN_OPERATION", qdb_e_origin_operation);
        AddErrorOrigin(exports, "E_ORIGIN_PROTOCOL", qdb_e_origin_protocol);

        AddErrorSeverity(exports, "E_SEVERITY_UNRECOVERABLE", qdb_e_severity_unrecoverable);
        AddErrorSeverity(exports, "E_SEVERITY_ERROR", qdb_e_severity_error);
        AddErrorSeverity(exports, "E_SEVERITY_WARNING", qdb_e_severity_warning);
        AddErrorSeverity(exports, "E_SEVERITY_INFO", qdb_e_severity_info);

        AddErrorCode(exports, "E_UNINITIALIZED", qdb_e_uninitialized);
        AddErrorCode(exports, "E_ALIAS_NOT_FOUND", qdb_e_alias_not_found);
        AddErrorCode(exports, "E_ALIAS_ALREADY_EXISTS", qdb_e_alias_already_exists);
        AddErrorCode(exports, "E_OUT_OF_BOUNDS", qdb_e_out_of_bounds);
        AddErrorCode(exports, "E_SKIPPED", qdb_e_skipped);
        AddErrorCode(exports, "E_INCOMPATIBLE_TYPE", qdb_e_incompatible_type);
        AddErrorCode(exports, "E_CONTAINER_EMPTY", qdb_e_container_empty);
        AddErrorCode(exports, "E_CONTAINER_FULL", qdb_e_container_full);
        AddErrorCode(exports, "E_ELEMENT_NOT_FOUND", qdb_e_element_not_found);
        AddErrorCode(exports, "E_ELEMENT_ALREADY_EXISTS", qdb_e_element_already_exists);
        AddErrorCode(exports, "E_OVERFLOW", qdb_e_overflow);
        AddErrorCode(exports, "E_UNDERFLOW", qdb_e_underflow);
        AddErrorCode(exports, "E_TAG_ALREADY_SET", qdb_e_tag_already_set);
        AddErrorCode(exports, "E_TAG_NOT_SET", qdb_e_tag_not_set);
        AddErrorCode(exports, "E_TIMEOUT", qdb_e_timeout);
        AddErrorCode(exports, "E_CONNECTION_REFUSED", qdb_e_connection_refused);
        AddErrorCode(exports, "E_CONNECTION_RESET", qdb_e_connection_reset);
        AddErrorCode(exports, "E_UNSTABLE_CLUSTER", qdb_e_unstable_cluster);
        AddErrorCode(exports, "E_TRY_AGAIN", qdb_e_try_again);
        AddErrorCode(exports, "E_CONFLICT", qdb_e_conflict);
        AddErrorCode(exports, "E_NOT_CONNECTED", qdb_e_not_connected);
        AddErrorCode(exports, "E_RESOURCE_LOCKED", qdb_e_resource_locked);
        AddErrorCode(exports, "E_SYSTEM_REMOTE", qdb_e_system_remote);
        AddErrorCode(exports, "E_SYSTEM_LOCAL", qdb_e_system_local);
        AddErrorCode(exports, "E_INTERNAL_REMOTE", qdb_e_internal_remote);
        AddErrorCode(exports, "E_INTERNAL_LOCAL", qdb_e_internal_local);
        AddErrorCode(exports, "E_NO_MEMORY_REMOTE", qdb_e_no_memory_remote);
        AddErrorCode(exports, "E_NO_MEMORY_LOCAL", qdb_e_no_memory_local);
        AddErrorCode(exports, "E_INVALID_PROTOCOL", qdb_e_invalid_protocol);
        AddErrorCode(exports, "E_HOST_NOT_FOUND", qdb_e_host_not_found);
        AddErrorCode(exports, "E_BUFFER_TOO_SMALL", qdb_e_buffer_too_small);
        AddErrorCode(exports, "E_NOT_IMPLEMENTED", qdb_e_not_implemented);
        AddErrorCode(exports, "E_INVALID_VERSION", qdb_e_invalid_version);
        AddErrorCode(exports, "E_INVALID_ARGUMENT", qdb_e_invalid_argument);
        AddErrorCode(exports, "E_INVALID_HANDLE", qdb_e_invalid_handle);
        AddErrorCode(exports, "E_RESERVED_ALIAS", qdb_e_reserved_alias);
        AddErrorCode(exports, "E_UNMATCHED_CONTENT", qdb_e_unmatched_content);
        AddErrorCode(exports, "E_INVALID_ITERATOR", qdb_e_invalid_iterator);
        AddErrorCode(exports, "E_ENTRY_TOO_LARGE", qdb_e_entry_too_large);
        AddErrorCode(exports, "E_TRANSACTION_PARTIAL_FAILURE", qdb_e_transaction_partial_failure);
        AddErrorCode(exports, "E_OPERATION_DISABLED", qdb_e_operation_disabled);
        AddErrorCode(exports, "E_OPERATION_NOT_PERMITTED", qdb_e_operation_not_permitted);
        AddErrorCode(exports, "E_ITERATOR_END", qdb_e_iterator_end);
        AddErrorCode(exports, "E_INVALID_REPLY", qdb_e_invalid_reply);
        AddErrorCode(exports, "E_OK_CREATED", qdb_e_ok_created);
        AddErrorCode(exports, "E_NO_SPACE_LEFT", qdb_e_no_space_left);
        AddErrorCode(exports, "E_QUOTA_EXCEEDED", qdb_e_quota_exceeded);

        constructor.Reset(isolate, tpl->GetFunction());
        exports->Set(v8::String::NewFromUtf8(isolate, "Error"), tpl->GetFunction());
    }

private:
    static void New(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        if (args.IsConstructCall())
        {
            MethodMan call(args);

            if (args.Length() != 1)
            {
                call.throwException("Expected exactly one argument");
                return;
            }

            ArgsEater argsEater(call);

            auto code = argsEater.eatNumber();
            if (!code.second)
            {
                call.throwException("Expected an error code as first argument");
                return;
            }

            const qdb_error_t err = static_cast<qdb_error_t>(static_cast<unsigned long>(code.first));

            Error * e = new Error(err);

            e->Wrap(args.This());
            args.GetReturnValue().Set(args.This());
        }
        else
        {
            NewInstance(args);
        }
    }

public:
    static void NewInstance(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        v8::Isolate * isolate = args.GetIsolate();
        // Invoked as plain function `MyObject(...)`, turn into construct call.
        static const int argc = 1;
        v8::Local<v8::Value> argv[argc] = {args[0]};
        v8::Local<v8::Function> cons = v8::Local<v8::Function>::New(isolate, constructor);
        v8::MaybeLocal<v8::Object> instance = cons->NewInstance(isolate->GetCurrentContext(), argc, argv);
        args.GetReturnValue().Set(instance.ToLocalChecked());
    }

public:
    static v8::Local<v8::Object> MakeError(v8::Isolate * isolate, qdb_error_t err)
    {
        static const int argc = 1;
        v8::Local<v8::Value> argv[argc] = {v8::Number::New(isolate, err)};
        v8::Local<v8::Function> cons = v8::Local<v8::Function>::New(isolate, constructor);
        return cons->NewInstance(isolate->GetCurrentContext(), 1, argv).ToLocalChecked();
    }

private:
    template <typename F>
    static void accessor(const v8::FunctionCallbackInfo<v8::Value> & args, F f)
    {
        MethodMan call(args);

        if (args.Length() != 0)
        {
            call.throwException("Wrong number of arguments");
            return;
        }

        Error * e = call.nativeHolder<Error>();
        assert(e);

        f(args, e);
    }

public:
    //:desc: Determines if the error is an informational error.
    //:returns: True if the error is informational, false otherwise.
    static void informational(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            v8::Isolate * isolate = args.GetIsolate();
            args.GetReturnValue().Set(v8::Boolean::New(isolate, QDB_SUCCESS(e->_error)));
        });
    }

    //:desc: Gets the origin of the error
    //:returns: A string containing the origin of the error.
    static void origin(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            v8::Isolate * isolate = args.GetIsolate();
            args.GetReturnValue().Set(v8::Integer::New(isolate, QDB_ERROR_ORIGIN(e->_error)));
        });
    }

    //:desc: Gets the severity of the errror
    //:returns: A string containing the severity of the error.
    static void severity(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            v8::Isolate * isolate = args.GetIsolate();
            args.GetReturnValue().Set(v8::Integer::New(isolate, QDB_ERROR_SEVERITY(e->_error)));
        });
    }

    //:desc: Gets a description of the error.
    //:returns: A string containing the error message.
    static void message(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            // the string returned by qdb_error is static it is therefore safe and efficient to do this
            v8::Isolate * isolate = args.GetIsolate();
            args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, qdb_error(e->_error)));
        });
    }

    //:desc: Gets the error code.
    //:returns: An integer with the error code.
    static void code(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            // the string returned by qdb_error is static it is therefore safe and efficient to do this
            v8::Isolate * isolate = args.GetIsolate();

            // read low 16-bit for the code
            args.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, e->_error & 0xffff));
        });
    }

private:
    const qdb_error_t _error;

    static v8::Persistent<v8::Function> constructor;
};

} // namespace quasardb
<commit_msg>Add missing error codes<commit_after>#pragma once

#include "utilities.hpp"
#include <qdb/client.h>

namespace quasardb
{

class Error : public node::ObjectWrap
{
public:
    explicit Error(qdb_error_t e) : _error(e)
    {
    }

    virtual ~Error(void)
    {
    }

private:
    static void AddErrorOrigin(v8::Local<v8::Object> exports, const char * name, qdb_error_origin_t origin)
    {
        v8::Isolate * isolate = exports->GetIsolate();
        detail::AddConstantProperty(isolate, exports, name, v8::Int32::New(isolate, origin));
    }

    static void AddErrorSeverity(v8::Local<v8::Object> exports, const char * name, qdb_error_severity_t severity)
    {
        v8::Isolate * isolate = exports->GetIsolate();
        detail::AddConstantProperty(isolate, exports, name, v8::Int32::New(isolate, severity));
    }

    static void AddErrorCode(v8::Local<v8::Object> exports, const char * name, qdb_error_t err)
    {
        static const int code_mask = 0xFFFF;

        v8::Isolate * isolate = exports->GetIsolate();
        detail::AddConstantProperty(isolate, exports, name, v8::Int32::New(isolate, err & code_mask));
    }

public:
    static void Init(v8::Local<v8::Object> exports)
    {
        v8::Isolate * isolate = exports->GetIsolate();

        // Prepare constructor template
        v8::Local<v8::FunctionTemplate> tpl = v8::FunctionTemplate::New(isolate, New);
        tpl->SetClassName(v8::String::NewFromUtf8(isolate, "Error"));
        tpl->InstanceTemplate()->SetInternalFieldCount(1);

        v8::HandleScope handle_scope(isolate);
        v8::Local<v8::Signature> s = v8::Signature::New(isolate, tpl);

        auto proto = tpl->PrototypeTemplate();

        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "informational"),
                                   v8::FunctionTemplate::New(isolate, Error::informational, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);
        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "origin"),
                                   v8::FunctionTemplate::New(isolate, Error::origin, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);
        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "severity"),
                                   v8::FunctionTemplate::New(isolate, Error::severity, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);
        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "message"),
                                   v8::FunctionTemplate::New(isolate, Error::message, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);
        proto->SetAccessorProperty(v8::String::NewFromUtf8(isolate, "code"),
                                   v8::FunctionTemplate::New(isolate, Error::code, v8::Local<v8::Value>(), s),
                                   v8::Local<v8::FunctionTemplate>(), v8::ReadOnly);

        AddErrorOrigin(exports, "E_ORIGIN_SYSTEM_REMOTE", qdb_e_origin_system_remote);
        AddErrorOrigin(exports, "E_ORIGIN_SYSTEM_LOCAL", qdb_e_origin_system_local);
        AddErrorOrigin(exports, "E_ORIGIN_CONNECTION", qdb_e_origin_connection);
        AddErrorOrigin(exports, "E_ORIGIN_INPUT", qdb_e_origin_input);
        AddErrorOrigin(exports, "E_ORIGIN_OPERATION", qdb_e_origin_operation);
        AddErrorOrigin(exports, "E_ORIGIN_PROTOCOL", qdb_e_origin_protocol);

        AddErrorSeverity(exports, "E_SEVERITY_UNRECOVERABLE", qdb_e_severity_unrecoverable);
        AddErrorSeverity(exports, "E_SEVERITY_ERROR", qdb_e_severity_error);
        AddErrorSeverity(exports, "E_SEVERITY_WARNING", qdb_e_severity_warning);
        AddErrorSeverity(exports, "E_SEVERITY_INFO", qdb_e_severity_info);

        AddErrorCode(exports, "E_UNINITIALIZED", qdb_e_uninitialized);
        AddErrorCode(exports, "E_ALIAS_NOT_FOUND", qdb_e_alias_not_found);
        AddErrorCode(exports, "E_ALIAS_ALREADY_EXISTS", qdb_e_alias_already_exists);
        AddErrorCode(exports, "E_OUT_OF_BOUNDS", qdb_e_out_of_bounds);
        AddErrorCode(exports, "E_SKIPPED", qdb_e_skipped);
        AddErrorCode(exports, "E_INCOMPATIBLE_TYPE", qdb_e_incompatible_type);
        AddErrorCode(exports, "E_CONTAINER_EMPTY", qdb_e_container_empty);
        AddErrorCode(exports, "E_CONTAINER_FULL", qdb_e_container_full);
        AddErrorCode(exports, "E_ELEMENT_NOT_FOUND", qdb_e_element_not_found);
        AddErrorCode(exports, "E_ELEMENT_ALREADY_EXISTS", qdb_e_element_already_exists);
        AddErrorCode(exports, "E_OVERFLOW", qdb_e_overflow);
        AddErrorCode(exports, "E_UNDERFLOW", qdb_e_underflow);
        AddErrorCode(exports, "E_TAG_ALREADY_SET", qdb_e_tag_already_set);
        AddErrorCode(exports, "E_TAG_NOT_SET", qdb_e_tag_not_set);
        AddErrorCode(exports, "E_TIMEOUT", qdb_e_timeout);
        AddErrorCode(exports, "E_CONNECTION_REFUSED", qdb_e_connection_refused);
        AddErrorCode(exports, "E_CONNECTION_RESET", qdb_e_connection_reset);
        AddErrorCode(exports, "E_UNSTABLE_CLUSTER", qdb_e_unstable_cluster);
        AddErrorCode(exports, "E_TRY_AGAIN", qdb_e_try_again);
        AddErrorCode(exports, "E_CONFLICT", qdb_e_conflict);
        AddErrorCode(exports, "E_NOT_CONNECTED", qdb_e_not_connected);
        AddErrorCode(exports, "E_RESOURCE_LOCKED", qdb_e_resource_locked);
        AddErrorCode(exports, "E_SYSTEM_REMOTE", qdb_e_system_remote);
        AddErrorCode(exports, "E_SYSTEM_LOCAL", qdb_e_system_local);
        AddErrorCode(exports, "E_INTERNAL_REMOTE", qdb_e_internal_remote);
        AddErrorCode(exports, "E_INTERNAL_LOCAL", qdb_e_internal_local);
        AddErrorCode(exports, "E_NO_MEMORY_REMOTE", qdb_e_no_memory_remote);
        AddErrorCode(exports, "E_NO_MEMORY_LOCAL", qdb_e_no_memory_local);
        AddErrorCode(exports, "E_INVALID_PROTOCOL", qdb_e_invalid_protocol);
        AddErrorCode(exports, "E_HOST_NOT_FOUND", qdb_e_host_not_found);
        AddErrorCode(exports, "E_BUFFER_TOO_SMALL", qdb_e_buffer_too_small);
        AddErrorCode(exports, "E_NOT_IMPLEMENTED", qdb_e_not_implemented);
        AddErrorCode(exports, "E_INVALID_VERSION", qdb_e_invalid_version);
        AddErrorCode(exports, "E_INVALID_ARGUMENT", qdb_e_invalid_argument);
        AddErrorCode(exports, "E_INVALID_HANDLE", qdb_e_invalid_handle);
        AddErrorCode(exports, "E_RESERVED_ALIAS", qdb_e_reserved_alias);
        AddErrorCode(exports, "E_UNMATCHED_CONTENT", qdb_e_unmatched_content);
        AddErrorCode(exports, "E_INVALID_ITERATOR", qdb_e_invalid_iterator);
        AddErrorCode(exports, "E_ENTRY_TOO_LARGE", qdb_e_entry_too_large);
        AddErrorCode(exports, "E_TRANSACTION_PARTIAL_FAILURE", qdb_e_transaction_partial_failure);
        AddErrorCode(exports, "E_OPERATION_DISABLED", qdb_e_operation_disabled);
        AddErrorCode(exports, "E_OPERATION_NOT_PERMITTED", qdb_e_operation_not_permitted);
        AddErrorCode(exports, "E_ITERATOR_END", qdb_e_iterator_end);
        AddErrorCode(exports, "E_INVALID_REPLY", qdb_e_invalid_reply);
        AddErrorCode(exports, "E_OK_CREATED", qdb_e_ok_created);
        AddErrorCode(exports, "E_NO_SPACE_LEFT", qdb_e_no_space_left);
        AddErrorCode(exports, "E_QUOTA_EXCEEDED", qdb_e_quota_exceeded);
        AddErrorCode(exports, "E_ALIAS_TOO_LONG", qdb_e_alias_too_long);
        AddErrorCode(exports, "E_CLOCK_SKEW", qdb_e_clock_skew);
        AddErrorCode(exports, "E_ACCESS_DENIED", qdb_e_access_denied);
        AddErrorCode(exports, "E_LOGIN_FAILED", qdb_e_login_failed);
        AddErrorCode(exports, "E_COLUMN_NOT_FOUND", qdb_e_column_not_found);
        AddErrorCode(exports, "E_QUERY_TOO_COMPLEX", qdb_e_query_too_complex);
        AddErrorCode(exports, "E_INVALID_CRYPTO_KEY", qdb_e_invalid_crypto_key);
        AddErrorCode(exports, "E_INVALID_QUERY", qdb_e_invalid_query);
        AddErrorCode(exports, "E_INVALID_REGEX", qdb_e_invalid_regex);

        constructor.Reset(isolate, tpl->GetFunction());
        exports->Set(v8::String::NewFromUtf8(isolate, "Error"), tpl->GetFunction());
    }

private:
    static void New(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        if (args.IsConstructCall())
        {
            MethodMan call(args);

            if (args.Length() != 1)
            {
                call.throwException("Expected exactly one argument");
                return;
            }

            ArgsEater argsEater(call);

            auto code = argsEater.eatNumber();
            if (!code.second)
            {
                call.throwException("Expected an error code as first argument");
                return;
            }

            const qdb_error_t err = static_cast<qdb_error_t>(static_cast<unsigned long>(code.first));

            Error * e = new Error(err);

            e->Wrap(args.This());
            args.GetReturnValue().Set(args.This());
        }
        else
        {
            NewInstance(args);
        }
    }

public:
    static void NewInstance(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        v8::Isolate * isolate = args.GetIsolate();
        // Invoked as plain function `MyObject(...)`, turn into construct call.
        static const int argc = 1;
        v8::Local<v8::Value> argv[argc] = {args[0]};
        v8::Local<v8::Function> cons = v8::Local<v8::Function>::New(isolate, constructor);
        v8::MaybeLocal<v8::Object> instance = cons->NewInstance(isolate->GetCurrentContext(), argc, argv);
        args.GetReturnValue().Set(instance.ToLocalChecked());
    }

public:
    static v8::Local<v8::Object> MakeError(v8::Isolate * isolate, qdb_error_t err)
    {
        static const int argc = 1;
        v8::Local<v8::Value> argv[argc] = {v8::Number::New(isolate, err)};
        v8::Local<v8::Function> cons = v8::Local<v8::Function>::New(isolate, constructor);
        return cons->NewInstance(isolate->GetCurrentContext(), 1, argv).ToLocalChecked();
    }

private:
    template <typename F>
    static void accessor(const v8::FunctionCallbackInfo<v8::Value> & args, F f)
    {
        MethodMan call(args);

        if (args.Length() != 0)
        {
            call.throwException("Wrong number of arguments");
            return;
        }

        Error * e = call.nativeHolder<Error>();
        assert(e);

        f(args, e);
    }

public:
    //:desc: Determines if the error is an informational error.
    //:returns: True if the error is informational, false otherwise.
    static void informational(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            v8::Isolate * isolate = args.GetIsolate();
            args.GetReturnValue().Set(v8::Boolean::New(isolate, QDB_SUCCESS(e->_error)));
        });
    }

    //:desc: Gets the origin of the error
    //:returns: A string containing the origin of the error.
    static void origin(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            v8::Isolate * isolate = args.GetIsolate();
            args.GetReturnValue().Set(v8::Integer::New(isolate, QDB_ERROR_ORIGIN(e->_error)));
        });
    }

    //:desc: Gets the severity of the errror
    //:returns: A string containing the severity of the error.
    static void severity(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            v8::Isolate * isolate = args.GetIsolate();
            args.GetReturnValue().Set(v8::Integer::New(isolate, QDB_ERROR_SEVERITY(e->_error)));
        });
    }

    //:desc: Gets a description of the error.
    //:returns: A string containing the error message.
    static void message(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            // the string returned by qdb_error is static it is therefore safe and efficient to do this
            v8::Isolate * isolate = args.GetIsolate();
            args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, qdb_error(e->_error)));
        });
    }

    //:desc: Gets the error code.
    //:returns: An integer with the error code.
    static void code(const v8::FunctionCallbackInfo<v8::Value> & args)
    {
        Error::accessor(args, [](const v8::FunctionCallbackInfo<v8::Value> & args, Error * e) {
            // the string returned by qdb_error is static it is therefore safe and efficient to do this
            v8::Isolate * isolate = args.GetIsolate();

            // read low 16-bit for the code
            args.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, e->_error & 0xffff));
        });
    }

private:
    const qdb_error_t _error;

    static v8::Persistent<v8::Function> constructor;
};

} // namespace quasardb
<|endoftext|>