#include <BoundDatum.h>
#include <TimestampColumn.h>
#include <MutateJS.h>
#include <codecvt>
#include <locale>
#include <cstring>

namespace mssql
{
	const int sql_server_2008_default_time_precision = 16;
	const int sql_server_2008_default_datetime_precision = 34;
	const int sql_server_2008_default_timestamp_precision = 27;
	const int sql_server_2008_default_datetime_scale = 7;

	static Local<Boolean> get_as_bool(const Local<Value> o, const char* v)
	{
		const nodeTypeFactory fact;
		const auto context = fact.isolate->GetCurrentContext();
		const auto vp = Nan::New(v).ToLocalChecked();
		const auto false_val = Nan::New(false);
		if (o->IsNull())
		{
			return false_val;
		}
		if (!o->IsObject())
		{
			return false_val;
		}
		Local<Object> as_obj;
		if (!o->ToObject(context).ToLocal(&as_obj))
		{
			return false_val;
		}
		if (as_obj->IsNull())
		{
			return false_val;
		}
		Local<Value> as_val;
		if (!as_obj->Get(context, vp).ToLocal(&as_val))
		{
			return false_val;
		}
		if (as_val->IsNull())
		{
			return false_val;
		}
		
		return Nan::To<Boolean>(as_val).ToLocalChecked();
	}

	bool sql_type_s_maps_to_tvp(const Local<Value> p)
	{
		const auto is_user_defined = get_as_bool(p, "is_user_defined");
		if (is_user_defined->IsNull()) return false;
		const auto as_bool = MutateJS::as_boolean(is_user_defined);	
		return as_bool;
	}

	bool BoundDatum::bind(Local<Value>& p)
	{
		auto res = false;
		if (sql_type_s_maps_to_tvp(p))
		{
			bind_tvp(p);
			return true;
		}
		if (p->IsArray())
		{
			res = bind_array(p);
		}
		else if (p->IsObject())
		{
			res = bind_object(p);
		}
		if (!res) res = bind_datum_type(p);
		return res;
	}

	static Local<String> get_as_string(const Local<Value> o, const char* v)
	{
		const auto key = Nan::New<String>(v).ToLocalChecked();
		const auto ss = Nan::Get(Nan::To<Object>(o).ToLocalChecked(), key).ToLocalChecked();
		if (!ss.IsEmpty()) {
			return Nan::To<String>(ss).ToLocalChecked();
		}
		return Nan::EmptyString();
	}

	void BoundDatum::bind_null(const Local<Value>& p)
	{
		reserve_null(1);
		_indvec[0] = SQL_NULL_DATA;
	}

