2018-11-01 13:30:38 +00:00
|
|
|
#include <Storages/MergeTree/ReplicatedMergeTreeTableMetadata.h>
|
|
|
|
#include <Storages/MergeTree/MergeTreeData.h>
|
|
|
|
#include <Parsers/formatAST.h>
|
|
|
|
#include <Parsers/parseQuery.h>
|
|
|
|
#include <Parsers/ExpressionListParsers.h>
|
|
|
|
#include <IO/Operators.h>
|
|
|
|
|
2019-11-28 10:13:53 +00:00
|
|
|
|
2018-11-01 13:30:38 +00:00
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int METADATA_MISMATCH;
|
|
|
|
}
|
|
|
|
|
|
|
|
static String formattedAST(const ASTPtr & ast)
|
|
|
|
{
|
|
|
|
if (!ast)
|
|
|
|
return "";
|
2020-11-09 16:05:40 +00:00
|
|
|
WriteBufferFromOwnString buf;
|
|
|
|
formatAST(*ast, buf, false, true);
|
|
|
|
return buf.str();
|
2018-11-01 13:30:38 +00:00
|
|
|
}
|
|
|
|
|
2020-06-16 16:55:04 +00:00
|
|
|
ReplicatedMergeTreeTableMetadata::ReplicatedMergeTreeTableMetadata(const MergeTreeData & data, const StorageMetadataPtr & metadata_snapshot)
|
2018-11-01 13:30:38 +00:00
|
|
|
{
|
|
|
|
if (data.format_version < MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING)
|
2021-03-02 10:33:54 +00:00
|
|
|
{
|
|
|
|
auto minmax_idx_column_names = data.getMinMaxColumnsNames(metadata_snapshot->getPartitionKey());
|
|
|
|
date_column = minmax_idx_column_names[data.minmax_idx_date_column_pos];
|
|
|
|
}
|
2018-11-01 13:30:38 +00:00
|
|
|
|
2019-08-26 14:24:29 +00:00
|
|
|
const auto data_settings = data.getSettings();
|
2020-06-17 12:07:09 +00:00
|
|
|
sampling_expression = formattedAST(metadata_snapshot->getSamplingKeyAST());
|
2019-08-13 10:29:31 +00:00
|
|
|
index_granularity = data_settings->index_granularity;
|
2018-11-02 15:39:19 +00:00
|
|
|
merging_params_mode = static_cast<int>(data.merging_params.mode);
|
|
|
|
sign_column = data.merging_params.sign_column;
|
2018-11-06 18:25:36 +00:00
|
|
|
|
2019-12-27 14:36:59 +00:00
|
|
|
/// This code may looks strange, but previously we had only one entity: PRIMARY KEY (or ORDER BY, it doesn't matter)
|
|
|
|
/// Now we have two different entities ORDER BY and it's optional prefix -- PRIMARY KEY.
|
2019-12-27 16:34:50 +00:00
|
|
|
/// In most cases user doesn't specify PRIMARY KEY and semantically it's equal to ORDER BY.
|
2019-12-27 14:36:59 +00:00
|
|
|
/// So rules in zookeeper metadata is following:
|
|
|
|
/// - When we have only ORDER BY, than store it in "primary key:" row of /metadata
|
|
|
|
/// - When we have both, than store PRIMARY KEY in "primary key:" row and ORDER BY in "sorting key:" row of /metadata
|
2020-07-04 07:35:17 +00:00
|
|
|
|
|
|
|
primary_key = formattedAST(metadata_snapshot->getPrimaryKey().expression_list_ast);
|
|
|
|
if (metadata_snapshot->isPrimaryKeyDefined())
|
2021-09-02 14:47:00 +00:00
|
|
|
{
|
|
|
|
/// We don't use preparsed AST `sorting_key.expression_list_ast` because
|
|
|
|
/// it contain version column for VersionedCollapsingMergeTree, which
|
2021-09-02 15:29:26 +00:00
|
|
|
/// is not stored in ZooKeeper for compatibility reasons. So the best
|
2021-09-02 14:47:00 +00:00
|
|
|
/// compatible way is just to convert definition_ast to list and
|
|
|
|
/// serialize it. In all other places key.expression_list_ast should be
|
|
|
|
/// used.
|
|
|
|
sorting_key = formattedAST(extractKeyExpressionList(metadata_snapshot->getSortingKey().definition_ast));
|
|
|
|
}
|
2018-11-06 18:25:36 +00:00
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
data_format_version = data.format_version;
|
2018-11-01 13:30:38 +00:00
|
|
|
|
|
|
|
if (data.format_version >= MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING)
|
2020-06-17 10:34:23 +00:00
|
|
|
partition_key = formattedAST(metadata_snapshot->getPartitionKey().expression_list_ast);
|
2019-02-05 14:50:25 +00:00
|
|
|
|
2020-06-16 16:55:04 +00:00
|
|
|
ttl_table = formattedAST(metadata_snapshot->getTableTTLs().definition_ast);
|
2019-11-28 10:13:53 +00:00
|
|
|
|
2020-06-16 16:55:04 +00:00
|
|
|
skip_indices = metadata_snapshot->getSecondaryIndices().toString();
|
2021-02-10 14:12:49 +00:00
|
|
|
|
|
|
|
projections = metadata_snapshot->getProjections().toString();
|
|
|
|
|
2019-06-20 16:25:32 +00:00
|
|
|
if (data.canUseAdaptiveGranularity())
|
2019-08-13 10:29:31 +00:00
|
|
|
index_granularity_bytes = data_settings->index_granularity_bytes;
|
2019-06-20 16:25:32 +00:00
|
|
|
else
|
|
|
|
index_granularity_bytes = 0;
|
2019-07-04 19:40:00 +00:00
|
|
|
|
2020-06-16 16:55:04 +00:00
|
|
|
constraints = metadata_snapshot->getConstraints().toString();
|
2018-11-02 15:39:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ReplicatedMergeTreeTableMetadata::write(WriteBuffer & out) const
|
|
|
|
{
|
2020-05-23 19:35:08 +00:00
|
|
|
out << "metadata format version: 1\n"
|
2018-11-02 15:39:19 +00:00
|
|
|
<< "date column: " << date_column << "\n"
|
|
|
|
<< "sampling expression: " << sampling_expression << "\n"
|
|
|
|
<< "index granularity: " << index_granularity << "\n"
|
|
|
|
<< "mode: " << merging_params_mode << "\n"
|
|
|
|
<< "sign column: " << sign_column << "\n"
|
|
|
|
<< "primary key: " << primary_key << "\n";
|
|
|
|
|
|
|
|
if (data_format_version >= MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING)
|
2018-11-01 13:30:38 +00:00
|
|
|
{
|
2018-11-02 15:39:19 +00:00
|
|
|
out << "data format version: " << data_format_version.toUnderType() << "\n"
|
|
|
|
<< "partition key: " << partition_key << "\n";
|
2018-11-01 13:30:38 +00:00
|
|
|
}
|
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
if (!sorting_key.empty())
|
|
|
|
out << "sorting key: " << sorting_key << "\n";
|
2019-02-05 14:50:25 +00:00
|
|
|
|
2019-04-15 09:30:45 +00:00
|
|
|
if (!ttl_table.empty())
|
|
|
|
out << "ttl: " << ttl_table << "\n";
|
|
|
|
|
2019-02-05 14:50:25 +00:00
|
|
|
if (!skip_indices.empty())
|
|
|
|
out << "indices: " << skip_indices << "\n";
|
2019-03-28 12:44:14 +00:00
|
|
|
|
2021-02-10 14:12:49 +00:00
|
|
|
if (!projections.empty())
|
|
|
|
out << "projections: " << projections << "\n";
|
|
|
|
|
2019-03-28 12:44:14 +00:00
|
|
|
if (index_granularity_bytes != 0)
|
|
|
|
out << "granularity bytes: " << index_granularity_bytes << "\n";
|
2019-08-14 19:51:03 +00:00
|
|
|
|
|
|
|
if (!constraints.empty())
|
|
|
|
out << "constraints: " << constraints << "\n";
|
2018-11-01 13:30:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String ReplicatedMergeTreeTableMetadata::toString() const
|
|
|
|
{
|
|
|
|
WriteBufferFromOwnString out;
|
|
|
|
write(out);
|
|
|
|
return out.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplicatedMergeTreeTableMetadata::read(ReadBuffer & in)
|
|
|
|
{
|
|
|
|
in >> "metadata format version: 1\n";
|
2018-11-02 15:39:19 +00:00
|
|
|
in >> "date column: " >> date_column >> "\n";
|
|
|
|
in >> "sampling expression: " >> sampling_expression >> "\n";
|
|
|
|
in >> "index granularity: " >> index_granularity >> "\n";
|
|
|
|
in >> "mode: " >> merging_params_mode >> "\n";
|
|
|
|
in >> "sign column: " >> sign_column >> "\n";
|
|
|
|
in >> "primary key: " >> primary_key >> "\n";
|
2018-11-01 13:30:38 +00:00
|
|
|
|
|
|
|
if (in.eof())
|
2018-11-02 15:39:19 +00:00
|
|
|
data_format_version = 0;
|
2019-06-19 12:30:56 +00:00
|
|
|
else if (checkString("data format version: ", in))
|
|
|
|
in >> data_format_version.toUnderType() >> "\n";
|
2018-11-01 13:30:38 +00:00
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
if (data_format_version >= MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING)
|
|
|
|
in >> "partition key: " >> partition_key >> "\n";
|
2018-11-01 13:30:38 +00:00
|
|
|
|
|
|
|
if (checkString("sorting key: ", in))
|
2018-11-02 15:39:19 +00:00
|
|
|
in >> sorting_key >> "\n";
|
2019-02-05 14:50:25 +00:00
|
|
|
|
2019-06-17 10:34:25 +00:00
|
|
|
if (checkString("ttl: ", in))
|
|
|
|
in >> ttl_table >> "\n";
|
|
|
|
|
2019-02-05 14:50:25 +00:00
|
|
|
if (checkString("indices: ", in))
|
|
|
|
in >> skip_indices >> "\n";
|
2019-03-28 12:44:14 +00:00
|
|
|
|
2021-02-10 14:12:49 +00:00
|
|
|
if (checkString("projections: ", in))
|
|
|
|
in >> projections >> "\n";
|
|
|
|
|
2019-03-28 12:44:14 +00:00
|
|
|
if (checkString("granularity bytes: ", in))
|
2019-06-20 16:25:32 +00:00
|
|
|
{
|
2019-03-28 12:44:14 +00:00
|
|
|
in >> index_granularity_bytes >> "\n";
|
2019-06-20 16:25:32 +00:00
|
|
|
index_granularity_bytes_found_in_zk = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
index_granularity_bytes = 0;
|
2019-08-14 19:51:03 +00:00
|
|
|
|
|
|
|
if (checkString("constraints: ", in))
|
|
|
|
in >> constraints >> "\n";
|
2018-11-01 13:30:38 +00:00
|
|
|
}
|
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
ReplicatedMergeTreeTableMetadata ReplicatedMergeTreeTableMetadata::parse(const String & s)
|
2018-11-01 13:30:38 +00:00
|
|
|
{
|
2018-11-02 15:39:19 +00:00
|
|
|
ReplicatedMergeTreeTableMetadata metadata;
|
2018-11-01 13:30:38 +00:00
|
|
|
ReadBufferFromString buf(s);
|
|
|
|
metadata.read(buf);
|
|
|
|
return metadata;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-14 10:17:04 +00:00
|
|
|
void ReplicatedMergeTreeTableMetadata::checkImmutableFieldsEquals(const ReplicatedMergeTreeTableMetadata & from_zk) const
|
|
|
|
{
|
2018-11-02 15:39:19 +00:00
|
|
|
if (data_format_version < MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING)
|
2018-11-01 13:30:38 +00:00
|
|
|
{
|
2018-11-02 15:39:19 +00:00
|
|
|
if (date_column != from_zk.date_column)
|
2018-11-01 13:30:38 +00:00
|
|
|
throw Exception("Existing table metadata in ZooKeeper differs in date index column."
|
2018-11-02 15:39:19 +00:00
|
|
|
" Stored in ZooKeeper: " + from_zk.date_column + ", local: " + date_column,
|
2018-11-01 13:30:38 +00:00
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
|
|
|
}
|
2018-11-02 15:39:19 +00:00
|
|
|
else if (!from_zk.date_column.empty())
|
2020-02-14 10:17:04 +00:00
|
|
|
{
|
2018-11-01 13:30:38 +00:00
|
|
|
throw Exception(
|
|
|
|
"Existing table metadata in ZooKeeper differs in date index column."
|
2018-11-02 15:39:19 +00:00
|
|
|
" Stored in ZooKeeper: " + from_zk.date_column + ", local is custom-partitioned.",
|
2018-11-01 13:30:38 +00:00
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
2020-02-14 10:17:04 +00:00
|
|
|
}
|
2018-11-01 13:30:38 +00:00
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
if (index_granularity != from_zk.index_granularity)
|
2018-11-01 13:30:38 +00:00
|
|
|
throw Exception("Existing table metadata in ZooKeeper differs in index granularity."
|
2018-11-02 15:39:19 +00:00
|
|
|
" Stored in ZooKeeper: " + DB::toString(from_zk.index_granularity) + ", local: " + DB::toString(index_granularity),
|
2018-11-01 13:30:38 +00:00
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
if (merging_params_mode != from_zk.merging_params_mode)
|
2018-11-01 13:30:38 +00:00
|
|
|
throw Exception("Existing table metadata in ZooKeeper differs in mode of merge operation."
|
2018-11-02 15:39:19 +00:00
|
|
|
" Stored in ZooKeeper: " + DB::toString(from_zk.merging_params_mode) + ", local: "
|
|
|
|
+ DB::toString(merging_params_mode),
|
2018-11-01 13:30:38 +00:00
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
if (sign_column != from_zk.sign_column)
|
2018-11-01 13:30:38 +00:00
|
|
|
throw Exception("Existing table metadata in ZooKeeper differs in sign column."
|
2018-11-02 15:39:19 +00:00
|
|
|
" Stored in ZooKeeper: " + from_zk.sign_column + ", local: " + sign_column,
|
2018-11-01 13:30:38 +00:00
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
|
|
|
|
|
|
|
/// NOTE: You can make a less strict check of match expressions so that tables do not break from small changes
|
|
|
|
/// in formatAST code.
|
2018-11-02 15:39:19 +00:00
|
|
|
if (primary_key != from_zk.primary_key)
|
2018-11-01 13:30:38 +00:00
|
|
|
throw Exception("Existing table metadata in ZooKeeper differs in primary key."
|
2018-11-02 15:39:19 +00:00
|
|
|
" Stored in ZooKeeper: " + from_zk.primary_key + ", local: " + primary_key,
|
2018-11-01 13:30:38 +00:00
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
if (data_format_version != from_zk.data_format_version)
|
2018-11-01 13:30:38 +00:00
|
|
|
throw Exception("Existing table metadata in ZooKeeper differs in data format version."
|
2018-11-02 15:39:19 +00:00
|
|
|
" Stored in ZooKeeper: " + DB::toString(from_zk.data_format_version.toUnderType()) +
|
|
|
|
", local: " + DB::toString(data_format_version.toUnderType()),
|
2018-11-01 13:30:38 +00:00
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
if (partition_key != from_zk.partition_key)
|
|
|
|
throw Exception(
|
|
|
|
"Existing table metadata in ZooKeeper differs in partition key expression."
|
|
|
|
" Stored in ZooKeeper: " + from_zk.partition_key + ", local: " + partition_key,
|
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
2018-11-01 13:30:38 +00:00
|
|
|
|
2020-02-14 10:17:04 +00:00
|
|
|
}
|
|
|
|
|
2021-04-10 23:33:54 +00:00
|
|
|
void ReplicatedMergeTreeTableMetadata::checkEquals(const ReplicatedMergeTreeTableMetadata & from_zk, const ColumnsDescription & columns, ContextPtr context) const
|
2020-02-14 10:17:04 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
checkImmutableFieldsEquals(from_zk);
|
|
|
|
|
2020-08-27 13:10:10 +00:00
|
|
|
if (sampling_expression != from_zk.sampling_expression)
|
|
|
|
throw Exception("Existing table metadata in ZooKeeper differs in sample expression."
|
|
|
|
" Stored in ZooKeeper: " + from_zk.sampling_expression + ", local: " + sampling_expression,
|
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
if (sorting_key != from_zk.sorting_key)
|
|
|
|
{
|
2020-02-14 10:17:04 +00:00
|
|
|
throw Exception(
|
|
|
|
"Existing table metadata in ZooKeeper differs in sorting key expression."
|
|
|
|
" Stored in ZooKeeper: " + from_zk.sorting_key + ", local: " + sorting_key,
|
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
2018-11-01 13:30:38 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 09:30:45 +00:00
|
|
|
if (ttl_table != from_zk.ttl_table)
|
|
|
|
{
|
2020-02-14 10:17:04 +00:00
|
|
|
throw Exception(
|
|
|
|
"Existing table metadata in ZooKeeper differs in TTL."
|
|
|
|
" Stored in ZooKeeper: " + from_zk.ttl_table +
|
|
|
|
", local: " + ttl_table,
|
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
2019-04-15 09:30:45 +00:00
|
|
|
}
|
|
|
|
|
2020-06-17 18:03:22 +00:00
|
|
|
String parsed_zk_skip_indices = IndicesDescription::parse(from_zk.skip_indices, columns, context).toString();
|
|
|
|
if (skip_indices != parsed_zk_skip_indices)
|
2019-02-05 14:50:25 +00:00
|
|
|
{
|
2020-02-14 10:17:04 +00:00
|
|
|
throw Exception(
|
|
|
|
"Existing table metadata in ZooKeeper differs in skip indexes."
|
|
|
|
" Stored in ZooKeeper: " + from_zk.skip_indices +
|
2020-06-17 18:03:22 +00:00
|
|
|
", parsed from ZooKeeper: " + parsed_zk_skip_indices +
|
2020-02-14 10:17:04 +00:00
|
|
|
", local: " + skip_indices,
|
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
2019-02-05 14:50:25 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 14:12:49 +00:00
|
|
|
String parsed_zk_projections = ProjectionsDescription::parse(from_zk.projections, columns, context).toString();
|
|
|
|
if (projections != parsed_zk_projections)
|
|
|
|
{
|
|
|
|
throw Exception(
|
|
|
|
"Existing table metadata in ZooKeeper differs in projections."
|
|
|
|
" Stored in ZooKeeper: " + from_zk.projections +
|
|
|
|
", parsed from ZooKeeper: " + parsed_zk_projections +
|
|
|
|
", local: " + projections,
|
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
|
|
|
}
|
|
|
|
|
2020-06-17 18:03:22 +00:00
|
|
|
String parsed_zk_constraints = ConstraintsDescription::parse(from_zk.constraints).toString();
|
|
|
|
if (constraints != parsed_zk_constraints)
|
2019-06-02 14:41:12 +00:00
|
|
|
{
|
2020-02-14 10:17:04 +00:00
|
|
|
throw Exception(
|
|
|
|
"Existing table metadata in ZooKeeper differs in constraints."
|
|
|
|
" Stored in ZooKeeper: " + from_zk.constraints +
|
2020-06-17 18:03:22 +00:00
|
|
|
", parsed from ZooKeeper: " + parsed_zk_constraints +
|
2020-02-14 10:17:04 +00:00
|
|
|
", local: " + constraints,
|
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
2019-06-02 14:41:12 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 16:25:32 +00:00
|
|
|
if (from_zk.index_granularity_bytes_found_in_zk && index_granularity_bytes != from_zk.index_granularity_bytes)
|
2019-03-28 12:44:14 +00:00
|
|
|
throw Exception("Existing table metadata in ZooKeeper differs in index granularity bytes."
|
|
|
|
" Stored in ZooKeeper: " + DB::toString(from_zk.index_granularity_bytes) +
|
|
|
|
", local: " + DB::toString(index_granularity_bytes),
|
|
|
|
ErrorCodes::METADATA_MISMATCH);
|
2020-02-14 10:17:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ReplicatedMergeTreeTableMetadata::Diff
|
|
|
|
ReplicatedMergeTreeTableMetadata::checkAndFindDiff(const ReplicatedMergeTreeTableMetadata & from_zk) const
|
|
|
|
{
|
|
|
|
|
|
|
|
checkImmutableFieldsEquals(from_zk);
|
|
|
|
|
|
|
|
Diff diff;
|
|
|
|
|
|
|
|
if (sorting_key != from_zk.sorting_key)
|
|
|
|
{
|
|
|
|
diff.sorting_key_changed = true;
|
|
|
|
diff.new_sorting_key = from_zk.sorting_key;
|
|
|
|
}
|
|
|
|
|
2020-08-27 13:10:10 +00:00
|
|
|
if (sampling_expression != from_zk.sampling_expression)
|
|
|
|
{
|
|
|
|
diff.sampling_expression_changed = true;
|
|
|
|
diff.new_sampling_expression = from_zk.sampling_expression;
|
|
|
|
}
|
|
|
|
|
2020-02-14 10:17:04 +00:00
|
|
|
if (ttl_table != from_zk.ttl_table)
|
|
|
|
{
|
|
|
|
diff.ttl_table_changed = true;
|
|
|
|
diff.new_ttl_table = from_zk.ttl_table;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (skip_indices != from_zk.skip_indices)
|
|
|
|
{
|
|
|
|
diff.skip_indices_changed = true;
|
|
|
|
diff.new_skip_indices = from_zk.skip_indices;
|
|
|
|
}
|
|
|
|
|
2021-02-10 14:12:49 +00:00
|
|
|
if (projections != from_zk.projections)
|
|
|
|
{
|
|
|
|
diff.projections_changed = true;
|
|
|
|
diff.new_projections = from_zk.projections;
|
|
|
|
}
|
|
|
|
|
2020-02-14 10:17:04 +00:00
|
|
|
if (constraints != from_zk.constraints)
|
|
|
|
{
|
|
|
|
diff.constraints_changed = true;
|
|
|
|
diff.new_constraints = from_zk.constraints;
|
|
|
|
}
|
2019-03-28 12:44:14 +00:00
|
|
|
|
2018-11-02 15:39:19 +00:00
|
|
|
return diff;
|
2018-11-01 13:30:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|