diff --git a/src/Common/format.h b/src/Common/format.h
new file mode 100644
index 00000000000..a9382f247ab
--- /dev/null
+++ b/src/Common/format.h
@@ -0,0 +1,178 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace DB
+{
+
+namespace ErrorCodes
+{
+ extern const int BAD_ARGUMENTS;
+}
+
+namespace Format
+{
+ using IndexPositions = PODArrayWithStackMemory;
+
+ static inline void parseNumber(const String & description, UInt64 l, UInt64 r, UInt64 & res, UInt64 argument_number)
+ {
+ res = 0;
+ for (UInt64 pos = l; pos < r; ++pos)
+ {
+ if (!isNumericASCII(description[pos]))
+ throw Exception("Not a number in curly braces at position " + std::to_string(pos), ErrorCodes::BAD_ARGUMENTS);
+ res = res * 10 + description[pos] - '0';
+ if (res >= argument_number)
+ throw Exception(
+ "Too big number for arguments, must be at most " + std::to_string(argument_number - 1), ErrorCodes::BAD_ARGUMENTS);
+ }
+ }
+
+ static inline void init(
+ const String & pattern,
+ size_t argument_number,
+ const std::vector> & constant_strings,
+ IndexPositions & index_positions,
+ std::vector & substrings)
+ {
+ /// Is current position after open curly brace.
+ bool is_open_curly = false;
+ /// The position of last open token.
+ size_t last_open = -1;
+
+ /// Is formatting in a plain {} token.
+ std::optional is_plain_numbering;
+ UInt64 index_if_plain = 0;
+
+ /// Left position of adding substrings, just to the closed brace position or the start of the string.
+ /// Invariant --- the start of substring is in this position.
+ size_t start_pos = 0;
+
+ /// A flag to decide whether we should glue the constant strings.
+ bool glue_to_next = false;
+
+ /// Handling double braces (escaping).
+ auto double_brace_removal = [](String & str)
+ {
+ size_t i = 0;
+ bool should_delete = true;
+ str.erase(
+ std::remove_if(
+ str.begin(),
+ str.end(),
+ [&i, &should_delete, &str](char)
+ {
+ bool is_double_brace = (str[i] == '{' && str[i + 1] == '{') || (str[i] == '}' && str[i + 1] == '}');
+ ++i;
+ if (is_double_brace && should_delete)
+ {
+ should_delete = false;
+ return true;
+ }
+ should_delete = true;
+ return false;
+ }),
+ str.end());
+ };
+
+ index_positions.emplace_back();
+
+ for (size_t i = 0; i < pattern.size(); ++i)
+ {
+ if (pattern[i] == '{')
+ {
+ /// Escaping handling
+ /// It is safe to access because of null termination
+ if (pattern[i + 1] == '{')
+ {
+ ++i;
+ continue;
+ }
+
+ if (is_open_curly)
+ throw Exception("Two open curly braces without close one at position " + std::to_string(i), ErrorCodes::BAD_ARGUMENTS);
+
+ String to_add = String(pattern.data() + start_pos, i - start_pos);
+ double_brace_removal(to_add);
+ if (!glue_to_next)
+ substrings.emplace_back(to_add);
+ else
+ substrings.back() += to_add;
+
+ glue_to_next = false;
+
+ is_open_curly = true;
+ last_open = i + 1;
+ }
+ else if (pattern[i] == '}')
+ {
+ if (pattern[i + 1] == '}')
+ {
+ ++i;
+ continue;
+ }
+
+ if (!is_open_curly)
+ throw Exception("Closed curly brace without open one at position " + std::to_string(i), ErrorCodes::BAD_ARGUMENTS);
+
+ is_open_curly = false;
+
+ if (last_open == i)
+ {
+ if (is_plain_numbering && !*is_plain_numbering)
+ throw Exception(
+ "Cannot switch from automatic field numbering to manual field specification", ErrorCodes::BAD_ARGUMENTS);
+ is_plain_numbering = true;
+ if (index_if_plain >= argument_number)
+ throw Exception("Argument is too big for formatting", ErrorCodes::BAD_ARGUMENTS);
+ index_positions.back() = index_if_plain++;
+ }
+ else
+ {
+ if (is_plain_numbering && *is_plain_numbering)
+ throw Exception(
+ "Cannot switch from automatic field numbering to manual field specification", ErrorCodes::BAD_ARGUMENTS);
+ is_plain_numbering = false;
+
+ UInt64 arg;
+ parseNumber(pattern, last_open, i, arg, argument_number);
+
+ if (arg >= argument_number)
+ throw Exception(
+ "Argument is too big for formatting. Note that indexing starts from zero", ErrorCodes::BAD_ARGUMENTS);
+
+ index_positions.back() = arg;
+ }
+
+ if (!constant_strings.empty() && constant_strings[index_positions.back()])
+ {
+ /// The next string should be glued to last `A {} C`.format('B') -> `A B C`.
+ glue_to_next = true;
+ substrings.back() += *constant_strings[index_positions.back()];
+ }
+ else
+ index_positions.emplace_back(); /// Otherwise we commit arg number and proceed.
+
+ start_pos = i + 1;
+ }
+ }
+
+ if (is_open_curly)
+ throw Exception("Last open curly brace is not closed", ErrorCodes::BAD_ARGUMENTS);
+
+ String to_add = String(pattern.data() + start_pos, pattern.size() - start_pos);
+ double_brace_removal(to_add);
+
+ if (!glue_to_next)
+ substrings.emplace_back(to_add);
+ else
+ substrings.back() += to_add;
+
+ index_positions.pop_back();
+ }
+}
+
+}
diff --git a/src/Functions/concat.cpp b/src/Functions/concat.cpp
index cd83223de3e..e11071265ce 100644
--- a/src/Functions/concat.cpp
+++ b/src/Functions/concat.cpp
@@ -52,23 +52,21 @@ public:
{
if (arguments.size() < 2)
throw Exception(
- "Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size())
- + ", should be at least 2.",
- ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
-
- if (arguments.size() > FormatImpl::argument_threshold)
- throw Exception(
- "Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size())
- + ", should be at most " + std::to_string(FormatImpl::argument_threshold),
- ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
+ ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH,
+ "Number of arguments for function {} doesn't match: passed {}, should be at least 2",
+ getName(),
+ arguments.size());
for (const auto arg_idx : collections::range(0, arguments.size()))
{
const auto * arg = arguments[arg_idx].get();
if (!isStringOrFixedString(arg))
- throw Exception{"Illegal type " + arg->getName() + " of argument " + std::to_string(arg_idx + 1) + " of function "
- + getName(),
- ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
+ throw Exception(
+ ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
+ "Illegal type {} of argument {} of function {}",
+ arg->getName(),
+ arg_idx + 1,
+ getName());
}
return std::make_shared();
@@ -125,7 +123,7 @@ private:
std::vector data(num_arguments);
std::vector offsets(num_arguments);
std::vector fixed_string_sizes(num_arguments);
- std::vector constant_strings(num_arguments);
+ std::vector> constant_strings(num_arguments);
bool has_column_string = false;
bool has_column_fixed_string = false;
for (size_t i = 0; i < num_arguments; ++i)
diff --git a/src/Functions/formatString.cpp b/src/Functions/formatString.cpp
index e138c072639..ee90a082e5b 100644
--- a/src/Functions/formatString.cpp
+++ b/src/Functions/formatString.cpp
@@ -45,25 +45,23 @@ public:
DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
{
- if (arguments.empty())
+ if (arguments.size() < 2)
throw Exception(
- "Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size())
- + ", should be at least 1",
- ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
-
- if (arguments.size() > FormatImpl::argument_threshold)
- throw Exception(
- "Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size())
- + ", should be at most " + std::to_string(FormatImpl::argument_threshold),
- ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
+ ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH,
+ "Number of arguments for function {} doesn't match: passed {}, should be at least 2",
+ getName(),
+ arguments.size());
for (const auto arg_idx : collections::range(0, arguments.size()))
{
const auto * arg = arguments[arg_idx].get();
if (!isStringOrFixedString(arg))
throw Exception(
- "Illegal type " + arg->getName() + " of argument " + std::to_string(arg_idx + 1) + " of function " + getName(),
- ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
+ ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
+ "Illegal type {} of argument {} of function {}",
+ arg->getName(),
+ arg_idx + 1,
+ getName());
}
return std::make_shared();
@@ -84,7 +82,7 @@ public:
std::vector data(arguments.size() - 1);
std::vector offsets(arguments.size() - 1);
std::vector fixed_string_sizes(arguments.size() - 1);
- std::vector constant_strings(arguments.size() - 1);
+ std::vector> constant_strings(arguments.size() - 1);
bool has_column_string = false;
bool has_column_fixed_string = false;
diff --git a/src/Functions/formatString.h b/src/Functions/formatString.h
index 419ecf1c773..44fbdac9378 100644
--- a/src/Functions/formatString.h
+++ b/src/Functions/formatString.h
@@ -4,8 +4,10 @@
#include
#include
#include
+#include
#include
+
#include
#include
#include
@@ -15,15 +17,9 @@
namespace DB
{
-namespace ErrorCodes
-{
- extern const int BAD_ARGUMENTS;
-}
struct FormatImpl
{
- static constexpr size_t small_argument_threshold = 1024;
- static constexpr size_t argument_threshold = std::numeric_limits::max();
static constexpr size_t right_padding = 15;
template
@@ -39,165 +35,10 @@ struct FormatImpl
format(std::forward(args)...);
}
- static void parseNumber(const String & description, UInt64 l, UInt64 r, UInt64 & res)
- {
- res = 0;
- for (UInt64 pos = l; pos < r; ++pos)
- {
- if (!isNumericASCII(description[pos]))
- throw Exception("Not a number in curly braces at position " + std::to_string(pos), ErrorCodes::BAD_ARGUMENTS);
- res = res * 10 + description[pos] - '0';
- if (res >= argument_threshold)
- throw Exception(
- "Too big number for arguments, must be at most " + std::to_string(argument_threshold), ErrorCodes::BAD_ARGUMENTS);
- }
- }
-
- static inline void init(
- const String & pattern,
- const std::vector & data,
- size_t argument_number,
- const std::vector & constant_strings,
- UInt64 * index_positions_ptr,
- std::vector & substrings)
- {
- /// Is current position after open curly brace.
- bool is_open_curly = false;
- /// The position of last open token.
- size_t last_open = -1;
-
- /// Is formatting in a plain {} token.
- std::optional is_plain_numbering;
- UInt64 index_if_plain = 0;
-
- /// Left position of adding substrings, just to the closed brace position or the start of the string.
- /// Invariant --- the start of substring is in this position.
- size_t start_pos = 0;
-
- /// A flag to decide whether we should glue the constant strings.
- bool glue_to_next = false;
-
- /// Handling double braces (escaping).
- auto double_brace_removal = [](String & str)
- {
- size_t i = 0;
- bool should_delete = true;
- str.erase(
- std::remove_if(
- str.begin(),
- str.end(),
- [&i, &should_delete, &str](char)
- {
- bool is_double_brace = (str[i] == '{' && str[i + 1] == '{') || (str[i] == '}' && str[i + 1] == '}');
- ++i;
- if (is_double_brace && should_delete)
- {
- should_delete = false;
- return true;
- }
- should_delete = true;
- return false;
- }),
- str.end());
- };
-
- for (size_t i = 0; i < pattern.size(); ++i)
- {
- if (pattern[i] == '{')
- {
- /// Escaping handling
- /// It is safe to access because of null termination
- if (pattern[i + 1] == '{')
- {
- ++i;
- continue;
- }
-
- if (is_open_curly)
- throw Exception("Two open curly braces without close one at position " + std::to_string(i), ErrorCodes::BAD_ARGUMENTS);
-
- String to_add = String(pattern.data() + start_pos, i - start_pos);
- double_brace_removal(to_add);
- if (!glue_to_next)
- substrings.emplace_back(to_add);
- else
- substrings.back() += to_add;
-
- glue_to_next = false;
-
- is_open_curly = true;
- last_open = i + 1;
- }
- else if (pattern[i] == '}')
- {
- if (pattern[i + 1] == '}')
- {
- ++i;
- continue;
- }
-
- if (!is_open_curly)
- throw Exception("Closed curly brace without open one at position " + std::to_string(i), ErrorCodes::BAD_ARGUMENTS);
-
- is_open_curly = false;
-
- if (last_open == i)
- {
- if (is_plain_numbering && !*is_plain_numbering)
- throw Exception(
- "Cannot switch from automatic field numbering to manual field specification", ErrorCodes::BAD_ARGUMENTS);
- is_plain_numbering = true;
- if (index_if_plain >= argument_number)
- throw Exception("Argument is too big for formatting", ErrorCodes::BAD_ARGUMENTS);
- *index_positions_ptr = index_if_plain++;
- }
- else
- {
- if (is_plain_numbering && *is_plain_numbering)
- throw Exception(
- "Cannot switch from automatic field numbering to manual field specification", ErrorCodes::BAD_ARGUMENTS);
- is_plain_numbering = false;
-
- UInt64 arg;
- parseNumber(pattern, last_open, i, arg);
-
- if (arg >= argument_number)
- throw Exception(
- "Argument is too big for formatting. Note that indexing starts from zero", ErrorCodes::BAD_ARGUMENTS);
-
- *index_positions_ptr = arg;
- }
-
- /// Constant string.
- if (!data[*index_positions_ptr])
- {
- /// The next string should be glued to last `A {} C`.format('B') -> `A B C`.
- glue_to_next = true;
- substrings.back() += constant_strings[*index_positions_ptr];
- }
- else
- ++index_positions_ptr; /// Otherwise we commit arg number and proceed.
-
- start_pos = i + 1;
- }
- }
-
- if (is_open_curly)
- throw Exception("Last open curly brace is not closed", ErrorCodes::BAD_ARGUMENTS);
-
- String to_add = String(pattern.data() + start_pos, pattern.size() - start_pos);
- double_brace_removal(to_add);
-
- if (!glue_to_next)
- substrings.emplace_back(to_add);
- else
- substrings.back() += to_add;
- }
-
/// data for ColumnString and ColumnFixed. Nullptr means no data, it is const string.
/// offsets for ColumnString, nullptr is an indicator that there is a fixed string rather than ColumnString.
/// fixed_string_N for savings N to fixed strings.
- /// constant_strings for constant strings. If data[i] is nullptr, than it is constant string.
+ /// constant_strings for constant strings. If data[i] is nullptr, it is constant string.
/// res_data is result_data, res_offsets is offset result.
/// input_rows_count is the number of rows processed.
/// Precondition: data.size() == offsets.size() == fixed_string_N.size() == constant_strings.size().
@@ -207,29 +48,22 @@ struct FormatImpl
const std::vector & data,
const std::vector & offsets,
[[maybe_unused]] /* Because sometimes !has_column_fixed_string */ const std::vector & fixed_string_N,
- const std::vector & constant_strings,
+ const std::vector> & constant_strings,
ColumnString::Chars & res_data,
ColumnString::Offsets & res_offsets,
size_t input_rows_count)
{
const size_t argument_number = offsets.size();
- UInt64 small_index_positions_buffer[small_argument_threshold];
- /// The subsequent indexes of strings we should use. e.g `Hello world {1} {3} {1} {0}` this array will be filled with [1, 3, 1, 0, ... (garbage)] but without constant string indices.
- UInt64 * index_positions = small_index_positions_buffer;
-
- std::unique_ptr big_index_positions_buffer;
- if (argument_number > small_argument_threshold)
- {
- big_index_positions_buffer.reset(new UInt64[argument_number]);
- index_positions = big_index_positions_buffer.get();
- }
+ /// The subsequent indexes of strings we should use. e.g `Hello world {1} {3} {1} {0}` this
+ /// array will be filled with [1, 3, 1, 0] but without constant string indices.
+ Format::IndexPositions index_positions;
/// Vector of substrings of pattern that will be copied to the answer, not string view because of escaping and iterators invalidation.
/// These are exactly what is between {} tokens, for `Hello {} world {}` we will have [`Hello `, ` world `, ``].
std::vector substrings;
- init(pattern, data, argument_number, constant_strings, index_positions, substrings);
+ Format::init(pattern, argument_number, constant_strings, index_positions, substrings);
UInt64 final_size = 0;
@@ -271,7 +105,7 @@ struct FormatImpl
for (size_t j = 1; j < substrings.size(); ++j)
{
UInt64 arg = index_positions[j - 1];
- auto offset_ptr = offsets[arg];
+ const auto * offset_ptr = offsets[arg];
UInt64 arg_offset = 0;
UInt64 size = 0;
diff --git a/tests/queries/0_stateless/02245_format_string_stack_overflow.reference b/tests/queries/0_stateless/02245_format_string_stack_overflow.reference
new file mode 100644
index 00000000000..6e163a64914
--- /dev/null
+++ b/tests/queries/0_stateless/02245_format_string_stack_overflow.reference
@@ -0,0 +1 @@
+00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
diff --git a/tests/queries/0_stateless/02245_format_string_stack_overflow.sql b/tests/queries/0_stateless/02245_format_string_stack_overflow.sql
new file mode 100644
index 00000000000..1ee3606d3a6
--- /dev/null
+++ b/tests/queries/0_stateless/02245_format_string_stack_overflow.sql
@@ -0,0 +1 @@
+select format('{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}', toString(number)) str from numbers(1);