	void BoundDatum::bind_null_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_null(len);
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
		}
	}

	void BoundDatum::reserve_null(const SQLLEN len)
	{
		buffer_len = 0;
		_indvec.resize(len);
		js_type = JS_NULL;
		c_type = SQL_C_CHAR;
		sql_type = SQL_CHAR;
		param_size = 1;
		digits = 0;
		buffer = nullptr;
	}

	void BoundDatum::bind_w_long_var_char(const Local<Value>& p)
	{
		bind_w_var_char(p);
		sql_type = SQL_WLONGVARCHAR;
		param_size = buffer_len;
	}

	void BoundDatum::bind_w_var_char(const Local<Value>& p)
	{
		const auto str_param = Nan::To<String>(p).FromMaybe(Nan::EmptyString());	
		bind_w_var_char(p, str_param->Length());
	}

	void BoundDatum::bind_char(const Local<Value>& p)
	{
		bind_var_char(p);
	}

	void BoundDatum::bind_var_char(const Local<Value>& p)
	{
		const auto local = Nan::To<String>(p).FromMaybe(Nan::EmptyString());
		SQLULEN precision = local->Length();
		if (param_size > 0) precision = min(param_size, precision);
		bind_var_char(p, static_cast<int>(precision));
	}

	void BoundDatum::reserve_var_char(const size_t precision, const size_t array_len)
	{
		js_type = JS_STRING;
		c_type = SQL_C_CHAR;
		sql_type = precision > 8000 ? SQL_WLONGVARCHAR : SQL_VARCHAR;
		if (precision == 4000) {
			sql_type = SQL_WCHAR;
		}
		digits = 0;
		_indvec[0] = SQL_NULL_DATA;
		_indvec.resize(array_len);
		_storage->ReserveChars(max(1, static_cast<int>(array_len * precision)));	
		auto* itr_p = _storage->charvec_ptr->data();
		buffer = itr_p;
		buffer_len = precision;
		param_size = max(buffer_len, static_cast<SQLLEN>(1));
	}
	
	void BoundDatum::bind_var_char(const Local<Value>& p, const int precision)
	{
		reserve_var_char(precision, 1);
		if (!p->IsNullOrUndefined())
		{	
			const auto str_param = Nan::To<String>(p).FromMaybe(Nan::EmptyString());
			Nan::Utf8String x(str_param);
			auto *x_p = *x;
			memcpy(_storage->charvec_ptr->data(), x_p, precision);	
			_indvec[0] = precision;	
		}
	}
	
	int get_max_str_len(const Local<Value>& p)
	{
		auto str_len = 0;
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		for (uint32_t i = 0; i < len; ++i)
		{	
			auto elem = Nan::Get(arr, i);
			if (elem.IsEmpty()) continue;
			auto maybe_value = Nan::To<String>(elem.ToLocalChecked());
			const auto str = maybe_value.FromMaybe(Nan::EmptyString()); 	
			if (str->Length() > str_len) {
				str_len = str->Length();
			}		
		}
		return str_len;
	}

	void BoundDatum::bind_var_char_array(const Local<Value>& p)
	{
		const auto max_str_len = max(1, get_max_str_len(p));
		const auto arr = Local<Array>::Cast(p);
		const auto array_len = arr->Length();
		reserve_var_char(max_str_len, array_len);
		auto itr = _storage->charvec_ptr->begin();
		for (uint32_t i = 0; i < array_len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			auto elem = Nan::Get(arr, i);
			if (elem.IsEmpty()) continue;
			auto maybe_value = Nan::To<String>(elem.ToLocalChecked());
			const auto str = maybe_value.FromMaybe(Nan::EmptyString()); 	
			const auto width = str->Length();
			_indvec[i] = width;
			Nan::Utf8String x(str);
			auto *x_p = *x;
			memcpy(&*itr, x_p, max_str_len);
			itr += max_str_len;
		}
	}
	
	void BoundDatum::reserve_w_var_char_array(const size_t max_str_len, const size_t array_len)
	{
		js_type = JS_STRING;
		c_type = SQL_C_WCHAR;
		sql_type = max_str_len > 2000 && max_str_len < 4000 ? SQL_WLONGVARCHAR : SQL_WVARCHAR;
		const auto size = sizeof(uint16_t);
		_indvec.resize(array_len);
		_storage->ReserveUint16(array_len * max_str_len);
		buffer = _storage->uint16vec_ptr->data();
		buffer_len = max_str_len * size;
		if (max_str_len >= 4000)
		{
			param_size = 0;
		}
		else
		{
			param_size = max(buffer_len, static_cast<SQLLEN>(1));
		}
	}

	void BoundDatum::bind_w_var_char_array(const Local<Value>& p)
	{
		const auto max_str_len = max(1, get_max_str_len(p));
		const auto arr = Local<Array>::Cast(p);
		const auto array_len = arr->Length();
		reserve_w_var_char_array(max_str_len, array_len);
		auto* const base = _storage->uint16vec_ptr->data();
		for (uint32_t i = 0; i < array_len; ++i)
		{
			const auto size = sizeof(uint16_t);
			auto* const itr = base + (max_str_len * i);
			_indvec[i] = SQL_NULL_DATA;
			auto elem = Nan::Get(arr, i);
			if (elem.IsEmpty()) continue;
			const auto local_elem = elem.ToLocalChecked();
			if (local_elem->IsNullOrUndefined()) continue;
			auto maybe_value = Nan::To<String>(local_elem);
			const auto str = maybe_value.FromMaybe(Nan::EmptyString()); 	
			const auto width = str->Length() * size;
			_indvec[i] = width;
			Nan::DecodeWrite(reinterpret_cast<char*>(&*itr), str->Length()*2, str, Nan::UCS2);
		}
	}

	void BoundDatum::bind_w_var_char(const Local<Value>& p, const int precision)
	{
		const size_t max_str_len = max(1, precision);
		reserve_w_var_char_array(max_str_len, 1);	
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{
			const auto size = sizeof(uint16_t);
			const auto str_param = Nan::To<String>(p).FromMaybe(Nan::EmptyString());
			auto* const first_p = _storage->uint16vec_ptr->data();
			Nan::DecodeWrite(reinterpret_cast<char*>(first_p), str_param->Length()*2, str_param, Nan::UCS2);
			buffer_len = precision * size;
			if (precision >= 4000)
			{
				param_size = 0;
			}
			else
			{
				param_size = max(buffer_len, static_cast<SQLLEN>(1));
			}
			_indvec[0] = buffer_len;
		}
	}

	size_t get_max_object_len(const Local<Value>& p)
	{
		size_t obj_len = 0;
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		for (uint32_t i = 0; i < len; ++i)
		{
			auto elem = Nan::Get(arr, i);
			if (elem.IsEmpty()) continue;
			const auto local = elem.ToLocalChecked();
			if (local->IsNullOrUndefined()) continue;
			auto maybe_value = Nan::To<Object>(local);
			if (maybe_value.IsEmpty()) continue;
			const auto local_instance = maybe_value.ToLocalChecked();
			const auto width = node::Buffer::Length(local_instance);
			if (width > obj_len) obj_len = width;
		}
		return obj_len;
	}

	void BoundDatum::bind_long_var_binary(Local<Value>& p)
	{
		bind_var_binary(p);
		sql_type = SQL_LONGVARBINARY;
	}

	void BoundDatum::reserve_var_binary_array(const size_t max_obj_len, const size_t array_len)
	{
		js_type = JS_BUFFER;
		c_type = SQL_C_BINARY;
		sql_type = max_obj_len > 2000 ? SQL_LONGVARBINARY : SQL_VARBINARY;
		digits = 0;
		const auto size = sizeof(uint8_t);
		_storage->ReserveChars(array_len * max_obj_len);
		_indvec.resize(array_len);
		buffer = _storage->charvec_ptr->data();
		buffer_len = max_obj_len * size;
		param_size = max_obj_len;
	}

	/*
	 *const auto r = SQLBindParameter(*_statement, current_param, datum.param_type, datum.c_type, datum.sql_type,
									  datum.param_size, datum.digits, datum.buffer, datum.buffer_len, datum.get_ind_vec().data());


									  retcode = SQLBindParameter(
									  hstmt,              // Statement handle
				current_param		1,                  // Parameter Number
				param_type			SQL_PARAM_INPUT,    // Input/Output Type (always INPUT for TVP)
				c_type				SQL_C_DEFAULT,      // C - Type (always this for a TVP)
				sql_type			SQL_SS_TABLE,       // SQL Type (always this for a TVP)
				param_size			MAX_ARRAY_SIZE,     // For a TVP this is max rows we will use
				digits				0,                  // For a TVP this is always 0
				buffer				TVPTableName,       // For a TVP this is the type name of the
									  // TVP, and also a token returned by
									  // SQLParamData.
				buffer_len			SQL_NTS,            // For a TVP this is the length of the type
									  // name or SQL_NTS.
									  &lTVPRowsUsed);     // For a TVP this is the number of rows
									  // actually available.
	 */

	static int get_row_count(Local<Value>& p)
	{
		auto rows = 1;
		auto maybe_object = Nan::To<Object>(p);
		if (maybe_object.IsEmpty()) return -1;
		const auto local = maybe_object.ToLocalChecked();	
		auto maybe_get = Nan::Get(local, Nan::New<String>("row_count").FromMaybe(Nan::EmptyString()));
		if (maybe_get.IsEmpty()) return rows;
		const auto int32 = Nan::To<int>(maybe_get.ToLocalChecked());
		rows = int32.FromMaybe(1);
		return rows;
	}

	wstring wide_from_js_string(const Local<String> s)
	{		
		wstring_convert<codecvt_utf8_utf16<wchar_t>> converter;
		Nan::Utf8String x(s);
		auto *x_p = *x;
		const string narrow = x_p;
		auto wide = converter.from_bytes(narrow);
		return wide;
	}

	void BoundDatum::bind_tvp(Local<Value>& p)
	{
		wstring_convert<codecvt_utf8_utf16<wchar_t>> converter;
		// string narrow = converter.to_bytes(wide_utf16_source_string);
		// fprintf(stderr, "bind tvp\n");
		is_tvp = true;
		param_type = SQL_PARAM_INPUT;
		c_type = SQL_C_DEFAULT;
		sql_type = SQL_SS_TABLE;
		const auto rows = get_row_count(p);
		const auto type_id_str = get_as_string(p, "type_id");
		const auto schema_str = get_as_string(p, "schema");
		
		if (!schema_str->IsNullOrUndefined())
		{
			_storage->schema = wide_from_js_string(schema_str);
		}
		_indvec.resize(1);
		const auto precision = type_id_str->Length();
		_storage->ReserveChars(static_cast<size_t>(precision) + 1);
		_storage->ReserveUint16(static_cast<size_t>(precision) + 1);
		auto* itr_p = _storage->charvec_ptr->data();
		Nan::Utf8String x(type_id_str);
		auto *x_p = *x;
		memcpy(itr_p, x_p, precision);
		//type_id_str->WriteUtf8(fact.isolate, itr_p, precision);
		const string narrow = _storage->charvec_ptr->data();
		const auto type_name = converter.from_bytes(narrow);
		auto type_name_vec = wstr2wcvec(type_name);
		const auto size = sizeof(type_name_vec[0]);
		memcpy(static_cast<void*>(_storage->uint16vec_ptr->data()), type_name_vec.data(), precision * size);
		buffer = _storage->uint16vec_ptr->data();
		buffer_len = precision * size;
		param_size = rows; // max no of rows.
		_indvec[0] = rows; // no of rows.
		digits = 0;
	}

	void BoundDatum::bind_var_binary(Local<Value>& p)
	{
		Local<Object> o;
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined()) {
		 	o = p.As<Object>();
		}
		const auto valid = !p->IsNullOrUndefined() && !o->IsNullOrUndefined();
		const auto obj_len = valid ? node::Buffer::Length(o) : 0;
		reserve_var_binary_array(obj_len, 1);

		if (valid)
		{
			const auto itr = _storage->charvec_ptr->begin();
			auto* const ptr = node::Buffer::Data(o);
			_indvec[0] = obj_len;
			memcpy(&*itr, ptr, obj_len);
		}
	}

	void BoundDatum::bind_var_binary_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto array_len = arr->Length();
		const auto max_obj_len = get_max_object_len(p);
		reserve_var_binary_array(max_obj_len, array_len);
		auto* const base = _storage->charvec_ptr->data();
		for (uint32_t i = 0; i < array_len; ++i)
		{
			auto* const itr = base + (max_obj_len * i);
			_indvec[i] = SQL_NULL_DATA;
			auto elem = Nan::Get(arr, i);
			if (elem.IsEmpty()) continue;
			const auto toLocal = elem.ToLocalChecked();
			if (toLocal->IsNullOrUndefined()) continue;
			auto maybe_value = Nan::To<Object>(toLocal);
			if (maybe_value.IsEmpty()) continue;
			const auto local_instance = maybe_value.ToLocalChecked();
			if (local_instance->IsNullOrUndefined()) continue;
			auto* const ptr = node::Buffer::Data(local_instance);
			const auto obj_len = node::Buffer::Length(local_instance);
			_indvec[i] = obj_len;
			memcpy(&*itr, ptr, obj_len);		
		}
	}

	void BoundDatum::bind_boolean(const Local<Value>& p)
	{
		reserve_boolean(1);
		auto& vec = *_storage->charvec_ptr;
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{
			const auto v = MutateJS::as_boolean(p);
			vec[0] = !v ? 0 : 1;
			_indvec[0] = 0;
		}
	}

	void BoundDatum::bind_boolean_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_boolean(len);
		auto& vec = *_storage->charvec_ptr;	
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			const auto elem = Nan::Get(arr, i).ToLocalChecked();
			if (!elem->IsNullOrUndefined())
			{
				const auto v = MutateJS::as_boolean(elem);
				const auto b = !v ? 0 : 1;
				vec[i] = static_cast<char>(b);
				_indvec[i] = 0;
			}
		}
	}

	void BoundDatum::reserve_boolean(const SQLLEN len)
	{
		const auto size = sizeof(char);
		buffer_len = len * size;
		_storage->ReserveChars(len);
		_indvec.resize(len);
		js_type = JS_BOOLEAN;
		c_type = SQL_C_BIT;
		sql_type = SQL_BIT;
		buffer = _storage->charvec_ptr->data();
		param_size = size;
		digits = 0;
	}

	void BoundDatum::bind_numeric(const Local<Value>& p)
	{
		reserve_numeric(1);
		sql_type = SQL_NUMERIC;
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{
			const auto local = Nan::To<Number>(p).ToLocalChecked();
			const auto d = local->Value();
			auto& vec = *_storage->numeric_ptr;
			auto& ns = vec[0];
			encode_numeric_struct(d, static_cast<int>(param_size), digits, ns);
			param_size = ns.precision;
			digits = static_cast<unsigned char>(ns.scale);
			_indvec[0] = sizeof(SQL_NUMERIC_STRUCT);
		}
	}

	void BoundDatum::bind_numeric_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const int len = arr->Length();
		reserve_numeric(len);
		auto& vec = *_storage->numeric_ptr;
		for (auto i = 0; i < len; ++i)
		{
			auto& ns = vec[i];
			_indvec[i] = SQL_NULL_DATA;
			const auto elem = Nan::Get(arr, i).ToLocalChecked();
			if (!elem->IsNullOrUndefined())
			{
				const auto num = Nan::To<Number>(elem).ToLocalChecked();
				const auto d = num->Value();
				encode_numeric_struct(d, static_cast<int>(param_size), 0, ns);
				param_size = max(static_cast<unsigned int>(param_size), static_cast<unsigned int>(ns.precision));
				//digits = max(static_cast<unsigned char>(digits),static_cast<unsigned char>(ns.scale));
				digits = 0;
				_indvec[i] = sizeof(SQL_NUMERIC_STRUCT);
			}
		}
	}

	void BoundDatum::reserve_numeric(const SQLLEN len)
	{
		definedPrecision = true;
		buffer_len = len * sizeof(SQL_NUMERIC_STRUCT);
		_storage->ReserveNumerics(len);
		_indvec.resize(len);
		js_type = JS_NUMBER;
		c_type = SQL_C_NUMERIC;
		sql_type = SQL_NUMERIC;
		buffer = _storage->numeric_ptr->data();
	}

	void BoundDatum::bind_tiny_int(const Local<Value>& p)
	{
		bind_int32(p);
		sql_type = SQL_TINYINT;
	}

	void BoundDatum::bind_small_int(const Local<Value>& p)
	{
		bind_int32(p);
		sql_type = SQL_SMALLINT;
	}

	void BoundDatum::bind_int32(const Local<Value>& p)
	{
		reserve_int32(1);
		_indvec[0] = SQL_NULL_DATA;
		auto& vec = *_storage->int32vec_ptr;
		vec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{			
			const auto local = Nan::To<Int32>(p).FromMaybe(Nan::New<Int32>(0));	
			const auto d = local->Value();
			vec[0] = d;
			_indvec[0] = 0;
		}
	}

	void BoundDatum::bind_int32_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const int len = arr->Length();
		reserve_int32(len);
		auto& vec = *_storage->int32vec_ptr;

		for (auto i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			auto maybe_elem = Nan::Get(arr, i);
			if (!maybe_elem.IsEmpty()) {
				const auto local_elem = maybe_elem.ToLocalChecked();
				if (local_elem->IsNullOrUndefined()) continue;
				const auto local = Nan::To<Int32>(local_elem).FromMaybe(Nan::New<Int32>(0));
				vec[i] = local->Value();
				_indvec[i] = 0;
			}
		}
	}

	void BoundDatum::reserve_int32(const SQLLEN len)
	{
		const auto size = sizeof(int32_t);
		buffer_len = len * size;
		_storage->ReserveInt32(len);
		_indvec.resize(len);
		js_type = JS_INT;
		c_type = SQL_C_SLONG;
		sql_type = SQL_INTEGER;
		buffer = _storage->int32vec_ptr->data();
		param_size = size;
		digits = 0;
	}

	void BoundDatum::bind_uint32(const Local<Value>& p)
	{
		reserve_uint32(1);
		auto& vec = *_storage->uint32vec_ptr;
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{
			const auto local = Nan::To<Uint32>(p).FromMaybe(Nan::New<Uint32>(0));
			vec[0] = local->Value();
			_indvec[0] = 0;	
		}
	}

	void BoundDatum::bind_uint32_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_uint32(len);
		auto& vec = *_storage->uint32vec_ptr;
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			auto maybe_elem = Nan::Get(arr, i);
			if (!maybe_elem.IsEmpty()) {
				const auto local_elem = maybe_elem.ToLocalChecked();
				if (local_elem->IsNullOrUndefined()) continue;
				const auto local = Nan::To<Uint32>(local_elem).FromMaybe(Nan::New<Uint32>(0));
				vec[i] = local->Value();
				_indvec[i] = 0;
			}
		}
	}

	void BoundDatum::reserve_uint32(const SQLLEN len)
	{
		const auto size = sizeof(uint32_t);
		buffer_len = len * size;
		_storage->ReserveUInt32(len);
		_indvec.resize(len);
		js_type = JS_UINT;
		c_type = SQL_C_ULONG;
		sql_type = SQL_BIGINT;
		buffer = _storage->uint32vec_ptr->data();
		param_size = size;
		digits = 0;
	}

	void BoundDatum::bind_date(const Local<Value>& p)
	{
		reserve_date(1);
		auto& vec = *_storage->datevec_ptr;
		// Since JS dates have no timezone context, all dates are assumed to be UTC
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{
				const auto d = Local<Date>::Cast<Value>(p);
				auto& dt = vec[0];
				const auto local = Nan::To<Number>(d).ToLocalChecked() ;
				const auto ms = local->Value() - offset * 60000;
				const TimestampColumn sql_date(-1, ms);
				sql_date.ToDateStruct(dt);
				_indvec[0] = sizeof(SQL_DATE_STRUCT);
		}
	}

	void BoundDatum::bind_date_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_date(len);
		auto& vec = *_storage->datevec_ptr;
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			const auto elem = Nan::Get(arr, i).ToLocalChecked();
			if (!elem->IsNullOrUndefined())
			{
				const auto d = Local<Date>::Cast<Value>(elem);
				auto& dt = vec[i];
				const auto local = Nan::To<Number>(d).ToLocalChecked() ;
				const auto ms = local->Value() - offset * 60000;
				const TimestampColumn sql_date(-1, ms);
				sql_date.ToDateStruct(dt);
				_indvec[i] = sizeof(SQL_DATE_STRUCT);
			}
		}
	}

	void BoundDatum::reserve_date(SQLLEN len)
	{
		buffer_len = sizeof(SQL_DATE_STRUCT);
		_storage->datevec_ptr = make_shared<vector<SQL_DATE_STRUCT>>(len);
		_indvec.resize(len);
		// Since JS dates have no timezone context, all dates are assumed to be UTC		
		js_type = JS_DATE;
		c_type = SQL_C_TYPE_DATE;
		// TODO: Determine proper SQL type based on version of server we're talking to
		sql_type = SQL_TYPE_DATE;
		buffer = _storage->datevec_ptr->data();
		// TODO: Determine proper precision and size based on version of server we're talking to
		if (param_size <= 0)
			param_size = sql_server_2008_default_datetime_precision;
		digits = sql_server_2008_default_datetime_scale;
	}

	void BoundDatum::bind_time_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_time(len);
		auto& vec = *_storage->time2vec_ptr;
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			const auto elem = Nan::Get(arr, i).ToLocalChecked();
			if (!elem->IsNullOrUndefined())
			{
				// dates in JS are stored internally as ms count from Jan 1, 1970
				const auto d = Local<Date>::Cast<Value>(elem);
				auto& time2 = vec[i];
				const auto local = Nan::To<Number>(d).ToLocalChecked() ;
				const auto ms = local->Value() - offset * 60000;
				const TimestampColumn sql_date(-1, ms);
				sql_date.ToTime2Struct(time2);
				_indvec[i] = sizeof(SQL_SS_TIME2_STRUCT);
			}
		}
	}

	void BoundDatum::bind_time(const Local<Value>& p)
	{
		reserve_time(1);
		// Since JS dates have no timezone context, all dates are assumed to be UTC
		_indvec[0] = SQL_NULL_DATA;
		auto& vec = *_storage->time2vec_ptr;
		if (!p->IsNullOrUndefined())
		{
				const auto d = Local<Date>::Cast<Value>(p);
				auto& time2 = vec[0];
				const auto local = Nan::To<Number>(d).ToLocalChecked() ;
				const auto ms = local->Value() - offset * 60000;
				const TimestampColumn sql_date(-1, ms);
				sql_date.ToTime2Struct(time2);
				_indvec[0] = sizeof(SQL_SS_TIME2_STRUCT);
		}
	}

	void BoundDatum::reserve_time(const SQLLEN len)
	{
		buffer_len = len * sizeof(SQL_SS_TIME2_STRUCT);
		_storage->Reservetime2(len);
		_indvec.resize(len);
		// Since JS dates have no timezone context, all dates are assumed to be UTC		
		js_type = JS_DATE;
		c_type = SQL_C_BINARY;
		// TODO: Determine proper SQL type based on version of server we're talking to
		sql_type = SQL_SS_TIME2;
		buffer = _storage->time2vec_ptr->data();
		// TODO: Determine proper precision and size based on version of server we're talking to

		param_size = sql_server_2008_default_time_precision;
		if (digits <= 0) digits = sql_server_2008_default_datetime_scale;
	}

	void BoundDatum::bind_time_stamp(const Local<Value>& p)
	{
		reserve_time_stamp(1);
		_indvec[0] = SQL_NULL_DATA;
		auto& vec = *_storage->timestampvec_ptr;
		if (!p->IsNullOrUndefined())
		{
			// dates in JS are stored internally as ms count from Jan 1, 1970
			const auto d = Local<Date>::Cast<Value>(p);
			auto& ts = vec[0];
			const auto local = Nan::To<Number>(d).ToLocalChecked() ;
			const auto ms = local->Value() - offset * 60000;
			const TimestampColumn sql_date(-1, ms);
			sql_date.to_timestamp_struct(ts);
			_indvec[0] = buffer_len;
		}
	}

	void BoundDatum::bind_time_stamp_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_time_stamp(len);
		auto& vec = *_storage->timestampvec_ptr;
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			const auto elem = Nan::Get(arr, i).ToLocalChecked();
			if (!elem->IsNullOrUndefined())
			{
				// dates in JS are stored internally as ms count from Jan 1, 1970
				const auto d = Local<Date>::Cast<Value>(elem);
				auto& ts = vec[i];
				const auto local = Nan::To<Number>(d).ToLocalChecked() ;
				const auto ms = local->Value() - offset * 60000;
				const TimestampColumn sql_date(-1, ms);
				sql_date.to_timestamp_struct(ts);
				_indvec[i] = buffer_len;
			}
		}
	}

	void BoundDatum::reserve_time_stamp(const SQLLEN len)
	{
		buffer_len = len * sizeof(SQL_TIMESTAMP_STRUCT);
		_storage->ReserveTimestamp(len);
		_indvec.resize(len);
		// Since JS dates have no timezone context, all dates are assumed to be UTC		
		js_type = JS_DATE;
		c_type = SQL_C_TIMESTAMP;
		// TODO: Determine proper SQL type based on version of server we're talking to
		sql_type = SQL_TYPE_TIMESTAMP;
		buffer = _storage->timestampvec_ptr->data();
		// TODO: Determine proper precision and size based on version of server we're talking to
		param_size = sql_server_2008_default_timestamp_precision;
		if (digits <= 0) digits = sql_server_2008_default_datetime_scale;
	}

	void BoundDatum::bind_time_stamp_offset(const Local<Value>& p)
	{
		reserve_time_stamp_offset(1);
		// Since JS dates have no timezone context, all dates are assumed to be UTC
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{
			const auto date_object = Local<Date>::Cast<Value>(p);
			assert(!date_object.IsEmpty());
			// dates in JS are stored internally as ms count from Jan 1, 1970
			const auto local = Nan::To<Number>(date_object).ToLocalChecked();
			auto& ts = (*_storage->timestampoffsetvec_ptr)[0];
			const TimestampColumn sql_date(-1, local->Value(), 0, offset);
			sql_date.to_timestamp_offset(ts);
			_indvec[0] = buffer_len;
		}
	}

	void BoundDatum::reserve_time_stamp_offset(SQLLEN len)
	{
		buffer_len = sizeof(SQL_SS_TIMESTAMPOFFSET_STRUCT);
		_storage->timestampoffsetvec_ptr = make_shared<vector<SQL_SS_TIMESTAMPOFFSET_STRUCT>>(len);
		_indvec.resize(len);
		// Since JS dates have no timezone context, all dates are assumed to be UTC		
		js_type = JS_DATE;
		c_type = SQL_C_BINARY;
		// TODO: Determine proper SQL type based on version of server we're talking to
		sql_type = SQL_SS_TIMESTAMPOFFSET;
		buffer = _storage->timestampoffsetvec_ptr->data();
		// TODO: Determine proper precision and size based on version of server we're talking to
		param_size = sql_server_2008_default_datetime_precision;
		if (digits <= 0) digits = sql_server_2008_default_datetime_scale;
	}

	void BoundDatum::bind_time_stamp_offset_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_time_stamp_offset(len);
		auto& vec = *_storage->timestampoffsetvec_ptr;
		buffer_len = sizeof(SQL_SS_TIMESTAMPOFFSET_STRUCT);
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			const auto elem = Nan::Get(arr, i).ToLocalChecked();
			if (!elem->IsNullOrUndefined())
			{
				_indvec[i] = sizeof(SQL_SS_TIMESTAMPOFFSET_STRUCT);
				const auto d = Local<Date>::Cast<Value>(elem);
				auto& ts = vec[i];
				const auto local = Nan::To<Number>(d).ToLocalChecked();
				TimestampColumn sql_date(-1, local->Value(), 0, offset);
				sql_date.to_timestamp_offset(ts);
			}
		}
	}

	void BoundDatum::bind_integer(const Local<Value>& p)
	{
		reserve_integer(1);
		auto& vec = *_storage->int64vec_ptr;
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{
			const auto local = Nan::To<Number>(p).ToLocalChecked();
			vec[0] = static_cast<long long>(local->Value());
			_indvec[0] = 0;
		}
	}

	void BoundDatum::reserve_integer(const SQLLEN len)
	{
		const auto size = sizeof(int64_t);
		_storage->ReserveInt64(len);
		_indvec.resize(len);
		js_type = JS_NUMBER;
		c_type = SQL_C_SBIGINT;
		sql_type = SQL_BIGINT;
		buffer = _storage->int64vec_ptr->data();
		buffer_len = size * len;
		param_size = size;
		digits = 0;
	}

	void BoundDatum::bind_integer_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_integer(len);
		auto& vec = *_storage->int64vec_ptr;
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			const auto elem = Nan::Get(arr, i).ToLocalChecked();
			if (!elem->IsNullOrUndefined())
			{
				_indvec[i] = 0;
				const auto v = Nan::To<int64_t>(elem).ToChecked();
				vec[i] = v;
			}
		}
	}

	void BoundDatum::bind_float(const Local<Value>& p)
	{
		bind_double(p);
		sql_type = SQL_FLOAT;
	}

	void BoundDatum::bind_real(const Local<Value>& p)
	{
		bind_double(p);
		sql_type = SQL_REAL;
	}

	void BoundDatum::bind_double(const Local<Value>& p)
	{
		reserve_double(1);
		auto& vec = *_storage->doublevec_ptr;
		_indvec[0] = SQL_NULL_DATA;
		if (!p->IsNullOrUndefined())
		{
			const auto v = Nan::To<double>(p).ToChecked();
			vec[0] = v;
			_indvec[0] = 0;	
		}
	}

	void BoundDatum::reserve_double(const SQLLEN len)
	{
		const auto size = sizeof(double);
		_storage->ReserveDouble(len);
		_indvec.resize(len);
		js_type = JS_NUMBER;
		c_type = SQL_C_DOUBLE;
		sql_type = SQL_DOUBLE;
		buffer = _storage->doublevec_ptr->data();
		buffer_len = size * len;
		param_size = size;
		digits = 0;
	}

	void BoundDatum::bind_double_array(const Local<Value>& p)
	{
		const auto arr = Local<Array>::Cast(p);
		const auto len = arr->Length();
		reserve_double(len);
		auto& vec = *_storage->doublevec_ptr;
		for (uint32_t i = 0; i < len; ++i)
		{
			_indvec[i] = SQL_NULL_DATA;
			auto maybe = Nan::Get(arr, i);
			if (maybe.IsEmpty()) continue;
			const auto local_elem = maybe.ToLocalChecked();
			if (local_elem->IsNullOrUndefined()) continue;
			const auto v = Nan::To<double>(local_elem).ToChecked();
			vec[i] = v;
			_indvec[i] = 0;
		}
	}

	void BoundDatum::bind_number(const Local<Value>& p)
	{
		// numbers can be either integers or doubles.  We attempt to determine which it is through a simple
		// cast and equality check
		const nodeTypeFactory fact;
		const auto context = fact.isolate->GetCurrentContext();
		const auto maybe = p->ToNumber(context);
		Local<Number> local;
		if (maybe.ToLocal(&local)) {
			const auto d = static_cast<long double>(local->Value());
			if (d == floor(d) &&
				d >= static_cast<long double>(numeric_limits<int64_t>::min()) &&
				d <= static_cast<long double>(numeric_limits<int64_t>::max()))
			{
				bind_integer(p);
			}
			else
			{
				bind_double(p);
			}
		}
	}

	void BoundDatum::bind_number_array(const Local<Value>& pp)
	{
		const nodeTypeFactory fact;
		const auto context = fact.isolate->GetCurrentContext();
		const auto arr = Local<Array>::Cast(pp);
		const auto maybe_elem = arr->Get(context, 0);
		Local<Value> p;
		if (maybe_elem.ToLocal(&p)) {
			const auto maybe = p->ToNumber(context);
			Local<Number> local;
			if (maybe.ToLocal(&local)) {
				const auto d = static_cast<long double>(local->Value());
				if (d == floor(d) &&
					d >= static_cast<long double>(numeric_limits<int64_t>::min()) &&
					d <= static_cast<long double>(numeric_limits<int64_t>::max()))
				{
					bind_integer_array(pp);
				}
				else
				{
					bind_double_array(pp);
				}
			}
		}
	}

	bool BoundDatum::bind(const Local<Object> o, const char* if_str, const uint16_t type)
	{
		auto val = Nan::Get(o, Nan::New(if_str).ToLocalChecked()).ToLocalChecked();
		if (!val->IsUndefined())
		{
			param_type = type;
			return bind_datum_type(val);
		}
		return false;
	}

	bool is_numeric(wstring& v)
	{
		const auto res = v == L"numeric"
			|| v == L"decimal"
			|| v == L"smallmoney"
			|| v == L"money"
			|| v == L"float"
			|| v == L"real";
		return res;
	}

	bool is_int(const wstring& v)
	{
		const auto res = v == L"smallint"
			|| v == L"int"
			|| v == L"bigint"
			|| v == L"tinyint";
		return res;
	}

	bool is_string(const wstring& v)
	{
		const auto res = v == L"char"
			|| v == L"text"
			|| v == L"varchar";
		return res;
	}

	bool is_binary(const wstring& v)
	{
		const auto res = v == L"binary";
		return res;
	}

	bool is_bit(const wstring& v)
	{
		const auto res = v == L"bit";
		return res;
	}

	bool is_date(const wstring& v)
	{
		const auto res = v == L"date"
			|| v == L"datetimeoffset"
			|| v == L"datetime2"
			|| v == L"smalldatetime"
			|| v == L"datetime"
			|| v == L"time";
		return res;
	}


	bool sql_type_s_maps_to_numeric(const Local<Value> p)
	{
		const auto str = get_as_string(p, "type_id");
		auto v = FromV8String(str);
		const auto res = is_numeric(v);
		return res;
	}

	bool sql_type_s_maps_to_u_int32(const Local<Value> p)
	{
		const auto str = get_as_string(p, "type_id");
		const auto v = FromV8String(str);
		const auto res = v == L"sbigint";
		return res;
	}

	bool sql_type_s_maps_to_int32(const Local<Value> p)
	{
		const auto str = get_as_string(p, "type_id");
		const auto v = FromV8String(str);
		const auto res = is_int(v);
		return res;
	}

	bool sql_type_s_maps_to_string(const Local<Value> p)
	{
		const auto str = get_as_string(p, "type_id");
		const auto v = FromV8String(str);
		const auto res = is_string(v);
		return res;
	}

	bool sql_type_s_maps_to_boolean(const Local<Value> p)
	{
		const auto str = get_as_string(p, "type_id");
		const auto v = FromV8String(str);
		const auto res = is_bit(v);
		return res;
	}

	bool sql_type_s_maps_to_date(const Local<Value> p)
	{
		const auto str = get_as_string(p, "type_id");
		const auto v = FromV8String(str);
		const auto res = is_date(v);
		return res;
	}

	bool BoundDatum::bind_datum_type(Local<Value>& p)
	{
		const nodeTypeFactory fact;
		const auto context = fact.isolate->GetCurrentContext();
		if (p->IsNull())
		{
			bind_null(p);
		}
		else if (p->IsString())
		{
			bind_w_var_char(p);
		}
		else if (p->IsBoolean())
		{
			bind_boolean(p);
		}
		else if (p->IsInt32())
		{
			bind_int32(p);
		}
		else if (p->IsUint32())
		{
			bind_uint32(p);
		}
		else if (p->IsNumber())
		{
			const auto maybe = p->ToNumber(context);
			Local<Number> local;
			if (maybe.ToLocal(&local)) {
				const auto d = local->Value();
				if (isnan(d) || !isfinite(d))
				{
					err = const_cast<char*>("Invalid number parameter");
					return false;
				}
				bind_number(p);
			}
		}
		else if (p->IsDate())
		{
			bind_time_stamp_offset(p);
		}
		else if (p->IsObject() && node::Buffer::HasInstance(p))
		{
			bind_var_binary(p);
		}
		else if (sql_type_s_maps_to_tvp(p))
		{
			bind_tvp(p);
		}
		else
		{
			err = const_cast<char*>("Invalid parameter type");
			return false;
		}

		return true;
	}

	Local<Value> reserve_output_param(const Local<Value> p, const int size)
	{
		Local<Value> pval;
		const nodeTypeFactory fact;

		if (sql_type_s_maps_to_int32(p) || sql_type_s_maps_to_boolean(p))
		{
			pval = fact.new_int32(0);
		}
		else if (sql_type_s_maps_to_u_int32(p))
		{
			pval = Nan::New<Integer>(0);
		}
		else if (sql_type_s_maps_to_numeric(p))
		{
			pval = Nan::New(0.0);
		}
		else if (sql_type_s_maps_to_date(p))
		{
			pval = fact.new_date();
		}
		else if (sql_type_s_maps_to_string(p))
		{
			vector<char> b;
			b.resize(static_cast<size_t>(size) + 1);
			pval = fact.new_string(b.data(), size + 1);
		}
		else
		{
			pval = fact.new_buffer(size);
		}
		return pval;
	}

	inline Local<Value> get(const char * key,Local<Object> local_object ) {
		return Nan::Get(local_object, Nan::New(key).ToLocalChecked()).ToLocalChecked();
	}

	bool BoundDatum::proc_bind(Local<Value>& p, Local<Value>& v)
	{
		const auto context = Nan::GetCurrentContext();
		const auto maybe_is_output = v->ToInteger(context);
		Local<Integer> is_output;
		if (!maybe_is_output.ToLocal(&is_output))
		{
			return false;
		}

		Local<Value> pval;
		const auto maybe_object = p->ToObject(context);
		Local<Object> local_object;
		if (!maybe_object.ToLocal(&local_object))
		{
			return false;
		}
		const auto maybe_size = get("max_length", local_object)->Int32Value(context);
		auto size = 0;
		if (!maybe_size.To(&size))
		{
			return false;
		}

		Local<Object> as_object;
		if (p->ToObject(context).ToLocal(&as_object))
		{
			pval = get("val", as_object);
		} else {
			pval = Nan::Null();
		}
		
		auto is_output_i = 0;
		if (!is_output->Int32Value(context).To(&is_output_i))
		{
			return false;
		}
		if (is_output_i != 0)
		{
			if (pval->IsNull()) {
				param_type = SQL_PARAM_OUTPUT;
				pval = reserve_output_param(p, size);
			} else {
				param_type = SQL_PARAM_INPUT_OUTPUT;
			}	
		}
		else
		{
			param_type = SQL_PARAM_INPUT;
		}

		return bind_datum_type(pval);
	}

	void BoundDatum::assign_precision(Local<Object>& pv)
	{
		const nodeTypeFactory fact;
		const auto context = fact.isolate->GetCurrentContext();
		const auto precision = get("precision", pv);
		if (!precision->IsUndefined())
		{
			const auto maybe_param_size = precision->Int32Value(context);
			param_size = maybe_param_size.FromMaybe(0);
		}

		const auto scale = get("scale", pv);
		if (!scale->IsUndefined())
		{
			const auto maybe_digits = scale->Int32Value(context);
			digits = static_cast<SQLSMALLINT>(maybe_digits.FromMaybe(0));
		}

		const auto off = get("offset", pv);
		if (!off->IsUndefined())
		{
			const auto maybe_offset = off->Int32Value(context);
			offset = static_cast<int32_t>(maybe_offset.FromMaybe(0));
		}
	}

	void BoundDatum::sql_longvarbinary(Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_var_binary_array(pp);
		}
		else
		{
			bind_long_var_binary(pp);
		}
	}

	void BoundDatum::sql_integer(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_int32_array(pp);
		}
		else
		{
			bind_int32(pp);
		}
	}

	void BoundDatum::sql_wvarchar(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_w_var_char_array(pp);
		}
		else
		{
			bind_w_var_char(pp);
		}
	}

	void BoundDatum::sql_wlongvarchar(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_w_var_char_array(pp);
		}
		else
		{
			bind_w_long_var_char(pp);
		}
	}

	void BoundDatum::sql_bit(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_boolean_array(pp);
		}
		else
		{
			bind_boolean(pp);
		}
	}

	void BoundDatum::sql_bigint(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_integer_array(pp);
		}
		else
		{
			bind_integer(pp);
		}
	}

	void BoundDatum::sql_double(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_double_array(pp);
		}
		else
		{
			bind_double(pp);
		}
	}

	void BoundDatum::sql_float(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_double_array(pp);
			sql_type = SQL_FLOAT;
		}
		else
		{
			bind_float(pp);
		}
	}

	void BoundDatum::sql_real(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_double_array(pp);
			sql_type = SQL_REAL;
		}
		else
		{
			bind_real(pp);
		}
	}

	void BoundDatum::sql_tinyint(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_int32_array(pp);
			sql_type = SQL_TINYINT;
		}
		else
		{
			bind_tiny_int(pp);
		}
	}

	void BoundDatum::sql_smallint(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_uint32_array(pp);
			sql_type = SQL_SMALLINT;
		}
		else
		{
			bind_small_int(pp);
		}
	}

	void BoundDatum::sql_numeric(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_double_array(pp);
		}
		else
		{
			bind_numeric(pp);
		}
	}

	void BoundDatum::sql_char(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_w_var_char_array(pp);
		}
		else
		{
			bind_char(pp);
		}
	}

	void BoundDatum::sql_varchar(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_w_var_char_array(pp);
		}
		else
		{
			bind_w_var_char(pp);
		}
	}

	void BoundDatum::sql_ss_time2(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_time_array(pp);
		}
		else
		{
			bind_time(pp);
		}
	}

	void BoundDatum::sql_type_date(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_date_array(pp);
		}
		else
		{
			bind_date(pp);
		}
	}

	void BoundDatum::sql_type_timestamp(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_time_stamp_array(pp);
		}
		else
		{
			bind_time_stamp(pp);
		}
	}

	void BoundDatum::sql_ss_timestampoffset(const Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_time_stamp_offset_array(pp);
		}
		else
		{
			bind_time_stamp_offset(pp);
		}
	}

	void BoundDatum::sql_varbinary(Local<Value> pp)
	{
		if (pp->IsArray())
		{
			bind_var_binary_array(pp);
		}
		else
		{
			if (pp->IsNull()
				|| (pp->IsObject() && node::Buffer::HasInstance(pp)))
			{
				bind_var_binary(pp);
			}
			else
			{
				err = const_cast<char*>("Invalid parameter type");
			}
		}
	}

	bool BoundDatum::user_bind(Local<Value>& p, Local<Value>& v)
	{
		nodeTypeFactory fact;
		const auto context = fact.isolate->GetCurrentContext();
		const auto maybe_sql_type = v->Int32Value(fact.isolate->GetCurrentContext());
		const auto local_sql_type = maybe_sql_type.FromMaybe(0);
		if (local_sql_type == 0) return false;
		sql_type = static_cast<SQLSMALLINT>(local_sql_type);
		param_type = SQL_PARAM_INPUT;

		const auto maybe_local = p->ToObject(context);
		Local<Object> as_local;
		if (!maybe_local.ToLocal(&as_local))
		{
			return false;
		}

		const auto pp = get("value", as_local);

		assign_precision(as_local);

		switch (sql_type)
		{
		case SQL_LONGVARBINARY:
			sql_longvarbinary(pp);
			break;

		case SQL_VARBINARY:
		{
			sql_varbinary(pp);
			if (err) return false;
		}
		break;

		case SQL_INTEGER:
			sql_integer(pp);
			break;

		case SQL_WVARCHAR:
			sql_wvarchar(pp);
			break;

		case SQL_WLONGVARCHAR:
			sql_wlongvarchar(pp);
			break;

		case SQL_BIT:
			sql_bit(pp);
			break;

		case SQL_BIGINT:
			sql_bigint(pp);
			break;

		case SQL_DOUBLE:
			sql_double(pp);
			break;

		case SQL_FLOAT:
			sql_float(pp);
			break;

		case SQL_REAL:
			sql_real(pp);
			break;

		case SQL_TINYINT:
			sql_tinyint(pp);
			break;

		case SQL_SMALLINT:
			sql_smallint(pp);
			break;

		case SQL_NUMERIC:
			sql_numeric(pp);
			break;

		case SQL_CHAR:
			sql_char(pp);
			break;

		case SQL_VARCHAR:
			sql_varchar(pp);
			break;

		case SQL_SS_TIME2:
			sql_ss_time2(pp);
			break;

		case SQL_TYPE_DATE:
			sql_type_date(pp);
			break;

		case SQL_TYPE_TIMESTAMP:
			sql_type_timestamp(pp);
			break;

		case SQL_SS_TIMESTAMPOFFSET:
			sql_ss_timestampoffset(pp);
			break;

		case SQL_UNKNOWN_TYPE:
		default:
			return false;
		}

		return true;
	}

	bool BoundDatum::bind_object(Local<Value>& p)
	{
		// fprintf(stderr, "bind obj\n");
		const nodeTypeFactory fact;
		const auto context = fact.isolate->GetCurrentContext();
		const auto maybe_object = p->ToObject(context);
		Local<Object> po;
		if (!maybe_object.ToLocal(&po))
		{
			return false;
		}

		auto v = get("is_output", po);
		if (!v->IsUndefined())
		{
			return proc_bind(p, v);
		}

		v = get("sql_type", po);
		if (!v->IsUndefined())
		{
			return user_bind(p, v);
		}

		const auto n = get_as_string(p, "name");
		if (!n->IsUndefined())
		{
			name = wide_from_js_string(n);
			auto pp = get("value", po);
			return bind_datum_type(pp);
		}
		
		return false;
	}

	bool BoundDatum::bind_array(Local<Value>& pp)
	{
		const auto arr = Local<Array>::Cast(pp);
		nodeTypeCounter counts;

		for (uint32_t i = 0; i < arr->Length(); ++i)
		{
			auto p = Nan::Get(arr, i);
			const auto l = p.ToLocalChecked();
			counts.Decode(l);
		}

		if (counts.boolCount != 0)
		{
			bind_boolean_array(pp);
		}
		else if (counts.stringCount != 0)
		{
			bind_w_var_char_array(pp);
		}
		else if (counts.dateCount != 0)
		{
			bind_time_stamp_offset_array(pp);
		}
		else if (counts.bufferCount != 0)
		{
			bind_var_binary_array(pp);
		}
		else if (counts.getoutBoundsCount() > 0)
		{
			err = const_cast<char*>("Invalid number parameter");
			return false;
		}
		else if (counts.numberCount > 0 || (counts.int64Count > 0 && counts.int32Count > 0))
		{
			bind_double_array(pp);
		}
		else if (counts.int64Count > 0)
		{
			bind_integer_array(pp);
		}
		else if (counts.int32Count != 0)
		{
			bind_int32_array(pp);
		}
		else if (counts.uint32Count != 0)
		{
			bind_uint32_array(pp);
		}
		else if (counts.nullCount == static_cast<int>(arr->Length()))
		{
			bind_null_array(pp);
		}
		else
		{
			err = const_cast<char*>("Invalid parameter type");
			return false;
		}

		return true;
	}

	Local<Value> BoundDatum::unbind_null()
	{
		const nodeTypeFactory fact;
		return fact.null();
	}

	Local<Value> BoundDatum::unbind_string() const
	{
		const auto s = Nan::New<String>(_storage->uint16vec_ptr->data()).ToLocalChecked();
		return s;
	}

	Local<Value> BoundDatum::unbind_double() const
	{
		const auto& vec = *_storage->doublevec_ptr;
		const auto s = Nan::New(vec[0]);
		return s;
	}

	Local<Value> BoundDatum::unbind_boolean() const
	{
		const auto& vec = *_storage->uint16vec_ptr;
		const auto s = Nan::New<Boolean>(vec[0] != 0);
		return s;
	}

	Local<Value> BoundDatum::unbind_int32() const
	{		
		const auto& vec = *_storage->int32vec_ptr;
		const auto s = Nan::New<Int32>(vec[0]);
		return s;
	}

	Local<Value> BoundDatum::unbind_uint32() const
	{	
		const auto& vec = *_storage->uint32vec_ptr;
		const auto s = Nan::New<Integer>(vec[0]);
		return s;
	}

	Local<Value> BoundDatum::unbind_number() const
	{
		Local<Value> v;
		if (sql_type == SQL_C_DOUBLE)
		{
			v = unbind_double();
		}
		else
		{
			const nodeTypeFactory fact;
			auto& vec = *_storage->int64vec_ptr;
			v = fact.new_int64(vec[0]);
		}
		return v;
	}

	Local<Value> BoundDatum::unbind_date() const
	{
		const auto& vec = *_storage->timestampoffsetvec_ptr;
		TimestampColumn tsc(-1, vec[0]);
		return tsc.ToValue();
	}

	void BoundDatum::reserve_column_type(const SQLSMALLINT type, const size_t len, const size_t row_count)
	{
		switch (type)
		{
		case SQL_SS_VARIANT:
			reserve_w_var_char_array(len, row_count);
			break;

		case SQL_CHAR:
		case SQL_VARCHAR:
		case SQL_LONGVARCHAR:
		case SQL_WCHAR:
		case SQL_WVARCHAR:
		case SQL_WLONGVARCHAR:
		case SQL_SS_XML:
		case SQL_GUID:
			reserve_w_var_char_array(len + 1, row_count);
			break;

		case SQL_BIT:
			reserve_boolean(row_count);
			break;

		case SQL_SMALLINT:
		case SQL_TINYINT:
		case SQL_INTEGER:
		case SQL_C_SLONG:
		case SQL_C_SSHORT:
		case SQL_C_STINYINT:
		case SQL_C_ULONG:
		case SQL_C_USHORT:
		case SQL_C_UTINYINT:
		case SQL_BIGINT:
			reserve_integer(row_count);
			break;

		case SQL_DECIMAL:
		case SQL_NUMERIC:
		case SQL_REAL:
		case SQL_FLOAT:
		case SQL_DOUBLE:
			reserve_double(row_count);
			break;

		case SQL_BINARY:
		case SQL_VARBINARY:
		case SQL_LONGVARBINARY:
		case SQL_SS_UDT:
			reserve_var_binary_array(len, row_count);
			break;

		case SQL_SS_TIMESTAMPOFFSET:
			reserve_time_stamp_offset(row_count);
			break;

		case SQL_TYPE_TIME:
		case SQL_SS_TIME2:
			reserve_time(row_count);
			break;

		case SQL_TIMESTAMP:
		case SQL_DATETIME:
		case SQL_TYPE_TIMESTAMP:
		case SQL_TYPE_DATE:
			reserve_time_stamp(row_count);
			break;

		default:
			reserve_w_var_char_array(len, row_count);
			break;
		}
	}

	Local<Value> BoundDatum::unbind() const
	{
		Local<Value> v;

		switch (js_type)
		{
		case JS_STRING:
			v = unbind_string();
			break;

		case JS_BOOLEAN:
			v = unbind_boolean();
			break;

		case JS_INT:
			v = unbind_int32();
			break;

		case JS_UINT:
			v = unbind_uint32();
			break;

		case JS_DATE:
			v = unbind_double();
			break;

		case JS_NUMBER:
			v = unbind_number();
			break;

		default:
			v = unbind_null();
			break;
		}

		return v;
	}
}
