Commit 50bcda01 authored by Alexander Barkov's avatar Alexander Barkov Committed by Sergei Golubchik

Changing the FixedBinTypeBundle parameter to a "storage class" instead of sizes

- Adding a new template FixedBinTypeStorage.

- Restoring classes UUID and Inet6
  as primitive "storage classes" for their data types.
  They derive from FixedBinTypeStorage.
  These storage classes have very few server dependencies so they
  can later be easily reused in smart engines, e.g. ColumnStore.

- Changing the FixedBinTypeBundle parameter from
  <size_t NATIVE_LEN, size_t MAX_CHAR_LEN> to <class FbtImpl>
  and fixing UUID and INET6 bundles to get their storage
  classes as a parameter.
parent b1fab9bf
......@@ -168,8 +168,7 @@ bool Inet4::ascii_to_ipv4(const char *str, size_t str_length)
IPv4-part differently on different platforms.
*/
template<>
bool Inet6Bundle::Fbt::ascii_to_fbt(const char *str, size_t str_length)
bool Inet6::ascii_to_fbt(const char *str, size_t str_length)
{
if (str_length < 2)
{
......@@ -382,8 +381,7 @@ size_t Inet4::to_string(char *dst, size_t dstsize) const
Windows Vista, but out the minimum supported version is Windows 2000.
*/
template<>
size_t Inet6Bundle::Fbt::to_string(char *dst, size_t dstsize) const
size_t Inet6::to_string(char *dst, size_t dstsize) const
{
struct Region
{
......@@ -509,8 +507,7 @@ size_t Inet6Bundle::Fbt::to_string(char *dst, size_t dstsize) const
return (size_t) (p - dst);
}
template<>
const Name &Inet6Bundle::Type_handler_fbt::default_value() const
const Name &Inet6::default_value()
{
static Name def(STRING_WITH_LEN("::"));
return def;
......
......@@ -31,8 +31,20 @@ static const size_t IN6_ADDR_NUM_WORDS= IN6_ADDR_SIZE / 2;
*/
static const uint IN6_ADDR_MAX_CHAR_LENGTH= 8 * 4 + 7;
#include "sql_type_fixedbin_storage.h"
class Inet6: public FixedBinTypeStorage<IN6_ADDR_SIZE, IN6_ADDR_MAX_CHAR_LENGTH>
{
public:
using FixedBinTypeStorage::FixedBinTypeStorage;
bool ascii_to_fbt(const char *str, size_t str_length);
size_t to_string(char *dst, size_t dstsize) const;
static const Name &default_value();
};
#include "sql_type_fixedbin.h"
typedef FixedBinTypeBundle<IN6_ADDR_SIZE, IN6_ADDR_MAX_CHAR_LENGTH> Inet6Bundle;
typedef FixedBinTypeBundle<Inet6> Inet6Bundle;
/***********************************************************************/
......
CREATE TABLE t1 (a UUID);
Field 1: `a`
Org_field: `a`
Catalog: `def`
Database: `test`
Table: `t1`
......
......@@ -42,8 +42,7 @@ static bool get_digit(char ch, uint *val)
}
template<>
bool UUIDBundle::Fbt::ascii_to_fbt(const char *str, size_t str_length)
bool UUID::ascii_to_fbt(const char *str, size_t str_length)
{
if (str_length < 32 || str_length > 3 * binary_length() - 1)
return true;
......@@ -71,16 +70,14 @@ bool UUIDBundle::Fbt::ascii_to_fbt(const char *str, size_t str_length)
return true;
}
template<>
size_t UUIDBundle::Fbt::to_string(char *dst, size_t dstsize) const
size_t UUID::to_string(char *dst, size_t dstsize) const
{
my_uuid2str((const uchar *) m_buffer, dst, 1);
return MY_UUID_STRING_LENGTH;
}
template<>
const Name &UUIDBundle::Type_handler_fbt::default_value() const
const Name &UUID::default_value()
{
static Name def(STRING_WITH_LEN("00000000-0000-0000-0000-000000000000"));
return def;
......
......@@ -16,7 +16,17 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
#include "sql_type_fixedbin_storage.h"
class UUID: public FixedBinTypeStorage<MY_UUID_SIZE, MY_UUID_STRING_LENGTH>
{
public:
using FixedBinTypeStorage::FixedBinTypeStorage;
bool ascii_to_fbt(const char *str, size_t str_length);
size_t to_string(char *dst, size_t dstsize) const;
static const Name &default_value();
};
#include "sql_type_fixedbin.h"
typedef FixedBinTypeBundle<MY_UUID_SIZE, MY_UUID_STRING_LENGTH> UUIDBundle;
typedef FixedBinTypeBundle<UUID> UUIDBundle;
#endif // SQL_TYPE_UUID_INCLUDED
......@@ -26,16 +26,19 @@
#define MYSQL_SERVER
#include "sql_class.h" // THD, SORT_FIELD_ATTR
#include "opt_range.h" // SEL_ARG, null_element
#include "sql_type_fixedbin_storage.h"
/***********************************************************************/
template<size_t NATIVE_LEN, size_t MAX_CHAR_LEN>
template<class FbtImpl>
class FixedBinTypeBundle
{
public:
class Fbt
class Fbt: public FbtImpl
{
protected:
char m_buffer[NATIVE_LEN];
using FbtImpl::m_buffer;
bool make_from_item(Item *item, bool warn)
{
if (item->type_handler() == type_handler_fbt())
......@@ -49,24 +52,23 @@ class FixedBinTypeBundle
memcpy(m_buffer, tmp.ptr(), sizeof(m_buffer));
return false;
}
StringBuffer<MAX_CHAR_LEN+1> tmp;
StringBuffer<FbtImpl::max_char_length()+1> tmp;
String *str= item->val_str(&tmp);
return str ? make_from_character_or_binary_string(str, warn) : true;
}
bool ascii_to_fbt(const char *str, size_t str_length); // XXX
bool character_string_to_fbt(const char *str, size_t str_length,
CHARSET_INFO *cs)
{
if (cs->state & MY_CS_NONASCII)
{
char tmp[MAX_CHAR_LEN+1];
char tmp[FbtImpl::max_char_length()+1];
String_copier copier;
uint length= copier.well_formed_copy(&my_charset_latin1, tmp, sizeof(tmp),
cs, str, str_length);
return ascii_to_fbt(tmp, length);
return FbtImpl::ascii_to_fbt(tmp, length);
}
return ascii_to_fbt(str, str_length);
return FbtImpl::ascii_to_fbt(str, str_length);
}
bool make_from_character_or_binary_string(const String *str, bool warn)
{
......@@ -101,21 +103,11 @@ class FixedBinTypeBundle
Fbt() { }
public:
static uint binary_length() { return NATIVE_LEN; }
static uint max_char_length() { return MAX_CHAR_LEN; }
static bool only_zero_bytes(const char *ptr, uint length)
{
for (uint i= 0 ; i < length; i++)
{
if (ptr[i] != 0)
return false;
}
return true;
}
static Fbt zero()
{
Fbt fbt;
bzero(&fbt.m_buffer, sizeof(fbt.m_buffer));
fbt.set_zero();
return fbt;
}
......@@ -153,13 +145,13 @@ class FixedBinTypeBundle
{
return to->copy(m_buffer, sizeof(m_buffer));
}
size_t to_string(char *dst, size_t dstsize) const; // XXX
bool to_string(String *to) const
{
to->set_charset(&my_charset_latin1);
if (to->alloc(MAX_CHAR_LEN+1))
if (to->alloc(FbtImpl::max_char_length()+1))
return true;
to->length((uint32) to_string(const_cast<char*>(to->ptr()), MAX_CHAR_LEN+1));
to->length((uint32) FbtImpl::to_string(const_cast<char*>(to->ptr()),
FbtImpl::max_char_length()+1));
return false;
}
int cmp(const char *str, size_t length) const
......@@ -222,7 +214,7 @@ class FixedBinTypeBundle
public:
Type_std_attributes_fbt()
:Type_std_attributes(
Type_numeric_attributes(MAX_CHAR_LEN, 0, true),
Type_numeric_attributes(FbtImpl::max_char_length(), 0, true),
DTCollation_numeric())
{ }
};
......@@ -235,7 +227,7 @@ class FixedBinTypeBundle
if (str->charset() == &my_charset_bin)
{
// Convert from a binary string
if (str->length() != NATIVE_LEN ||
if (str->length() != FbtImpl::binary_length() ||
to->copy(str->ptr(), str->length()))
{
thd->push_warning_wrong_value(Sql_condition::WARN_LEVEL_WARN,
......@@ -261,7 +253,10 @@ class FixedBinTypeBundle
return &type_collection_fbt;
}
const Name &default_value() const override; // XXX
const Name &default_value() const override
{
return FbtImpl::default_value();
}
protocol_send_type_t protocol_send_type() const override
{
return PROTOCOL_SEND_STRING;
......@@ -295,7 +290,7 @@ class FixedBinTypeBundle
uint32 max_display_length_for_field(const Conv_source &src) const override
{
return MAX_CHAR_LEN;
return FbtImpl::max_char_length();
}
const Type_handler *type_handler_for_comparison() const override
......@@ -309,7 +304,7 @@ class FixedBinTypeBundle
Fbt_null ni(item); // Convert Item to Fbt
if (ni.is_null())
return 0;
NativeBuffer<NATIVE_LEN+1> tmp;
NativeBuffer<FbtImpl::binary_length()+1> tmp;
if (field->val_native(&tmp))
{
DBUG_ASSERT(0);
......@@ -348,7 +343,7 @@ class FixedBinTypeBundle
decimal_digits_t Item_decimal_precision(const Item *item) const override
{
/* This will be needed if we ever allow cast from Fbt to DECIMAL. */
return (NATIVE_LEN*8+7)/10*3; // = bytes to decimal digits
return (FbtImpl::binary_length()*8+7)/10*3; // = bytes to decimal digits
}
/*
......@@ -377,7 +372,7 @@ class FixedBinTypeBundle
// Fix attributes after the parser
bool Column_definition_fix_attributes(Column_definition *c) const override
{
c->length= MAX_CHAR_LEN;
c->length= FbtImpl::max_char_length();
return false;
}
......@@ -424,7 +419,7 @@ class FixedBinTypeBundle
partition_value_print_mode_t mode)
const override
{
StringBuffer<MAX_CHAR_LEN+64> fbtstr;
StringBuffer<FbtImpl::max_char_length()+64> fbtstr;
Fbt_null fbt(item_expr);
if (fbt.is_null())
{
......@@ -471,20 +466,20 @@ class FixedBinTypeBundle
Sort_param *param) const override
{
DBUG_ASSERT(item->type_handler() == this);
NativeBuffer<NATIVE_LEN+1> tmp;
NativeBuffer<FbtImpl::binary_length()+1> tmp;
item->val_native_result(current_thd, &tmp);
if (item->maybe_null())
{
if (item->null_value)
{
memset(to, 0, NATIVE_LEN + 1);
memset(to, 0, FbtImpl::binary_length() + 1);
return;
}
*to++= 1;
}
DBUG_ASSERT(!item->null_value);
DBUG_ASSERT(NATIVE_LEN == tmp.length());
DBUG_ASSERT(NATIVE_LEN == sort_field->length);
DBUG_ASSERT(FbtImpl::binary_length() == tmp.length());
DBUG_ASSERT(FbtImpl::binary_length() == sort_field->length);
memcpy(to, tmp.ptr(), tmp.length());
}
uint make_packed_sort_key_part(uchar *to, Item *item,
......@@ -492,7 +487,7 @@ class FixedBinTypeBundle
Sort_param *param) const override
{
DBUG_ASSERT(item->type_handler() == this);
NativeBuffer<NATIVE_LEN+1> tmp;
NativeBuffer<FbtImpl::binary_length()+1> tmp;
item->val_native_result(current_thd, &tmp);
if (item->maybe_null())
{
......@@ -504,28 +499,28 @@ class FixedBinTypeBundle
*to++= 1;
}
DBUG_ASSERT(!item->null_value);
DBUG_ASSERT(NATIVE_LEN == tmp.length());
DBUG_ASSERT(NATIVE_LEN == sort_field->length);
DBUG_ASSERT(FbtImpl::binary_length() == tmp.length());
DBUG_ASSERT(FbtImpl::binary_length() == sort_field->length);
memcpy(to, tmp.ptr(), tmp.length());
return tmp.length();
}
void sort_length(THD *thd, const Type_std_attributes *item,
SORT_FIELD_ATTR *attr) const override
{
attr->original_length= attr->length= NATIVE_LEN;
attr->original_length= attr->length= FbtImpl::binary_length();
attr->suffix_length= 0;
}
uint32 max_display_length(const Item *item) const override
{
return MAX_CHAR_LEN;
return FbtImpl::max_char_length();
}
uint32 calc_pack_length(uint32 length) const override
{
return NATIVE_LEN;
return FbtImpl::binary_length();
}
void Item_update_null_value(Item *item) const override
{
NativeBuffer<NATIVE_LEN+1> tmp;
NativeBuffer<FbtImpl::binary_length()+1> tmp;
item->val_native(current_thd, &tmp);
}
bool Item_save_in_value(THD *thd, Item *item, st_value *value) const override
......@@ -578,7 +573,7 @@ class FixedBinTypeBundle
bool Item_param_val_native(THD *thd, Item_param *item, Native *to)
const override
{
StringBuffer<MAX_CHAR_LEN+1> buffer;
StringBuffer<FbtImpl::max_char_length()+1> buffer;
String *str= item->val_str(&buffer);
if (!str)
return true;
......@@ -606,7 +601,7 @@ class FixedBinTypeBundle
String *print_item_value(THD *thd, Item *item, String *str) const override
{
StringBuffer<MAX_CHAR_LEN+64> buf;
StringBuffer<FbtImpl::max_char_length()+64> buf;
String *result= item->val_str(&buf);
/*
TODO: This should eventually use one of these notations:
......@@ -705,9 +700,9 @@ class FixedBinTypeBundle
}
int cmp_native(const Native &a, const Native &b) const override
{
DBUG_ASSERT(a.length() == NATIVE_LEN);
DBUG_ASSERT(b.length() == NATIVE_LEN);
return memcmp(a.ptr(), b.ptr(), NATIVE_LEN);
DBUG_ASSERT(a.length() == FbtImpl::binary_length());
DBUG_ASSERT(b.length() == FbtImpl::binary_length());
return memcmp(a.ptr(), b.ptr(), FbtImpl::binary_length());
}
bool set_comparator_func(THD *thd, Arg_comparator *cmp) const override
{
......@@ -782,7 +777,7 @@ class FixedBinTypeBundle
{
if (item->type_handler() == this)
return item->val_native(thd, to); // No conversion needed
StringBuffer<MAX_CHAR_LEN+1> buffer;
StringBuffer<FbtImpl::max_char_length()+1> buffer;
String *str= item->val_str(&buffer);
return str ? character_or_binary_string_to_native(thd, str, to) : true;
}
......@@ -791,14 +786,14 @@ class FixedBinTypeBundle
{
if (item->type_handler() == this)
return item->val_native_result(thd, to); // No conversion needed
StringBuffer<MAX_CHAR_LEN+1> buffer;
StringBuffer<FbtImpl::max_char_length()+1> buffer;
String *str= item->str_result(&buffer);
return str ? character_or_binary_string_to_native(thd, str, to) : true;
}
bool Item_val_bool(Item *item) const override
{
NativeBuffer<NATIVE_LEN+1> tmp;
NativeBuffer<FbtImpl::binary_length()+1> tmp;
if (item->val_native(current_thd, &tmp))
return false;
return !Fbt::only_zero_bytes(tmp.ptr(), tmp.length());
......@@ -824,10 +819,10 @@ class FixedBinTypeBundle
String *Item_func_hex_val_str_ascii(Item_func_hex *item, String *str)
const override
{
NativeBuffer<NATIVE_LEN+1> tmp;
NativeBuffer<FbtImpl::binary_length()+1> tmp;
if ((item->null_value= item->arguments()[0]->val_native(current_thd, &tmp)))
return nullptr;
DBUG_ASSERT(tmp.length() == NATIVE_LEN);
DBUG_ASSERT(tmp.length() == FbtImpl::binary_length());
if (str->set_hex(tmp.ptr(), tmp.length()))
{
str->length(0);
......@@ -839,13 +834,13 @@ class FixedBinTypeBundle
String *Item_func_hybrid_field_type_val_str(Item_func_hybrid_field_type *item,
String *str) const override
{
NativeBuffer<NATIVE_LEN+1> native;
NativeBuffer<FbtImpl::binary_length()+1> native;
if (item->val_native(current_thd, &native))
{
DBUG_ASSERT(item->null_value);
return nullptr;
}
DBUG_ASSERT(native.length() == NATIVE_LEN);
DBUG_ASSERT(native.length() == FbtImpl::binary_length());
Fbt_null tmp(native.ptr(), native.length());
return tmp.is_null() || tmp.to_string(str) ? nullptr : str;
}
......@@ -985,7 +980,7 @@ class FixedBinTypeBundle
{
static Item_char_typecast_func_handler_fbt_to_binary
item_char_typecast_func_handler_fbt_to_binary;
item->fix_length_and_dec_native_to_binary(NATIVE_LEN);
item->fix_length_and_dec_native_to_binary(FbtImpl::binary_length());
item->set_func_handler(&item_char_typecast_func_handler_fbt_to_binary);
return false;
}
......@@ -1070,11 +1065,11 @@ class FixedBinTypeBundle
{
static void set_min_value(char *ptr)
{
memset(ptr, 0, NATIVE_LEN);
memset(ptr, 0, FbtImpl::binary_length());
}
static void set_max_value(char *ptr)
{
memset(ptr, 0xFF, NATIVE_LEN);
memset(ptr, 0xFF, FbtImpl::binary_length());
}
void store_warning(const ErrConv &str,
Sql_condition::enum_warning_level level)
......@@ -1112,13 +1107,13 @@ class FixedBinTypeBundle
if (fbt.is_null())
return maybe_null() ? set_null_with_warn(err)
: set_min_value_with_warn(err);
fbt.to_binary((char *) ptr, NATIVE_LEN);
fbt.to_binary((char *) ptr, FbtImpl::binary_length());
return 0;
}
public:
Field_fbt(const LEX_CSTRING *field_name_arg, const Record_addr &rec)
:Field(rec.ptr(), MAX_CHAR_LEN,
:Field(rec.ptr(), FbtImpl::max_char_length(),
rec.null_ptr(), rec.null_bit(), Field::NONE, field_name_arg)
{
flags|= BINARY_FLAG | UNSIGNED_FLAG;
......@@ -1165,11 +1160,11 @@ class FixedBinTypeBundle
}
uint32 pack_length() const override
{
return NATIVE_LEN;
return FbtImpl::binary_length();
}
uint pack_length_from_metadata(uint field_metadata) const override
{
return NATIVE_LEN;
return FbtImpl::binary_length();
}
void sql_type(String &str) const override
......@@ -1225,13 +1220,13 @@ class FixedBinTypeBundle
bool val_bool(void) override
{
DBUG_ASSERT(marked_for_read());
return !Fbt::only_zero_bytes((const char *) ptr, NATIVE_LEN);
return !Fbt::only_zero_bytes((const char *) ptr, FbtImpl::binary_length());
}
int store_native(const Native &value) override
{
DBUG_ASSERT(marked_for_write_or_computed());
DBUG_ASSERT(value.length() == NATIVE_LEN);
DBUG_ASSERT(value.length() == FbtImpl::binary_length());
memcpy(ptr, value.ptr(), value.length());
return 0;
}
......@@ -1298,7 +1293,7 @@ class FixedBinTypeBundle
dynamic_cast<const Type_handler_general_purpose_string*>
(to->type_handler()))
{
NativeBuffer<NATIVE_LEN+1> res;
NativeBuffer<FbtImpl::binary_length()+1> res;
val_native(&res);
return to->store(res.ptr(), res.length(), &my_charset_bin);
}
......@@ -1336,7 +1331,7 @@ class FixedBinTypeBundle
static void do_field_fbt_native_to_binary(Copy_field *copy)
{
NativeBuffer<NATIVE_LEN+1> res;
NativeBuffer<FbtImpl::binary_length()+1> res;
copy->from_field->val_native(&res);
copy->to_field->store(res.ptr(), res.length(), &my_charset_bin);
}
......@@ -1353,7 +1348,7 @@ class FixedBinTypeBundle
if (type_handler() == source.type_handler() ||
(source.type_handler() == &type_handler_string &&
source.type_handler()->max_display_length_for_field(source) ==
NATIVE_LEN))
FbtImpl::binary_length()))
return rpl_conv_type_from_same_data_type(source.metadata(), rli, param);
return CONV_TYPE_IMPOSSIBLE;
}
......@@ -1449,19 +1444,19 @@ class FixedBinTypeBundle
{
DBUG_ASSERT(type() == binlog_type());
return Binlog_type_info_fixed_string(Field_fbt::binlog_type(),
NATIVE_LEN, &my_charset_bin);
FbtImpl::binary_length(), &my_charset_bin);
}
uchar *pack(uchar *to, const uchar *from, uint max_length) override
{
DBUG_PRINT("debug", ("Packing field '%s'", field_name.str));
return StringPack(&my_charset_bin, NATIVE_LEN).pack(to, from, max_length);
return StringPack(&my_charset_bin, FbtImpl::binary_length()).pack(to, from, max_length);
}
const uchar *unpack(uchar *to, const uchar *from, const uchar *from_end,
uint param_data) override
{
return StringPack(&my_charset_bin, NATIVE_LEN).unpack(to, from, from_end, param_data);
return StringPack(&my_charset_bin, FbtImpl::binary_length()).unpack(to, from, from_end, param_data);
}
uint max_packed_col_length(uint max_length) override
......@@ -1555,7 +1550,7 @@ class FixedBinTypeBundle
class Item_cache_fbt: public Item_cache
{
NativeBuffer<NATIVE_LEN+1> m_value;
NativeBuffer<FbtImpl::binary_length()+1> m_value;
public:
Item_cache_fbt(THD *thd)
:Item_cache(thd, type_handler_fbt()) { }
......@@ -1669,7 +1664,7 @@ class FixedBinTypeBundle
}
void print(String *str, enum_query_type query_type) override
{
StringBuffer<MAX_CHAR_LEN+64> tmp;
StringBuffer<FbtImpl::max_char_length()+64> tmp;
tmp.append(type_handler_fbt()->name().lex_cstring());
my_caseup_str(&my_charset_latin1, tmp.c_ptr());
str->append(tmp);
......@@ -1690,7 +1685,7 @@ class FixedBinTypeBundle
class Item_copy_fbt: public Item_copy
{
NativeBuffer<NATIVE_LEN+1> m_value;
NativeBuffer<Fbt::binary_length()+1> m_value;
public:
Item_copy_fbt(THD *thd, Item *item_arg): Item_copy(thd, item_arg) {}
......
#ifndef SQL_TYPE_FIXEDBIN_STORAGE
#define SQL_TYPE_FIXEDBIN_STORAGE
/* Copyright (c) 2019,2021 MariaDB Corporation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
/*
This is a common code for plugin (?) types that are generally
handled like strings, but have their own fixed size on-disk binary storage
format and their own (variable size) canonical string representation.
Examples are INET6 and UUID types.
*/
/***********************************************************************/
template<size_t NATIVE_LEN, size_t MAX_CHAR_LEN>
class FixedBinTypeStorage
{
protected:
char m_buffer[NATIVE_LEN];
// Non-initializing constructor
FixedBinTypeStorage()
{ }
FixedBinTypeStorage & set_zero()
{
bzero(&m_buffer, sizeof(m_buffer));
return *this;
}
public:
// Initialize from binary representation
FixedBinTypeStorage(const char *str, size_t length)
{
if (length != binary_length())
set_zero();
else
memcpy(&m_buffer, str, sizeof(m_buffer));
}
static constexpr uint binary_length() { return NATIVE_LEN; }
static constexpr uint max_char_length() { return MAX_CHAR_LEN; }
static bool only_zero_bytes(const char *ptr, size_t length)
{
for (uint i= 0 ; i < length; i++)
{
if (ptr[i] != 0)
return false;
}
return true;
}
};
#endif /* SQL_TYPE_FIXEDBIN_STORAGE */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment