2012-08-22 20:29:01 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <set>
|
|
|
|
|
|
2015-09-29 19:19:54 +00:00
|
|
|
|
#include <common/logger_useful.h>
|
2012-09-05 19:51:09 +00:00
|
|
|
|
|
2012-08-23 23:49:28 +00:00
|
|
|
|
#include <DB/Core/ColumnNumbers.h>
|
2013-02-08 19:34:44 +00:00
|
|
|
|
#include <DB/Common/Arena.h>
|
2012-08-23 23:49:28 +00:00
|
|
|
|
|
|
|
|
|
#include <DB/DataStreams/IBlockInputStream.h>
|
|
|
|
|
|
2012-08-22 20:29:01 +00:00
|
|
|
|
#include <DB/Parsers/IAST.h>
|
|
|
|
|
|
2014-04-28 01:48:24 +00:00
|
|
|
|
#include <DB/Common/HashTable/HashSet.h>
|
2012-08-23 23:49:28 +00:00
|
|
|
|
#include <DB/Interpreters/AggregationCommon.h>
|
2013-06-20 12:12:27 +00:00
|
|
|
|
#include <DB/Interpreters/Limits.h>
|
2013-03-25 13:02:12 +00:00
|
|
|
|
#include <DB/Columns/ColumnConst.h>
|
|
|
|
|
#include <DB/Columns/ColumnArray.h>
|
2012-08-22 20:29:01 +00:00
|
|
|
|
|
2014-03-26 18:19:25 +00:00
|
|
|
|
#include <DB/Storages/MergeTree/BoolMask.h>
|
2014-03-31 14:49:43 +00:00
|
|
|
|
#include <DB/Storages/MergeTree/PKCondition.h>
|
2012-08-22 20:29:01 +00:00
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
2015-03-02 01:11:37 +00:00
|
|
|
|
/** Методы для разных вариантов реализации множеств.
|
|
|
|
|
* Используются в качестве параметра шаблона.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Для случая, когда есть один числовой ключ.
|
|
|
|
|
template <typename FieldType, typename TData> /// UInt8/16/32/64 для любых типов соответствующей битности.
|
|
|
|
|
struct SetMethodOneNumber
|
|
|
|
|
{
|
|
|
|
|
typedef TData Data;
|
|
|
|
|
typedef typename Data::key_type Key;
|
|
|
|
|
|
|
|
|
|
Data data;
|
|
|
|
|
|
|
|
|
|
/// Для использования одного Method в разных потоках, используйте разные State.
|
|
|
|
|
struct State
|
|
|
|
|
{
|
|
|
|
|
const FieldType * vec;
|
|
|
|
|
|
|
|
|
|
/** Вызывается в начале обработки каждого блока.
|
|
|
|
|
* Устанавливает переменные, необходимые для остальных методов, вызываемых во внутренних циклах.
|
|
|
|
|
*/
|
|
|
|
|
void init(const ConstColumnPlainPtrs & key_columns)
|
|
|
|
|
{
|
|
|
|
|
vec = &static_cast<const ColumnVector<FieldType> *>(key_columns[0])->getData()[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Достать из ключевых столбцов ключ для вставки в хэш-таблицу.
|
|
|
|
|
Key getKey(
|
|
|
|
|
const ConstColumnPlainPtrs & key_columns, /// Ключевые столбцы.
|
|
|
|
|
size_t keys_size, /// Количество ключевых столбцов.
|
2015-03-03 20:00:39 +00:00
|
|
|
|
size_t i, /// Из какой строки блока достать ключ.
|
|
|
|
|
const Sizes & key_sizes) const /// Если ключи фиксированной длины - их длины. Не используется в методах по ключам переменной длины.
|
2015-03-02 01:11:37 +00:00
|
|
|
|
{
|
|
|
|
|
return unionCastToUInt64(vec[i]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Разместить дополнительные данные, если это необходимо, в случае, когда в хэш-таблицу был вставлен новый ключ.
|
|
|
|
|
*/
|
2015-03-03 20:00:39 +00:00
|
|
|
|
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, Arena & pool) {}
|
2015-03-02 01:11:37 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Для случая, когда есть один строковый ключ.
|
|
|
|
|
template <typename TData>
|
|
|
|
|
struct SetMethodString
|
|
|
|
|
{
|
|
|
|
|
typedef TData Data;
|
|
|
|
|
typedef typename Data::key_type Key;
|
|
|
|
|
|
|
|
|
|
Data data;
|
|
|
|
|
|
|
|
|
|
struct State
|
|
|
|
|
{
|
|
|
|
|
const ColumnString::Offsets_t * offsets;
|
|
|
|
|
const ColumnString::Chars_t * chars;
|
|
|
|
|
|
|
|
|
|
void init(const ConstColumnPlainPtrs & key_columns)
|
|
|
|
|
{
|
|
|
|
|
const IColumn & column = *key_columns[0];
|
|
|
|
|
const ColumnString & column_string = static_cast<const ColumnString &>(column);
|
|
|
|
|
offsets = &column_string.getOffsets();
|
|
|
|
|
chars = &column_string.getChars();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key getKey(
|
|
|
|
|
const ConstColumnPlainPtrs & key_columns,
|
|
|
|
|
size_t keys_size,
|
|
|
|
|
size_t i,
|
2015-03-03 20:00:39 +00:00
|
|
|
|
const Sizes & key_sizes) const
|
2015-03-02 01:11:37 +00:00
|
|
|
|
{
|
|
|
|
|
return StringRef(
|
|
|
|
|
&(*chars)[i == 0 ? 0 : (*offsets)[i - 1]],
|
|
|
|
|
(i == 0 ? (*offsets)[i] : ((*offsets)[i] - (*offsets)[i - 1])) - 1);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2015-03-03 20:00:39 +00:00
|
|
|
|
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, Arena & pool)
|
2015-03-02 01:11:37 +00:00
|
|
|
|
{
|
|
|
|
|
value.data = pool.insert(value.data, value.size);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Для случая, когда есть один строковый ключ фиксированной длины.
|
|
|
|
|
template <typename TData>
|
|
|
|
|
struct SetMethodFixedString
|
|
|
|
|
{
|
|
|
|
|
typedef TData Data;
|
|
|
|
|
typedef typename Data::key_type Key;
|
|
|
|
|
|
|
|
|
|
Data data;
|
|
|
|
|
|
|
|
|
|
struct State
|
|
|
|
|
{
|
|
|
|
|
size_t n;
|
|
|
|
|
const ColumnFixedString::Chars_t * chars;
|
|
|
|
|
|
|
|
|
|
void init(const ConstColumnPlainPtrs & key_columns)
|
|
|
|
|
{
|
|
|
|
|
const IColumn & column = *key_columns[0];
|
|
|
|
|
const ColumnFixedString & column_string = static_cast<const ColumnFixedString &>(column);
|
|
|
|
|
n = column_string.getN();
|
|
|
|
|
chars = &column_string.getChars();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key getKey(
|
|
|
|
|
const ConstColumnPlainPtrs & key_columns,
|
|
|
|
|
size_t keys_size,
|
|
|
|
|
size_t i,
|
2015-03-03 20:00:39 +00:00
|
|
|
|
const Sizes & key_sizes) const
|
2015-03-02 01:11:37 +00:00
|
|
|
|
{
|
|
|
|
|
return StringRef(&(*chars)[i * n], n);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2015-03-03 20:00:39 +00:00
|
|
|
|
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, Arena & pool)
|
2015-03-02 01:11:37 +00:00
|
|
|
|
{
|
|
|
|
|
value.data = pool.insert(value.data, value.size);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Для случая, когда все ключи фиксированной длины, и они помещаются в N (например, 128) бит.
|
|
|
|
|
template <typename TData>
|
|
|
|
|
struct SetMethodKeysFixed
|
|
|
|
|
{
|
|
|
|
|
typedef TData Data;
|
|
|
|
|
typedef typename Data::key_type Key;
|
|
|
|
|
|
|
|
|
|
Data data;
|
|
|
|
|
|
|
|
|
|
struct State
|
|
|
|
|
{
|
|
|
|
|
void init(const ConstColumnPlainPtrs & key_columns)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key getKey(
|
|
|
|
|
const ConstColumnPlainPtrs & key_columns,
|
|
|
|
|
size_t keys_size,
|
|
|
|
|
size_t i,
|
2015-03-03 20:00:39 +00:00
|
|
|
|
const Sizes & key_sizes) const
|
2015-03-02 01:11:37 +00:00
|
|
|
|
{
|
|
|
|
|
return packFixed<Key>(i, keys_size, key_columns, key_sizes);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2015-03-03 20:00:39 +00:00
|
|
|
|
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, Arena & pool) {}
|
2015-03-02 01:11:37 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Для остальных случаев. По 128 битному хэшу от ключа. (При этом, строки, содержащие нули посередине, могут склеиться.)
|
|
|
|
|
template <typename TData>
|
|
|
|
|
struct SetMethodHashed
|
|
|
|
|
{
|
|
|
|
|
typedef TData Data;
|
|
|
|
|
typedef typename Data::key_type Key;
|
|
|
|
|
|
|
|
|
|
Data data;
|
|
|
|
|
|
|
|
|
|
struct State
|
|
|
|
|
{
|
|
|
|
|
void init(const ConstColumnPlainPtrs & key_columns)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key getKey(
|
|
|
|
|
const ConstColumnPlainPtrs & key_columns,
|
|
|
|
|
size_t keys_size,
|
|
|
|
|
size_t i,
|
2015-03-03 20:00:39 +00:00
|
|
|
|
const Sizes & key_sizes) const
|
2015-03-02 01:11:37 +00:00
|
|
|
|
{
|
2015-03-03 20:00:39 +00:00
|
|
|
|
return hash128(i, keys_size, key_columns);
|
2015-03-02 01:11:37 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2015-03-03 20:00:39 +00:00
|
|
|
|
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, Arena & pool) {}
|
2015-03-02 01:11:37 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Разные варианты реализации множества.
|
|
|
|
|
*/
|
|
|
|
|
struct SetVariants
|
|
|
|
|
{
|
|
|
|
|
/// TODO Использовать для этих двух вариантов bit- или byte- set.
|
|
|
|
|
std::unique_ptr<SetMethodOneNumber<UInt8, HashSet<UInt8, TrivialHash, HashTableFixedGrower<8>>>> key8;
|
|
|
|
|
std::unique_ptr<SetMethodOneNumber<UInt16, HashSet<UInt16, TrivialHash, HashTableFixedGrower<16>>>> key16;
|
|
|
|
|
|
2015-03-02 05:41:21 +00:00
|
|
|
|
/** Также для эксперимента проверялась возможность использовать SmallSet,
|
|
|
|
|
* пока количество элементов в множестве небольшое (и, при необходимости, конвертировать в полноценный HashSet).
|
|
|
|
|
* Но этот эксперимент показал, что преимущество есть только в редких случаях.
|
|
|
|
|
*/
|
2015-03-02 01:11:37 +00:00
|
|
|
|
std::unique_ptr<SetMethodOneNumber<UInt32, HashSet<UInt32, HashCRC32<UInt32>>>> key32;
|
|
|
|
|
std::unique_ptr<SetMethodOneNumber<UInt64, HashSet<UInt64, HashCRC32<UInt64>>>> key64;
|
|
|
|
|
std::unique_ptr<SetMethodString<HashSetWithSavedHash<StringRef>>> key_string;
|
|
|
|
|
std::unique_ptr<SetMethodFixedString<HashSetWithSavedHash<StringRef>>> key_fixed_string;
|
|
|
|
|
std::unique_ptr<SetMethodKeysFixed<HashSet<UInt128, UInt128HashCRC32>>> keys128;
|
|
|
|
|
std::unique_ptr<SetMethodKeysFixed<HashSet<UInt256, UInt256HashCRC32>>> keys256;
|
|
|
|
|
std::unique_ptr<SetMethodHashed<HashSet<UInt128, UInt128TrivialHash>>> hashed;
|
|
|
|
|
|
2015-03-02 05:41:21 +00:00
|
|
|
|
/** В отличие от Aggregator, здесь не используется метод concat.
|
|
|
|
|
* Это сделано потому что метод hashed, хоть и медленнее, но в данном случае, использует меньше оперативки.
|
|
|
|
|
* так как при его использовании, сами значения ключей не сохраняются.
|
|
|
|
|
*/
|
2015-03-02 01:11:37 +00:00
|
|
|
|
|
|
|
|
|
Arena string_pool;
|
|
|
|
|
|
|
|
|
|
#define APPLY_FOR_SET_VARIANTS(M) \
|
2015-03-02 05:41:21 +00:00
|
|
|
|
M(key8) \
|
|
|
|
|
M(key16) \
|
2015-03-02 01:11:37 +00:00
|
|
|
|
M(key32) \
|
|
|
|
|
M(key64) \
|
|
|
|
|
M(key_string) \
|
|
|
|
|
M(key_fixed_string) \
|
|
|
|
|
M(keys128) \
|
|
|
|
|
M(keys256) \
|
|
|
|
|
M(hashed)
|
|
|
|
|
|
|
|
|
|
enum class Type
|
|
|
|
|
{
|
|
|
|
|
EMPTY,
|
|
|
|
|
|
2015-03-02 05:41:21 +00:00
|
|
|
|
#define M(NAME) NAME,
|
2015-03-02 01:11:37 +00:00
|
|
|
|
APPLY_FOR_SET_VARIANTS(M)
|
|
|
|
|
#undef M
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Type type = Type::EMPTY;
|
|
|
|
|
|
|
|
|
|
bool empty() const { return type == Type::EMPTY; }
|
|
|
|
|
|
|
|
|
|
static Type chooseMethod(const ConstColumnPlainPtrs & key_columns, Sizes & key_sizes);
|
|
|
|
|
|
|
|
|
|
void init(Type type_);
|
|
|
|
|
|
|
|
|
|
size_t getTotalRowCount() const;
|
|
|
|
|
/// Считает размер в байтах буфера Set и размер string_pool'а
|
|
|
|
|
size_t getTotalByteCount() const;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2012-08-22 20:29:01 +00:00
|
|
|
|
/** Структура данных для реализации выражения IN.
|
|
|
|
|
*/
|
2012-08-23 22:40:51 +00:00
|
|
|
|
class Set
|
2012-08-22 20:29:01 +00:00
|
|
|
|
{
|
2012-08-23 22:40:51 +00:00
|
|
|
|
public:
|
2014-07-06 19:48:39 +00:00
|
|
|
|
Set(const Limits & limits) :
|
2014-05-15 10:24:03 +00:00
|
|
|
|
log(&Logger::get("Set")),
|
2013-06-20 12:12:27 +00:00
|
|
|
|
max_rows(limits.max_rows_in_set),
|
|
|
|
|
max_bytes(limits.max_bytes_in_set),
|
|
|
|
|
overflow_mode(limits.set_overflow_mode)
|
|
|
|
|
{
|
|
|
|
|
}
|
2014-07-06 19:48:39 +00:00
|
|
|
|
|
2015-03-02 01:11:37 +00:00
|
|
|
|
bool empty() const { return data.empty(); }
|
2012-08-23 20:22:44 +00:00
|
|
|
|
|
2012-08-24 19:42:03 +00:00
|
|
|
|
/** Создать множество по выражению (для перечисления в самом запросе).
|
|
|
|
|
* types - типы того, что стоит слева от IN.
|
|
|
|
|
* node - это список значений: 1, 2, 3 или список tuple-ов: (1, 2), (3, 4), (5, 6).
|
2014-04-08 12:54:32 +00:00
|
|
|
|
* create_ordered_set - создавать ли вектор упорядоченных элементов. Нужен для работы индекса
|
2012-08-24 19:42:03 +00:00
|
|
|
|
*/
|
2015-06-12 05:18:47 +00:00
|
|
|
|
void createFromAST(DataTypes & types, ASTPtr node, const Context & context, bool create_ordered_set);
|
2014-03-04 11:26:55 +00:00
|
|
|
|
|
|
|
|
|
// Возвращает false, если превышено какое-нибудь ограничение, и больше не нужно вставлять.
|
2015-01-27 00:52:03 +00:00
|
|
|
|
bool insertFromBlock(const Block & block, bool create_ordered_set = false);
|
2014-03-04 11:26:55 +00:00
|
|
|
|
|
2015-10-08 03:41:11 +00:00
|
|
|
|
/** Для столбцов блока проверить принадлежность их значений множеству.
|
2012-08-23 20:22:44 +00:00
|
|
|
|
* Записать результат в столбец в позиции result.
|
|
|
|
|
*/
|
2015-10-08 03:41:11 +00:00
|
|
|
|
ColumnPtr execute(const Block & block, bool negative) const;
|
2014-03-26 10:56:21 +00:00
|
|
|
|
|
2015-10-12 07:05:54 +00:00
|
|
|
|
std::string describe() const;
|
2014-04-01 10:09:22 +00:00
|
|
|
|
|
2014-04-08 12:54:32 +00:00
|
|
|
|
/// проверяет есть ли в Set элементы для заданного диапазона индекса
|
2015-03-27 03:37:46 +00:00
|
|
|
|
BoolMask mayBeTrueInRange(const Range & range) const;
|
2014-06-18 20:08:31 +00:00
|
|
|
|
|
2015-03-02 01:11:37 +00:00
|
|
|
|
size_t getTotalRowCount() const { return data.getTotalRowCount(); }
|
|
|
|
|
size_t getTotalByteCount() const { return data.getTotalByteCount(); }
|
2014-06-18 20:08:31 +00:00
|
|
|
|
|
2012-08-23 20:22:44 +00:00
|
|
|
|
private:
|
2013-03-25 13:02:12 +00:00
|
|
|
|
Sizes key_sizes;
|
2012-08-23 22:40:51 +00:00
|
|
|
|
|
2015-03-02 01:11:37 +00:00
|
|
|
|
SetVariants data;
|
|
|
|
|
|
2012-08-23 22:40:51 +00:00
|
|
|
|
/** Типы данных, из которых было создано множество.
|
|
|
|
|
* При проверке на принадлежность множеству, типы проверяемых столбцов должны с ними совпадать.
|
|
|
|
|
*/
|
|
|
|
|
DataTypes data_types;
|
2014-07-06 19:48:39 +00:00
|
|
|
|
|
2012-08-23 20:22:44 +00:00
|
|
|
|
Logger * log;
|
2014-07-06 19:48:39 +00:00
|
|
|
|
|
2013-06-20 12:12:27 +00:00
|
|
|
|
/// Ограничения на максимальный размер множества
|
|
|
|
|
size_t max_rows;
|
|
|
|
|
size_t max_bytes;
|
2014-02-17 23:56:45 +00:00
|
|
|
|
OverflowMode overflow_mode;
|
2014-07-06 19:48:39 +00:00
|
|
|
|
|
2013-03-19 12:25:59 +00:00
|
|
|
|
/// Если в левой части IN стоит массив. Проверяем, что хоть один элемент массива лежит в множестве.
|
2013-03-25 13:02:12 +00:00
|
|
|
|
void executeArray(const ColumnArray * key_column, ColumnUInt8::Container_t & vec_res, bool negative) const;
|
2014-07-06 19:48:39 +00:00
|
|
|
|
|
2013-03-19 12:25:59 +00:00
|
|
|
|
/// Если в левой части набор столбцов тех же типов, что элементы множества.
|
|
|
|
|
void executeOrdinary(const ConstColumnPlainPtrs & key_columns, ColumnUInt8::Container_t & vec_res, bool negative) const;
|
2014-07-06 19:48:39 +00:00
|
|
|
|
|
2013-06-20 12:12:27 +00:00
|
|
|
|
/// Проверить не превышены ли допустимые размеры множества ключей
|
|
|
|
|
bool checkSetSizeLimits() const;
|
2014-05-15 10:24:03 +00:00
|
|
|
|
|
2015-03-02 01:11:37 +00:00
|
|
|
|
/// Вектор упорядоченных элементов Set.
|
|
|
|
|
/// Нужен для работы индекса по первичному ключу в операторе IN.
|
2014-04-08 12:54:32 +00:00
|
|
|
|
typedef std::vector<Field> OrderedSetElements;
|
|
|
|
|
typedef std::unique_ptr<OrderedSetElements> OrderedSetElementsPtr;
|
|
|
|
|
OrderedSetElementsPtr ordered_set_elements;
|
2015-01-27 00:52:03 +00:00
|
|
|
|
|
|
|
|
|
/** Защищает работу с множеством в функциях insertFromBlock и execute.
|
|
|
|
|
* Эти функции могут вызываться одновременно из разных потоков только при использовании StorageSet,
|
|
|
|
|
* и StorageSet вызывает только эти две функции.
|
|
|
|
|
* Поэтому остальные функции по работе с множеством, не защинены.
|
|
|
|
|
*/
|
|
|
|
|
mutable Poco::RWLock rwlock;
|
2015-03-02 01:11:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Method>
|
2015-03-02 05:41:21 +00:00
|
|
|
|
void insertFromBlockImpl(
|
2015-03-02 01:11:37 +00:00
|
|
|
|
Method & method,
|
|
|
|
|
const ConstColumnPlainPtrs & key_columns,
|
|
|
|
|
size_t rows,
|
|
|
|
|
SetVariants & variants);
|
|
|
|
|
|
|
|
|
|
template <typename Method>
|
|
|
|
|
void executeImpl(
|
|
|
|
|
Method & method,
|
|
|
|
|
const ConstColumnPlainPtrs & key_columns,
|
|
|
|
|
ColumnUInt8::Container_t & vec_res,
|
|
|
|
|
bool negative,
|
2015-03-03 20:00:39 +00:00
|
|
|
|
size_t rows) const;
|
2015-03-02 01:11:37 +00:00
|
|
|
|
|
|
|
|
|
template <typename Method>
|
|
|
|
|
void executeArrayImpl(
|
|
|
|
|
Method & method,
|
|
|
|
|
const ConstColumnPlainPtrs & key_columns,
|
|
|
|
|
const ColumnArray::Offsets_t & offsets,
|
|
|
|
|
ColumnUInt8::Container_t & vec_res,
|
|
|
|
|
bool negative,
|
2015-03-03 20:00:39 +00:00
|
|
|
|
size_t rows) const;
|
2012-08-22 20:29:01 +00:00
|
|
|
|
};
|
|
|
|
|
|
2015-11-02 19:19:29 +00:00
|
|
|
|
typedef std::shared_ptr<Set> SetPtr;
|
|
|
|
|
typedef std::shared_ptr<const Set> ConstSetPtr;
|
2014-03-04 11:26:55 +00:00
|
|
|
|
typedef std::vector<SetPtr> Sets;
|
2012-08-22 20:29:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|