1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "schema_negotiate.h"
16
17 #include "db_common.h"
18 #include "log_print.h"
19 #include "schema_utils.h"
20
21 namespace DistributedDB {
22 // Some principle in current version describe below. (Relative-type will be introduced in future but not involved now)
23 // 1. PermitSync: Be false may because schemaType-unrecognized, schemaType-different, schema-unparsable,
24 // schemaVersion-unrecognized, schema-incompatible, and so on.
25 // 2. RequirePeerConvert: Be true normally when permitSync false, for future possible sync and convert(by remote).
26 // 3. checkOnReceive: Be false when local is KV-DB, or when local is not KV-DB only if schema type equal as well as
27 // define equal or remote is the upgradation of local.
MakeLocalSyncOpinion(const SchemaObject & localSchema,const std::string & remoteSchema,uint8_t remoteSchemaType)28 SyncOpinion SchemaNegotiate::MakeLocalSyncOpinion(const SchemaObject &localSchema, const std::string &remoteSchema,
29 uint8_t remoteSchemaType)
30 {
31 SchemaType localType = localSchema.GetSchemaType(); // An invalid schemaObject will return SchemaType::NONE
32 SchemaType remoteType = ReadSchemaType(remoteSchemaType);
33 // Logic below only be correct in current version, should be redesigned if new type added in the future
34 // 1. If remote-type unrecognized(Include Relative-type), Do not permit sync.
35 if (remoteType == SchemaType::UNRECOGNIZED) {
36 LOGE("[Schema][Opinion] Remote-type=%" PRIu8 " unrecognized.", remoteSchemaType);
37 return SyncOpinion{false, true, true};
38 }
39 // 2. If local-type is KV(Here remote-type is within recognized), Always permit sync.
40 if (localType == SchemaType::NONE) {
41 LOGI("[Schema][Opinion] Local-type KV.");
42 return SyncOpinion{true, false, false};
43 }
44 // 3. If remote-type is KV(Here local-type can only be JSON or FLATBUFFER), Always permit sync but need check.
45 if (remoteType == SchemaType::NONE) {
46 LOGI("[Schema][Opinion] Remote-type KV.");
47 return SyncOpinion{true, false, true};
48 }
49 // 4. If local-type differ with remote-type(Here both type can only be JSON or FLATBUFFER), Do not permit sync.
50 if (localType != remoteType) {
51 LOGE("[Schema][Opinion] Local-type=%s differ remote-type=%s.", SchemaUtils::SchemaTypeString(localType).c_str(),
52 SchemaUtils::SchemaTypeString(remoteType).c_str());
53 return SyncOpinion{false, true, true};
54 }
55 // 5. If schema parse fail, Do not permit sync.
56 SchemaObject remoteSchemaObj;
57 int errCode = remoteSchemaObj.ParseFromSchemaString(remoteSchema);
58 if (errCode != E_OK) {
59 LOGE("[Schema][Opinion] Parse remote-schema fail, errCode=%d, remote-type=%s.", errCode,
60 SchemaUtils::SchemaTypeString(remoteType).c_str());
61 return SyncOpinion{false, true, true};
62 }
63 // 6. If remote-schema is not incompatible based on local-schema(SchemaDefine Equal), Permit sync and don't check.
64 errCode = localSchema.CompareAgainstSchemaObject(remoteSchemaObj);
65 if (errCode != -E_SCHEMA_UNEQUAL_INCOMPATIBLE) {
66 return SyncOpinion{true, false, false};
67 }
68 // 7. If local-schema is not incompatible based on remote-schema(Can only be COMPATIBLE_UPGRADE), Sync and check.
69 errCode = remoteSchemaObj.CompareAgainstSchemaObject(localSchema);
70 if (errCode != -E_SCHEMA_UNEQUAL_INCOMPATIBLE) {
71 return SyncOpinion{true, false, true};
72 }
73 // 8. Local-schema incompatible with remote-schema mutually.
74 LOGE("[Schema][Opinion] Local-schema incompatible with remote-schema mutually.");
75 return SyncOpinion{false, true, true};
76 }
77
ConcludeSyncStrategy(const SyncOpinion & localOpinion,const SyncOpinion & remoteOpinion)78 SyncStrategy SchemaNegotiate::ConcludeSyncStrategy(const SyncOpinion &localOpinion, const SyncOpinion &remoteOpinion)
79 {
80 SyncStrategy outStrategy;
81 // Any side permit sync, the final conclusion is permit sync.
82 outStrategy.permitSync = (localOpinion.permitSync || remoteOpinion.permitSync);
83 bool convertConflict = (localOpinion.requirePeerConvert && remoteOpinion.requirePeerConvert);
84 if (convertConflict) {
85 outStrategy.permitSync = false;
86 }
87 // Responsible for conversion on send now that local do not require remote to do conversion
88 outStrategy.convertOnSend = (!localOpinion.requirePeerConvert);
89 // Responsible for conversion on receive since remote will not do conversion on send and require local to convert
90 outStrategy.convertOnReceive = remoteOpinion.requirePeerConvert;
91 // Only depend on local opinion
92 outStrategy.checkOnReceive = localOpinion.checkOnReceive;
93 LOGI("[Schema][Strategy] PermitSync=%d, SendConvert=%d, ReceiveConvert=%d, ReceiveCheck=%d.",
94 outStrategy.permitSync, outStrategy.convertOnSend, outStrategy.convertOnReceive, outStrategy.checkOnReceive);
95 return outStrategy;
96 }
97
MakeOpinionEachTable(const RelationalSchemaObject & localSchema,const RelationalSchemaObject & remoteSchema)98 RelationalSyncOpinion SchemaNegotiate::MakeOpinionEachTable(const RelationalSchemaObject &localSchema,
99 const RelationalSchemaObject &remoteSchema)
100 {
101 RelationalSyncOpinion opinion;
102 for (const auto &it : localSchema.GetTables()) {
103 if (!DBCommon::CaseInsensitiveCompare(remoteSchema.GetTable(it.first).GetTableName(), it.first)) {
104 LOGW("[RelationalSchema][opinion] Table was missing in remote schema");
105 continue;
106 }
107 // remote table is compatible(equal or upgrade) based on local table, permit sync and don't need check
108 int errCode = it.second.CompareWithTable(remoteSchema.GetTable(it.first), localSchema.GetSchemaVersion());
109 if (errCode != -E_RELATIONAL_TABLE_INCOMPATIBLE) {
110 opinion[it.first] = {true, false, false};
111 continue;
112 }
113 // local table is compatible upgrade based on remote table, permit sync and need check
114 errCode = remoteSchema.GetTable(it.first).CompareWithTable(it.second, remoteSchema.GetSchemaVersion());
115 if (errCode != -E_RELATIONAL_TABLE_INCOMPATIBLE) {
116 opinion[it.first] = {true, false, true};
117 continue;
118 }
119 // local table is incompatible with remote table mutually, don't permit sync and need check
120 LOGW("[RelationalSchema][opinion] Local table is incompatible with remote table mutually.");
121 opinion[it.first] = {false, true, true};
122 }
123 return opinion;
124 }
125
MakeLocalSyncOpinion(const RelationalSchemaObject & localSchema,const std::string & remoteSchema,uint8_t remoteSchemaType)126 RelationalSyncOpinion SchemaNegotiate::MakeLocalSyncOpinion(const RelationalSchemaObject &localSchema,
127 const std::string &remoteSchema, uint8_t remoteSchemaType)
128 {
129 SchemaType localType = localSchema.GetSchemaType();
130 SchemaType remoteType = ReadSchemaType(remoteSchemaType);
131 if (remoteType == SchemaType::UNRECOGNIZED) {
132 LOGW("[RelationalSchema][opinion] Remote schema type %" PRIu8 " is unrecognized.", remoteSchemaType);
133 return {};
134 }
135
136 if (remoteType != SchemaType::RELATIVE) {
137 LOGW("[RelationalSchema][opinion] Not support sync with schema type: local-type=[%s] remote-type=[%s]",
138 SchemaUtils::SchemaTypeString(localType).c_str(), SchemaUtils::SchemaTypeString(remoteType).c_str());
139 return {};
140 }
141
142 if (!localSchema.IsSchemaValid()) {
143 LOGW("[RelationalSchema][opinion] Local schema is not valid");
144 return {};
145 }
146
147 RelationalSchemaObject remoteSchemaObj;
148 int errCode = remoteSchemaObj.ParseFromSchemaString(remoteSchema);
149 if (errCode != E_OK) {
150 LOGW("[RelationalSchema][opinion] Parse remote schema failed %d, remote schema type %s", errCode,
151 SchemaUtils::SchemaTypeString(remoteType).c_str());
152 return {};
153 }
154
155 if (localSchema.GetSchemaVersion() != remoteSchemaObj.GetSchemaVersion()) {
156 LOGW("[RelationalSchema][opinion] Schema version mismatch, local %s, remote %s",
157 localSchema.GetSchemaVersion().c_str(), remoteSchemaObj.GetSchemaVersion().c_str());
158 return {};
159 }
160
161 if (localSchema.GetSchemaVersion() == SchemaConstant::SCHEMA_SUPPORT_VERSION_V2_1 &&
162 localSchema.GetTableMode() != remoteSchemaObj.GetTableMode()) {
163 LOGW("[RelationalSchema][opinion] Schema table mode mismatch, local %d, remote %d",
164 localSchema.GetTableMode(), remoteSchemaObj.GetTableMode());
165 return {};
166 }
167
168 return MakeOpinionEachTable(localSchema, remoteSchemaObj);
169 }
170
ConcludeSyncStrategy(const RelationalSyncOpinion & localOpinion,const RelationalSyncOpinion & remoteOpinion)171 RelationalSyncStrategy SchemaNegotiate::ConcludeSyncStrategy(const RelationalSyncOpinion &localOpinion,
172 const RelationalSyncOpinion &remoteOpinion)
173 {
174 RelationalSyncStrategy syncStrategy;
175 for (const auto &itLocal : localOpinion) {
176 if (remoteOpinion.find(itLocal.first) == remoteOpinion.end()) {
177 LOGW("[RelationalSchema][Strategy] Table opinion is not found from remote.");
178 continue;
179 }
180 SyncOpinion localTableOpinion = itLocal.second;
181 SyncOpinion remoteTableOpinion = remoteOpinion.at(itLocal.first);
182 syncStrategy[itLocal.first] = ConcludeSyncStrategy(localTableOpinion, remoteTableOpinion);
183 }
184
185 return syncStrategy;
186 }
187
188 namespace {
189 const std::string MAGIC = "relational_opinion";
190 const uint32_t SYNC_OPINION_VERSION = 1;
191 } // namespace
192
193
CalculateParcelLen(const RelationalSyncOpinion & opinions)194 uint32_t SchemaNegotiate::CalculateParcelLen(const RelationalSyncOpinion &opinions)
195 {
196 uint64_t len = Parcel::GetStringLen(MAGIC);
197 len += Parcel::GetUInt32Len();
198 len += Parcel::GetUInt32Len();
199 len = Parcel::GetEightByteAlign(len);
200 for (const auto &it : opinions) {
201 len += Parcel::GetStringLen(it.first);
202 len += Parcel::GetUInt32Len();
203 len += Parcel::GetUInt32Len();
204 len = Parcel::GetEightByteAlign(len);
205 }
206 if (len > UINT32_MAX) {
207 return 0;
208 }
209 return static_cast<uint32_t>(len);
210 }
211
SerializeData(const RelationalSyncOpinion & opinions,Parcel & parcel)212 int SchemaNegotiate::SerializeData(const RelationalSyncOpinion &opinions, Parcel &parcel)
213 {
214 (void)parcel.WriteString(MAGIC);
215 (void)parcel.WriteUInt32(SYNC_OPINION_VERSION);
216 (void)parcel.WriteUInt32(static_cast<uint32_t>(opinions.size()));
217 parcel.EightByteAlign();
218 for (const auto &it : opinions) {
219 (void)parcel.WriteString(it.first);
220 (void)parcel.WriteUInt32(it.second.permitSync);
221 (void)parcel.WriteUInt32(it.second.requirePeerConvert);
222 parcel.EightByteAlign();
223 }
224 return parcel.IsError() ? -E_INVALID_ARGS : E_OK;
225 }
226
DeserializeData(Parcel & parcel,RelationalSyncOpinion & opinion)227 int SchemaNegotiate::DeserializeData(Parcel &parcel, RelationalSyncOpinion &opinion)
228 {
229 if (!parcel.IsContinueRead()) {
230 return E_OK;
231 }
232 std::string magicStr;
233 (void)parcel.ReadString(magicStr);
234 if (magicStr != MAGIC) {
235 LOGE("Deserialize sync opinion failed while read MAGIC string");
236 return -E_INVALID_ARGS;
237 }
238 uint32_t version;
239 (void)parcel.ReadUInt32(version);
240 if (version != SYNC_OPINION_VERSION) {
241 LOGE("Not support sync opinion version: %u", version);
242 return -E_NOT_SUPPORT;
243 }
244 uint32_t opinionSize;
245 (void)parcel.ReadUInt32(opinionSize);
246 parcel.EightByteAlign();
247 static const uint32_t MAX_OPINION_SIZE = 1024; // max 1024 opinions
248 if (parcel.IsError() || opinionSize > MAX_OPINION_SIZE) {
249 return -E_INVALID_ARGS;
250 }
251 for (uint32_t i = 0; i < opinionSize; i++) {
252 std::string tableName;
253 SyncOpinion tableOpinion;
254 (void)parcel.ReadString(tableName);
255 uint32_t permitSync;
256 (void)parcel.ReadUInt32(permitSync);
257 tableOpinion.permitSync = static_cast<bool>(permitSync);
258 uint32_t requirePeerConvert;
259 (void)parcel.ReadUInt32(requirePeerConvert);
260 tableOpinion.requirePeerConvert = static_cast<bool>(requirePeerConvert);
261 (void)parcel.EightByteAlign();
262 opinion[tableName] = tableOpinion;
263 }
264 return parcel.IsError() ? -E_INVALID_ARGS : E_OK;
265 }
266 }