2011-08-28 00:31:30 +00:00
|
|
|
|
#pragma once
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
|
|
|
|
#include <vector>
|
2013-01-05 20:03:19 +00:00
|
|
|
|
#include <tr1/type_traits>
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
#include <boost/static_assert.hpp>
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
2012-09-05 19:51:09 +00:00
|
|
|
|
#include <mysqlxx/Date.h>
|
|
|
|
|
#include <mysqlxx/DateTime.h>
|
|
|
|
|
#include <mysqlxx/Manip.h>
|
2011-08-09 19:19:00 +00:00
|
|
|
|
|
2010-03-01 16:59:51 +00:00
|
|
|
|
#include <DB/Core/Types.h>
|
2011-10-10 10:05:39 +00:00
|
|
|
|
#include <DB/Core/Exception.h>
|
|
|
|
|
#include <DB/Core/ErrorCodes.h>
|
2013-03-09 05:44:26 +00:00
|
|
|
|
#include <DB/IO/ReadHelpers.h>
|
|
|
|
|
#include <DB/IO/WriteHelpers.h>
|
2013-05-15 22:58:57 +00:00
|
|
|
|
#include <DB/IO/WriteBufferFromString.h>
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
2011-09-19 01:42:16 +00:00
|
|
|
|
using Poco::SharedPtr;
|
|
|
|
|
|
2013-01-08 21:32:16 +00:00
|
|
|
|
/** 32 хватает с запасом (достаточно 28), но выбрано круглое число,
|
|
|
|
|
* чтобы арифметика при использовании массивов из Field была проще (не содержала умножения).
|
|
|
|
|
*/
|
|
|
|
|
#define DBMS_TOTAL_FIELD_SIZE 32
|
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
|
|
|
|
/** Discriminated union из нескольких типов.
|
|
|
|
|
* Сделан для замены boost::variant:
|
|
|
|
|
* является не обобщённым,
|
|
|
|
|
* зато несколько более эффективным, и более простым.
|
|
|
|
|
*
|
|
|
|
|
* Используется для представления единичного значения одного из нескольких типов в оперативке.
|
2010-03-01 16:59:51 +00:00
|
|
|
|
* Внимание! Предпочтительно вместо единичных значений хранить кусочки столбцов. См. Column.h
|
|
|
|
|
*/
|
2013-01-08 21:32:16 +00:00
|
|
|
|
class __attribute__((aligned(DBMS_TOTAL_FIELD_SIZE))) Field
|
2013-01-05 20:03:19 +00:00
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
struct Types
|
|
|
|
|
{
|
|
|
|
|
/// Идентификатор типа.
|
|
|
|
|
enum Which
|
|
|
|
|
{
|
|
|
|
|
Null = 0,
|
|
|
|
|
UInt64 = 1,
|
|
|
|
|
Int64 = 2,
|
|
|
|
|
Float64 = 3,
|
2013-01-07 06:47:15 +00:00
|
|
|
|
|
2013-02-09 02:20:26 +00:00
|
|
|
|
/// не POD типы. Для них предполагается relocatable.
|
2013-01-07 06:47:15 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
String = 16,
|
2013-02-03 23:11:21 +00:00
|
|
|
|
Array = 17,
|
2013-01-05 20:03:19 +00:00
|
|
|
|
};
|
|
|
|
|
|
2013-01-07 06:53:24 +00:00
|
|
|
|
static const int MIN_NON_POD = 16;
|
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
static const char * toString(Which which)
|
|
|
|
|
{
|
|
|
|
|
switch (which)
|
|
|
|
|
{
|
|
|
|
|
case Null: return "Null";
|
|
|
|
|
case UInt64: return "UInt64";
|
|
|
|
|
case Int64: return "Int64";
|
|
|
|
|
case Float64: return "Float64";
|
|
|
|
|
case String: return "String";
|
|
|
|
|
case Array: return "Array";
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
/// Позволяет получить идентификатор для типа или наоборот.
|
|
|
|
|
template <typename T> struct TypeToEnum;
|
|
|
|
|
template <Types::Which which> struct EnumToType;
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
2012-08-26 06:48:39 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
Field()
|
|
|
|
|
: which(Types::Null)
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << "Field()" << std::endl;
|
|
|
|
|
}
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
/** Не смотря на наличие шаблонного конструктора, этот конструктор всё-равно нужен,
|
2013-01-08 20:31:24 +00:00
|
|
|
|
* так как при его отсутствии, компилятор всё-равно сгенерирует конструктор по-умолчанию.
|
2013-01-05 20:03:19 +00:00
|
|
|
|
*/
|
|
|
|
|
Field(const Field & rhs)
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << this << " Field::Field(const Field &)" << std::endl;
|
|
|
|
|
create(rhs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Field & operator= (const Field & rhs)
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << this << " Field::operator=(const Field &)" << std::endl;
|
|
|
|
|
destroy();
|
|
|
|
|
create(rhs);
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
Field(const T & rhs)
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << this << " Field::Field(" << Types::toString(TypeToEnum<T>::value) << ")" << std::endl;
|
|
|
|
|
create(rhs);
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-07 05:33:36 +00:00
|
|
|
|
/// Создать строку inplace.
|
|
|
|
|
Field(const char * data, size_t size)
|
|
|
|
|
{
|
2013-01-07 06:47:15 +00:00
|
|
|
|
create(data, size);
|
2013-01-07 05:33:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Field(const unsigned char * data, size_t size)
|
|
|
|
|
{
|
2013-01-07 06:47:15 +00:00
|
|
|
|
create(data, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void assignString(const char * data, size_t size)
|
|
|
|
|
{
|
|
|
|
|
destroy();
|
|
|
|
|
create(data, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void assignString(const unsigned char * data, size_t size)
|
|
|
|
|
{
|
|
|
|
|
destroy();
|
|
|
|
|
create(data, size);
|
2013-01-07 05:33:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
template <typename T>
|
|
|
|
|
Field & operator= (const T & rhs)
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << this << " Field::operator=(" << Types::toString(TypeToEnum<T>::value) << ")" << std::endl;
|
|
|
|
|
destroy();
|
|
|
|
|
create(rhs);
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~Field()
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << this << " Field::~Field()" << std::endl;
|
|
|
|
|
destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Types::Which getType() const { return which; }
|
|
|
|
|
const char * getTypeName() const { return Types::toString(which); }
|
|
|
|
|
|
2013-02-08 19:34:44 +00:00
|
|
|
|
bool isNull() const { return which == Types::Null; }
|
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
|
|
|
|
template <typename T> T & get()
|
|
|
|
|
{
|
|
|
|
|
typedef typename std::tr1::remove_reference<T>::type TWithoutRef;
|
|
|
|
|
TWithoutRef * __attribute__((__may_alias__)) ptr = reinterpret_cast<TWithoutRef*>(storage);
|
|
|
|
|
return *ptr;
|
|
|
|
|
};
|
2013-02-25 23:08:25 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
template <typename T> const T & get() const
|
|
|
|
|
{
|
|
|
|
|
typedef typename std::tr1::remove_reference<T>::type TWithoutRef;
|
|
|
|
|
const TWithoutRef * __attribute__((__may_alias__)) ptr = reinterpret_cast<const TWithoutRef*>(storage);
|
|
|
|
|
return *ptr;
|
2010-03-01 16:59:51 +00:00
|
|
|
|
};
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
|
|
|
|
template <typename T> T & safeGet()
|
|
|
|
|
{
|
|
|
|
|
const Types::Which requested = TypeToEnum<typename std::tr1::remove_cv<typename std::tr1::remove_reference<T>::type>::type>::value;
|
|
|
|
|
if (which != requested)
|
|
|
|
|
throw Exception("Bad get: has " + std::string(getTypeName()) + ", requested " + std::string(Types::toString(requested)), ErrorCodes::BAD_GET);
|
|
|
|
|
return get<T>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T> const T & safeGet() const
|
|
|
|
|
{
|
|
|
|
|
const Types::Which requested = TypeToEnum<typename std::tr1::remove_cv<typename std::tr1::remove_reference<T>::type>::type>::value;
|
|
|
|
|
if (which != requested)
|
|
|
|
|
throw Exception("Bad get: has " + std::string(getTypeName()) + ", requested " + std::string(Types::toString(requested)), ErrorCodes::BAD_GET);
|
|
|
|
|
return get<T>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool operator< (const Field & rhs) const
|
|
|
|
|
{
|
|
|
|
|
if (which < rhs.which)
|
|
|
|
|
return true;
|
|
|
|
|
if (which > rhs.which)
|
|
|
|
|
return false;
|
2013-02-25 23:08:25 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
switch (which)
|
|
|
|
|
{
|
2013-01-08 20:31:24 +00:00
|
|
|
|
case Types::Null: return false;
|
2013-01-05 20:03:19 +00:00
|
|
|
|
case Types::UInt64: return get<UInt64>() < rhs.get<UInt64>();
|
|
|
|
|
case Types::Int64: return get<Int64>() < rhs.get<Int64>();
|
|
|
|
|
case Types::Float64: return get<Float64>() < rhs.get<Float64>();
|
|
|
|
|
case Types::String: return get<String>() < rhs.get<String>();
|
|
|
|
|
case Types::Array: return get<Array>() < rhs.get<Array>();
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool operator> (const Field & rhs) const
|
|
|
|
|
{
|
|
|
|
|
return rhs < *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool operator<= (const Field & rhs) const
|
|
|
|
|
{
|
|
|
|
|
if (which < rhs.which)
|
|
|
|
|
return true;
|
|
|
|
|
if (which > rhs.which)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
switch (which)
|
|
|
|
|
{
|
2013-01-08 20:31:24 +00:00
|
|
|
|
case Types::Null: return true;
|
2013-01-05 20:03:19 +00:00
|
|
|
|
case Types::UInt64: return get<UInt64>() <= rhs.get<UInt64>();
|
|
|
|
|
case Types::Int64: return get<Int64>() <= rhs.get<Int64>();
|
|
|
|
|
case Types::Float64: return get<Float64>() <= rhs.get<Float64>();
|
|
|
|
|
case Types::String: return get<String>() <= rhs.get<String>();
|
|
|
|
|
case Types::Array: return get<Array>() <= rhs.get<Array>();
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool operator>= (const Field & rhs) const
|
|
|
|
|
{
|
|
|
|
|
return rhs <= *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool operator== (const Field & rhs) const
|
|
|
|
|
{
|
|
|
|
|
if (which != rhs.which)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
switch (which)
|
|
|
|
|
{
|
2013-01-08 20:31:24 +00:00
|
|
|
|
case Types::Null: return true;
|
|
|
|
|
case Types::UInt64:
|
|
|
|
|
case Types::Int64:
|
|
|
|
|
case Types::Float64: return get<UInt64>() == rhs.get<UInt64>();
|
2013-01-05 20:03:19 +00:00
|
|
|
|
case Types::String: return get<String>() == rhs.get<String>();
|
|
|
|
|
case Types::Array: return get<Array>() == rhs.get<Array>();
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool operator!= (const Field & rhs) const
|
|
|
|
|
{
|
|
|
|
|
return !(*this == rhs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef std::vector<Field> Array;
|
2013-02-25 23:08:25 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
private:
|
2013-01-08 21:32:16 +00:00
|
|
|
|
/// Хватает с запасом
|
|
|
|
|
static const size_t storage_size = DBMS_TOTAL_FIELD_SIZE - sizeof(Types::Which);
|
2013-02-25 23:08:25 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
BOOST_STATIC_ASSERT(storage_size >= sizeof(Null));
|
|
|
|
|
BOOST_STATIC_ASSERT(storage_size >= sizeof(UInt64));
|
|
|
|
|
BOOST_STATIC_ASSERT(storage_size >= sizeof(Int64));
|
|
|
|
|
BOOST_STATIC_ASSERT(storage_size >= sizeof(Float64));
|
|
|
|
|
BOOST_STATIC_ASSERT(storage_size >= sizeof(String));
|
|
|
|
|
BOOST_STATIC_ASSERT(storage_size >= sizeof(Array));
|
|
|
|
|
|
2013-01-08 20:31:24 +00:00
|
|
|
|
char storage[storage_size] __attribute__((aligned(8)));
|
2013-01-05 20:03:19 +00:00
|
|
|
|
Types::Which which;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
void create(const T & x)
|
|
|
|
|
{
|
|
|
|
|
which = TypeToEnum<T>::value;
|
|
|
|
|
// std::cerr << this << " Creating " << getTypeName() << std::endl;
|
2013-01-05 21:57:11 +00:00
|
|
|
|
T * __attribute__((__may_alias__)) ptr = reinterpret_cast<T*>(storage);
|
|
|
|
|
new (ptr) T(x);
|
2013-01-05 20:03:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void create(const Null & x)
|
|
|
|
|
{
|
|
|
|
|
which = Types::Null;
|
|
|
|
|
// std::cerr << this << " Creating " << getTypeName() << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void create(const Field & x)
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << this << " Creating Field" << std::endl;
|
|
|
|
|
|
|
|
|
|
switch (x.which)
|
|
|
|
|
{
|
|
|
|
|
case Types::Null: create(Null()); break;
|
|
|
|
|
case Types::UInt64: create(x.get<UInt64>()); break;
|
|
|
|
|
case Types::Int64: create(x.get<Int64>()); break;
|
|
|
|
|
case Types::Float64: create(x.get<Float64>()); break;
|
|
|
|
|
case Types::String: create(x.get<String>()); break;
|
|
|
|
|
case Types::Array: create(x.get<Array>()); break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-07 06:47:15 +00:00
|
|
|
|
void create(const char * data, size_t size)
|
|
|
|
|
{
|
|
|
|
|
which = Types::String;
|
|
|
|
|
String * __attribute__((__may_alias__)) ptr = reinterpret_cast<String*>(storage);
|
|
|
|
|
new (ptr) String(data, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void create(const unsigned char * data, size_t size)
|
|
|
|
|
{
|
|
|
|
|
create(reinterpret_cast<const char *>(data), size);
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
2013-01-07 08:27:39 +00:00
|
|
|
|
__attribute__((__always_inline__)) void destroy()
|
2013-01-05 20:03:19 +00:00
|
|
|
|
{
|
|
|
|
|
// std::cerr << this << " Destroying " << getTypeName() << std::endl;
|
2013-01-07 06:47:15 +00:00
|
|
|
|
|
2013-01-07 06:53:24 +00:00
|
|
|
|
if (which < Types::MIN_NON_POD)
|
|
|
|
|
return;
|
2013-02-25 23:08:25 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
switch (which)
|
|
|
|
|
{
|
|
|
|
|
case Types::String:
|
|
|
|
|
destroy<String>();
|
|
|
|
|
break;
|
|
|
|
|
case Types::Array:
|
|
|
|
|
destroy<Array>();
|
|
|
|
|
break;
|
2013-01-07 06:47:15 +00:00
|
|
|
|
default:
|
|
|
|
|
break;
|
2013-01-05 20:03:19 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
void destroy()
|
|
|
|
|
{
|
|
|
|
|
T * __attribute__((__may_alias__)) ptr = reinterpret_cast<T*>(storage);
|
|
|
|
|
ptr->~T();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2013-02-03 23:11:21 +00:00
|
|
|
|
template <> struct Field::TypeToEnum<Null> { static const Types::Which value = Types::Null; };
|
|
|
|
|
template <> struct Field::TypeToEnum<UInt64> { static const Types::Which value = Types::UInt64; };
|
|
|
|
|
template <> struct Field::TypeToEnum<Int64> { static const Types::Which value = Types::Int64; };
|
|
|
|
|
template <> struct Field::TypeToEnum<Float64> { static const Types::Which value = Types::Float64; };
|
|
|
|
|
template <> struct Field::TypeToEnum<String> { static const Types::Which value = Types::String; };
|
|
|
|
|
template <> struct Field::TypeToEnum<Field::Array> { static const Types::Which value = Types::Array; };
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
2013-02-03 23:11:21 +00:00
|
|
|
|
template <> struct Field::EnumToType<Field::Types::Null> { typedef Null Type; };
|
|
|
|
|
template <> struct Field::EnumToType<Field::Types::UInt64> { typedef UInt64 Type; };
|
|
|
|
|
template <> struct Field::EnumToType<Field::Types::Int64> { typedef Int64 Type; };
|
|
|
|
|
template <> struct Field::EnumToType<Field::Types::Float64> { typedef Float64 Type; };
|
|
|
|
|
template <> struct Field::EnumToType<Field::Types::String> { typedef String Type; };
|
|
|
|
|
template <> struct Field::EnumToType<Field::Types::Array> { typedef Array Type; };
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
T get(const Field & field)
|
|
|
|
|
{
|
|
|
|
|
return field.template get<T>();
|
2010-03-01 16:59:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
template <typename T>
|
|
|
|
|
T get(Field & field)
|
|
|
|
|
{
|
|
|
|
|
return field.template get<T>();
|
|
|
|
|
}
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
template <typename T>
|
|
|
|
|
T safeGet(const Field & field)
|
2010-03-01 16:59:51 +00:00
|
|
|
|
{
|
2013-01-05 20:03:19 +00:00
|
|
|
|
return field.template safeGet<T>();
|
|
|
|
|
}
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
template <typename T>
|
|
|
|
|
T safeGet(Field & field)
|
2010-03-01 16:59:51 +00:00
|
|
|
|
{
|
2013-01-05 20:03:19 +00:00
|
|
|
|
return field.template safeGet<T>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** StaticVisitor (его наследники) - класс с перегруженными для разных типов операторами ().
|
|
|
|
|
* Вызвать visitor для field можно с помощью функции apply_visitor.
|
|
|
|
|
* Также поддерживается visitor, в котором оператор () принимает два аргумента.
|
|
|
|
|
*/
|
|
|
|
|
template <typename R = void>
|
|
|
|
|
struct StaticVisitor
|
|
|
|
|
{
|
|
|
|
|
typedef R ResultType;
|
2010-03-01 16:59:51 +00:00
|
|
|
|
};
|
|
|
|
|
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
|
|
|
|
template <typename Visitor, typename F>
|
|
|
|
|
typename Visitor::ResultType apply_visitor_impl(Visitor & visitor, F & field)
|
|
|
|
|
{
|
|
|
|
|
switch (field.getType())
|
|
|
|
|
{
|
2013-01-06 16:04:52 +00:00
|
|
|
|
case Field::Types::Null: return visitor(field.template get<Null>());
|
|
|
|
|
case Field::Types::UInt64: return visitor(field.template get<UInt64>());
|
|
|
|
|
case Field::Types::Int64: return visitor(field.template get<Int64>());
|
|
|
|
|
case Field::Types::Float64: return visitor(field.template get<Float64>());
|
|
|
|
|
case Field::Types::String: return visitor(field.template get<String>());
|
|
|
|
|
case Field::Types::Array: return visitor(field.template get<Field::Array>());
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Эти штуки нужны, чтобы принимать временный объект по константной ссылке.
|
|
|
|
|
* В шаблон выше, типы форвардятся уже с const-ом.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(const Visitor & visitor, Field & field)
|
|
|
|
|
{
|
|
|
|
|
return apply_visitor_impl(visitor, field);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(const Visitor & visitor, const Field & field)
|
|
|
|
|
{
|
|
|
|
|
return apply_visitor_impl(visitor, field);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(Visitor & visitor, Field & field)
|
|
|
|
|
{
|
|
|
|
|
return apply_visitor_impl(visitor, field);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(Visitor & visitor, const Field & field)
|
|
|
|
|
{
|
|
|
|
|
return apply_visitor_impl(visitor, field);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Visitor, typename F1, typename F2>
|
|
|
|
|
typename Visitor::ResultType apply_binary_visitor_impl2(Visitor & visitor, F1 & field1, F2 & field2)
|
|
|
|
|
{
|
|
|
|
|
switch (field2.getType())
|
|
|
|
|
{
|
2013-01-06 16:04:52 +00:00
|
|
|
|
case Field::Types::Null: return visitor(field1, field2.template get<Null>());
|
|
|
|
|
case Field::Types::UInt64: return visitor(field1, field2.template get<UInt64>());
|
|
|
|
|
case Field::Types::Int64: return visitor(field1, field2.template get<Int64>());
|
|
|
|
|
case Field::Types::Float64: return visitor(field1, field2.template get<Float64>());
|
|
|
|
|
case Field::Types::String: return visitor(field1, field2.template get<String>());
|
|
|
|
|
case Field::Types::Array: return visitor(field1, field2.template get<Field::Array>());
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor, typename F1, typename F2>
|
|
|
|
|
typename Visitor::ResultType apply_binary_visitor_impl1(Visitor & visitor, F1 & field1, F2 & field2)
|
|
|
|
|
{
|
|
|
|
|
switch (field1.getType())
|
|
|
|
|
{
|
2013-01-08 20:31:24 +00:00
|
|
|
|
case Field::Types::Null: return apply_binary_visitor_impl2(visitor, field1.template get<Null>(), field2);
|
2013-01-06 16:04:52 +00:00
|
|
|
|
case Field::Types::UInt64: return apply_binary_visitor_impl2(visitor, field1.template get<UInt64>(), field2);
|
|
|
|
|
case Field::Types::Int64: return apply_binary_visitor_impl2(visitor, field1.template get<Int64>(), field2);
|
|
|
|
|
case Field::Types::Float64: return apply_binary_visitor_impl2(visitor, field1.template get<Float64>(), field2);
|
|
|
|
|
case Field::Types::String: return apply_binary_visitor_impl2(visitor, field1.template get<String>(), field2);
|
|
|
|
|
case Field::Types::Array: return apply_binary_visitor_impl2(visitor, field1.template get<Field::Array>(), field2);
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(Visitor & visitor, Field & field1, Field & field2)
|
|
|
|
|
{
|
|
|
|
|
return apply_binary_visitor_impl1(visitor, field1, field2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(Visitor & visitor, Field & field1, const Field & field2)
|
|
|
|
|
{
|
|
|
|
|
return apply_binary_visitor_impl1(visitor, field1, field2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(Visitor & visitor, const Field & field1, Field & field2)
|
|
|
|
|
{
|
|
|
|
|
return apply_binary_visitor_impl1(visitor, field1, field2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(Visitor & visitor, const Field & field1, const Field & field2)
|
|
|
|
|
{
|
|
|
|
|
return apply_binary_visitor_impl1(visitor, field1, field2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(const Visitor & visitor, Field & field1, Field & field2)
|
|
|
|
|
{
|
|
|
|
|
return apply_binary_visitor_impl1(visitor, field1, field2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(const Visitor & visitor, Field & field1, const Field & field2)
|
|
|
|
|
{
|
|
|
|
|
return apply_binary_visitor_impl1(visitor, field1, field2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(const Visitor & visitor, const Field & field1, Field & field2)
|
|
|
|
|
{
|
|
|
|
|
return apply_binary_visitor_impl1(visitor, field1, field2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Visitor>
|
|
|
|
|
typename Visitor::ResultType apply_visitor(const Visitor & visitor, const Field & field1, const Field & field2)
|
|
|
|
|
{
|
|
|
|
|
return apply_binary_visitor_impl1(visitor, field1, field2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef std::vector<Field> Array; /// Значение типа "массив"
|
|
|
|
|
|
|
|
|
|
template <> struct TypeName<Array> { static std::string get() { return "Array"; } };
|
|
|
|
|
|
|
|
|
|
|
2011-08-09 19:19:00 +00:00
|
|
|
|
/** Возвращает строковый дамп типа */
|
2013-05-15 22:58:57 +00:00
|
|
|
|
class FieldVisitorDump : public StaticVisitor<String>
|
2011-08-09 19:19:00 +00:00
|
|
|
|
{
|
2013-05-15 22:58:57 +00:00
|
|
|
|
private:
|
|
|
|
|
template <typename T>
|
|
|
|
|
static inline String formatQuotedWithPrefix(T x, const char * prefix)
|
|
|
|
|
{
|
|
|
|
|
String res;
|
|
|
|
|
WriteBufferFromString wb(res);
|
|
|
|
|
wb.write(prefix, strlen(prefix));
|
|
|
|
|
writeQuoted(x, wb);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
2011-08-09 19:19:00 +00:00
|
|
|
|
public:
|
2011-09-19 01:42:16 +00:00
|
|
|
|
String operator() (const Null & x) const { return "NULL"; }
|
2013-05-15 22:58:57 +00:00
|
|
|
|
String operator() (const UInt64 & x) const { return formatQuotedWithPrefix(x, "UInt64_"); }
|
|
|
|
|
String operator() (const Int64 & x) const { return formatQuotedWithPrefix(x, "Int64_"); }
|
|
|
|
|
String operator() (const Float64 & x) const { return formatQuotedWithPrefix(x, "Float64_"); }
|
2011-08-09 19:19:00 +00:00
|
|
|
|
|
2011-09-19 01:42:16 +00:00
|
|
|
|
String operator() (const String & x) const
|
2011-08-09 19:19:00 +00:00
|
|
|
|
{
|
2013-05-15 22:58:57 +00:00
|
|
|
|
String res;
|
|
|
|
|
WriteBufferFromString wb(res);
|
|
|
|
|
writeQuoted(x, wb);
|
|
|
|
|
return res;
|
2011-08-09 19:19:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-09-19 01:42:16 +00:00
|
|
|
|
String operator() (const Array & x) const
|
2011-08-09 19:19:00 +00:00
|
|
|
|
{
|
2013-05-15 22:58:57 +00:00
|
|
|
|
String res;
|
|
|
|
|
WriteBufferFromString wb(res);
|
|
|
|
|
FieldVisitorDump visitor;
|
2011-08-09 19:19:00 +00:00
|
|
|
|
|
2013-05-15 22:58:57 +00:00
|
|
|
|
wb.write("Array_[", 7);
|
2011-08-09 19:19:00 +00:00
|
|
|
|
for (Array::const_iterator it = x.begin(); it != x.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
if (it != x.begin())
|
2013-05-15 22:58:57 +00:00
|
|
|
|
wb.write(", ", 2);
|
|
|
|
|
writeString(apply_visitor(visitor, *it), wb);
|
2011-08-09 19:19:00 +00:00
|
|
|
|
}
|
2013-05-15 22:58:57 +00:00
|
|
|
|
writeChar(']', wb);
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
2013-05-15 22:58:57 +00:00
|
|
|
|
return res;
|
2011-08-09 19:19:00 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2011-08-12 18:27:39 +00:00
|
|
|
|
/** Выводит текстовое представление типа, как литерала в SQL запросе */
|
2013-01-05 20:03:19 +00:00
|
|
|
|
class FieldVisitorToString : public StaticVisitor<String>
|
2011-08-12 18:27:39 +00:00
|
|
|
|
{
|
2013-05-15 22:58:57 +00:00
|
|
|
|
private:
|
|
|
|
|
template <typename T>
|
|
|
|
|
static inline String formatQuoted(T x)
|
2011-08-12 18:27:39 +00:00
|
|
|
|
{
|
2013-05-15 22:58:57 +00:00
|
|
|
|
String res;
|
|
|
|
|
WriteBufferFromString wb(res);
|
|
|
|
|
writeQuoted(x, wb);
|
|
|
|
|
return res;
|
2011-08-12 18:27:39 +00:00
|
|
|
|
}
|
2013-05-15 22:58:57 +00:00
|
|
|
|
public:
|
|
|
|
|
String operator() (const Null & x) const { return "NULL"; }
|
|
|
|
|
String operator() (const UInt64 & x) const { return formatQuoted(x); }
|
|
|
|
|
String operator() (const Int64 & x) const { return formatQuoted(x); }
|
|
|
|
|
String operator() (const Float64 & x) const { return formatQuoted(x); }
|
|
|
|
|
String operator() (const String & x) const { return formatQuoted(x); }
|
2011-08-12 18:27:39 +00:00
|
|
|
|
|
|
|
|
|
String operator() (const Array & x) const
|
|
|
|
|
{
|
2013-05-15 22:58:57 +00:00
|
|
|
|
String res;
|
|
|
|
|
WriteBufferFromString wb(res);
|
2011-08-12 18:27:39 +00:00
|
|
|
|
FieldVisitorToString visitor;
|
|
|
|
|
|
2013-05-15 22:58:57 +00:00
|
|
|
|
writeChar('[', wb);
|
2011-08-12 18:27:39 +00:00
|
|
|
|
for (Array::const_iterator it = x.begin(); it != x.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
if (it != x.begin())
|
2013-05-15 22:58:57 +00:00
|
|
|
|
wb.write(", ", 2);
|
|
|
|
|
writeString(apply_visitor(visitor, *it), wb);
|
2011-08-12 18:27:39 +00:00
|
|
|
|
}
|
2013-05-15 22:58:57 +00:00
|
|
|
|
writeChar(']', wb);
|
2011-08-12 18:27:39 +00:00
|
|
|
|
|
2013-05-15 22:58:57 +00:00
|
|
|
|
return res;
|
2011-08-12 18:27:39 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2011-10-10 10:05:39 +00:00
|
|
|
|
/** Числовой тип преобразует в указанный. */
|
|
|
|
|
template <typename T>
|
2013-01-05 20:03:19 +00:00
|
|
|
|
class FieldVisitorConvertToNumber : public StaticVisitor<T>
|
2011-10-10 10:05:39 +00:00
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
T operator() (const Null & x) const
|
|
|
|
|
{
|
|
|
|
|
throw Exception("Cannot convert NULL to " + TypeName<T>::get(), ErrorCodes::CANNOT_CONVERT_TYPE);
|
|
|
|
|
}
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
2011-10-10 10:05:39 +00:00
|
|
|
|
T operator() (const String & x) const
|
|
|
|
|
{
|
|
|
|
|
throw Exception("Cannot convert String to " + TypeName<T>::get(), ErrorCodes::CANNOT_CONVERT_TYPE);
|
|
|
|
|
}
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
2011-10-10 10:05:39 +00:00
|
|
|
|
T operator() (const Array & x) const
|
|
|
|
|
{
|
|
|
|
|
throw Exception("Cannot convert Array to " + TypeName<T>::get(), ErrorCodes::CANNOT_CONVERT_TYPE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
T operator() (const UInt64 & x) const { return x; }
|
|
|
|
|
T operator() (const Int64 & x) const { return x; }
|
|
|
|
|
T operator() (const Float64 & x) const { return x; }
|
|
|
|
|
};
|
|
|
|
|
|
2010-03-01 16:59:51 +00:00
|
|
|
|
|
2010-03-18 19:32:14 +00:00
|
|
|
|
template <typename T> struct NearestFieldType;
|
|
|
|
|
|
2011-08-21 03:41:37 +00:00
|
|
|
|
template <> struct NearestFieldType<UInt8> { typedef UInt64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<UInt16> { typedef UInt64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<UInt32> { typedef UInt64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<UInt64> { typedef UInt64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<Int8> { typedef Int64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<Int16> { typedef Int64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<Int32> { typedef Int64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<Int64> { typedef Int64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<Float32> { typedef Float64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<Float64> { typedef Float64 Type; };
|
|
|
|
|
template <> struct NearestFieldType<String> { typedef String Type; };
|
2012-08-26 06:48:39 +00:00
|
|
|
|
template <> struct NearestFieldType<Array> { typedef Array Type; };
|
2012-06-07 18:32:21 +00:00
|
|
|
|
template <> struct NearestFieldType<bool> { typedef UInt64 Type; };
|
|
|
|
|
|
2010-03-01 16:59:51 +00:00
|
|
|
|
}
|
2012-09-05 19:51:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Заглушки, чтобы DBObject-ы с полем типа Array компилировались.
|
|
|
|
|
namespace mysqlxx
|
|
|
|
|
{
|
|
|
|
|
inline std::ostream & operator<< (mysqlxx::EscapeManipResult res, const DB::Array & value)
|
|
|
|
|
{
|
2013-02-25 23:08:25 +00:00
|
|
|
|
return res.ostr << apply_visitor(DB::FieldVisitorToString(), value);
|
2012-09-05 19:51:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::ostream & operator<< (mysqlxx::QuoteManipResult res, const DB::Array & value)
|
|
|
|
|
{
|
|
|
|
|
throw Poco::Exception("Cannot quote Array with mysqlxx::quote.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::istream & operator>> (mysqlxx::UnEscapeManipResult res, DB::Array & value)
|
|
|
|
|
{
|
|
|
|
|
throw Poco::Exception("Cannot unescape Array with mysqlxx::unescape.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::istream & operator>> (mysqlxx::UnQuoteManipResult res, DB::Array & value)
|
|
|
|
|
{
|
|
|
|
|
throw Poco::Exception("Cannot unquote Array with mysqlxx::unquote.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
class ReadBuffer;
|
|
|
|
|
class WriteBuffer;
|
2013-03-09 05:44:26 +00:00
|
|
|
|
|
|
|
|
|
/// Предполагается что у всех элементов массива одинаковый тип.
|
|
|
|
|
inline void readBinary(Array & x, ReadBuffer & buf)
|
|
|
|
|
{
|
|
|
|
|
size_t size;
|
|
|
|
|
UInt8 type;
|
|
|
|
|
DB::readBinary(type, buf);
|
|
|
|
|
DB::readBinary(size, buf);
|
2013-01-05 20:03:19 +00:00
|
|
|
|
|
2013-03-09 05:44:26 +00:00
|
|
|
|
for (size_t index = 0; index < size; ++index)
|
|
|
|
|
{
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case Field::Types::Null:
|
|
|
|
|
{
|
|
|
|
|
x.push_back(DB::Field());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::UInt64:
|
|
|
|
|
{
|
|
|
|
|
UInt64 value;
|
|
|
|
|
DB::readVarUInt(value, buf);
|
|
|
|
|
x.push_back(value);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::Int64:
|
|
|
|
|
{
|
|
|
|
|
Int64 value;
|
|
|
|
|
DB::readVarInt(value, buf);
|
|
|
|
|
x.push_back(value);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::Float64:
|
|
|
|
|
{
|
|
|
|
|
Float64 value;
|
|
|
|
|
DB::readFloatBinary(value, buf);
|
|
|
|
|
x.push_back(value);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::String:
|
|
|
|
|
{
|
|
|
|
|
std::string value;
|
|
|
|
|
DB::readStringBinary(value, buf);
|
|
|
|
|
x.push_back(value);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::Array:
|
|
|
|
|
{
|
|
|
|
|
Array value;
|
|
|
|
|
DB::readBinary(value, buf);
|
|
|
|
|
x.push_back(value);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-05 19:51:09 +00:00
|
|
|
|
inline void readText(Array & x, ReadBuffer & buf) { throw Exception("Cannot read Array.", ErrorCodes::NOT_IMPLEMENTED); }
|
|
|
|
|
inline void readQuoted(Array & x, ReadBuffer & buf) { throw Exception("Cannot read Array.", ErrorCodes::NOT_IMPLEMENTED); }
|
|
|
|
|
|
2013-03-09 05:44:26 +00:00
|
|
|
|
/// Предполагается что у всех элементов массива одинаковый тип.
|
|
|
|
|
inline void writeBinary(const Array & x, WriteBuffer & buf)
|
|
|
|
|
{
|
|
|
|
|
UInt8 type = Field::Types::Null;
|
|
|
|
|
size_t size = x.size();
|
|
|
|
|
if (size)
|
|
|
|
|
type = x.front().getType();
|
|
|
|
|
DB::writeBinary(type, buf);
|
|
|
|
|
DB::writeBinary(size, buf);
|
|
|
|
|
|
|
|
|
|
for (Array::const_iterator it = x.begin(); it != x.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case Field::Types::Null: break;
|
|
|
|
|
case Field::Types::UInt64:
|
|
|
|
|
{
|
|
|
|
|
DB::writeVarUInt(get<UInt64>(*it), buf);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::Int64:
|
|
|
|
|
{
|
|
|
|
|
DB::writeVarInt(get<Int64>(*it), buf);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::Float64:
|
|
|
|
|
{
|
|
|
|
|
DB::writeFloatBinary(get<Float64>(*it), buf);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::String:
|
|
|
|
|
{
|
|
|
|
|
DB::writeStringBinary(get<std::string>(*it), buf);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Field::Types::Array:
|
|
|
|
|
{
|
|
|
|
|
DB::writeBinary(get<Array>(*it), buf);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-11 10:29:41 +00:00
|
|
|
|
inline void writeText(const Array & x, WriteBuffer & buf)
|
|
|
|
|
{
|
|
|
|
|
DB::String res = apply_visitor(DB::FieldVisitorToString(), DB::Field(x));
|
|
|
|
|
buf.write(res.data(), res.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void writeQuoted(const Array & x, WriteBuffer & buf) { throw Exception("Cannot write Array quoted.", ErrorCodes::NOT_IMPLEMENTED); }
|
2012-09-05 19:51:09 +00:00
|
|
|
|
}
|
2013-01-08 21:32:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#undef DBMS_TOTAL_FIELD_SIZE
|