1 /*
2  * Copyright (c) 2023 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 
16 #include "cloud/schema_mgr.h"
17 
18 #include <unordered_set>
19 
20 #include "cloud/cloud_db_constant.h"
21 #include "cloud/cloud_storage_utils.h"
22 #include "cloud/cloud_store_types.h"
23 #include "db_common.h"
24 #include "db_errno.h"
25 namespace DistributedDB {
SchemaMgr()26 SchemaMgr::SchemaMgr()
27 {
28 }
29 
ChkSchema(const TableName & tableName,RelationalSchemaObject & localSchema)30 int SchemaMgr::ChkSchema(const TableName &tableName, RelationalSchemaObject &localSchema)
31 {
32     if (cloudSchema_ == nullptr) {
33         LOGE("Cloud schema has not been set");
34         return -E_SCHEMA_MISMATCH;
35     }
36     TableInfo tableInfo = localSchema.GetTable(tableName);
37     if (tableInfo.Empty()) {
38         LOGE("Local schema does not contain certain table [ %s size = %d ]",
39             DBCommon::StringMiddleMasking(tableName).c_str(), tableName.size());
40         return -E_SCHEMA_MISMATCH;
41     }
42     if (tableInfo.GetTableSyncType() != TableSyncType::CLOUD_COOPERATION) {
43         LOGE("Sync type of local table [ %s size = %d ] is not CLOUD_COOPERATION",
44             DBCommon::StringMiddleMasking(tableName).c_str(), tableName.size());
45         return -E_NOT_SUPPORT;
46     }
47     TableSchema cloudTableSchema;
48     int ret = GetCloudTableSchema(tableName, cloudTableSchema);
49     if (ret != E_OK) {
50         LOGE("Cloud schema does not contain certain table [ %s size = %d ]:%d",
51             DBCommon::StringMiddleMasking(tableName).c_str(), tableName.size(), ret);
52         return -E_SCHEMA_MISMATCH;
53     }
54     std::map<int, FieldName> primaryKeys = tableInfo.GetPrimaryKey();
55     FieldInfoMap localFields = tableInfo.GetFields();
56     return CompareFieldSchema(primaryKeys, localFields, cloudTableSchema.fields);
57 }
58 
CompareFieldSchema(std::map<int,FieldName> & primaryKeys,FieldInfoMap & localFields,std::vector<Field> & cloudFields)59 int SchemaMgr::CompareFieldSchema(std::map<int, FieldName> &primaryKeys, FieldInfoMap &localFields,
60     std::vector<Field> &cloudFields)
61 {
62     std::unordered_set<std::string> cloudColNames;
63     for (const Field &cloudField : cloudFields) {
64         if (localFields.find(cloudField.colName) == localFields.end()) {
65             LOGE("Column name mismatch between local and cloud schema");
66             return -E_SCHEMA_MISMATCH;
67         }
68         if (IsAssetPrimaryField(cloudField)) {
69             LOGE("Asset type can not be primary field");
70             return -E_SCHEMA_MISMATCH;
71         }
72         FieldInfo &localField = localFields[cloudField.colName];
73         if (!CompareType(localField, cloudField)) {
74             LOGE("Type mismatch between local and cloud schema");
75             return -E_SCHEMA_MISMATCH;
76         }
77         if (!CompareNullable(localField, cloudField)) {
78             LOGE("The nullable property is mismatched between local and cloud schema");
79             return -E_SCHEMA_MISMATCH;
80         }
81         if (!ComparePrimaryField(primaryKeys, cloudField)) {
82             LOGE("The primary key property is mismatched between local and cloud schema");
83             return -E_SCHEMA_MISMATCH;
84         }
85         cloudColNames.emplace(cloudField.colName);
86     }
87     if (!primaryKeys.empty() && !(primaryKeys.size() == 1 && primaryKeys[0] == CloudDbConstant::ROW_ID_FIELD_NAME)) {
88         LOGE("Local schema contain extra primary key:%d", -E_SCHEMA_MISMATCH);
89         return -E_SCHEMA_MISMATCH;
90     }
91     for (const auto &[fieldName, fieldInfo] : localFields) {
92         if (!fieldInfo.HasDefaultValue() &&
93             fieldInfo.IsNotNull() &&
94             cloudColNames.find(fieldName) == cloudColNames.end()) {
95             LOGE("Column from local schema is not within cloud schema but doesn't have default value");
96             return -E_SCHEMA_MISMATCH;
97         }
98     }
99     return E_OK;
100 }
101 
IsAssetPrimaryField(const Field & cloudField)102 bool SchemaMgr::IsAssetPrimaryField(const Field &cloudField)
103 {
104     return cloudField.primary && (cloudField.type == TYPE_INDEX<Assets> || cloudField.type == TYPE_INDEX<Asset>);
105 }
106 
CompareType(const FieldInfo & localField,const Field & cloudField)107 bool SchemaMgr::CompareType(const FieldInfo &localField, const Field &cloudField)
108 {
109     StorageType localType = localField.GetStorageType();
110     switch (cloudField.type) {
111         case TYPE_INDEX<std::monostate>:
112         case TYPE_INDEX<bool>:
113             // BOOL type should be stored as NUMERIC type,
114             // but we regard it as NULL type for historic reason
115             return localType == StorageType::STORAGE_TYPE_NULL;
116         case TYPE_INDEX<int64_t>:
117             return localType == StorageType::STORAGE_TYPE_INTEGER;
118         case TYPE_INDEX<double>:
119             return localType == StorageType::STORAGE_TYPE_REAL;
120         case TYPE_INDEX<std::string>:
121             return localType == StorageType::STORAGE_TYPE_TEXT;
122         case TYPE_INDEX<Bytes>:
123         case TYPE_INDEX<Asset>:
124         case TYPE_INDEX<Assets>:
125             return localType == StorageType::STORAGE_TYPE_BLOB;
126         default:
127             return false;
128     }
129 }
130 
CompareNullable(const FieldInfo & localField,const Field & cloudField)131 bool SchemaMgr::CompareNullable(const FieldInfo &localField, const Field &cloudField)
132 {
133     return localField.IsNotNull() == !cloudField.nullable;
134 }
135 
ComparePrimaryField(std::map<int,FieldName> & localPrimaryKeys,const Field & cloudField)136 bool SchemaMgr::ComparePrimaryField(std::map<int, FieldName> &localPrimaryKeys, const Field &cloudField)
137 {
138     // whether the corresponding field in local schema is primary key
139     bool isLocalFieldPrimary = false;
140     for (const auto &kvPair : localPrimaryKeys) {
141         if (DBCommon::CaseInsensitiveCompare(kvPair.second, cloudField.colName)) {
142             isLocalFieldPrimary = true;
143             localPrimaryKeys.erase(kvPair.first);
144             break;
145         }
146     }
147     return isLocalFieldPrimary == cloudField.primary;
148 }
149 
SetCloudDbSchema(const DataBaseSchema & schema,RelationalSchemaObject & localSchema)150 void SchemaMgr::SetCloudDbSchema(const DataBaseSchema &schema, RelationalSchemaObject &localSchema)
151 {
152     DataBaseSchema cloudSchema = schema;
153     for (TableSchema &table : cloudSchema.tables) {
154         std::string tableName = table.name;
155         TableInfo tableInfo = localSchema.GetTable(tableName);
156         if (tableInfo.Empty()) {
157             LOGD("Local schema does not contain certain table [%s size = %d]",
158                 DBCommon::StringMiddleMasking(tableName).c_str(), tableName.size());
159             continue;
160         }
161         FieldInfoMap localFields = tableInfo.GetFields();
162 
163         // remove the fields that are not found in local schema from cloud schema
164         for (auto it = table.fields.begin(); it != table.fields.end();) {
165             if (localFields.find((*it).colName) == localFields.end()) {
166                 it = table.fields.erase(it);
167                 LOGI("Column name mismatch between local and cloud schema, colName: %s", (*it).colName.c_str());
168             } else {
169                 ++it;
170             }
171         }
172     }
173     SetCloudDbSchema(cloudSchema);
174 }
175 
SetCloudDbSchema(const DataBaseSchema & schema)176 void SchemaMgr::SetCloudDbSchema(const DataBaseSchema &schema)
177 {
178     DataBaseSchema cloudSchema = schema;
179     DataBaseSchema cloudSharedSchema;
180     for (const auto &tableSchema : cloudSchema.tables) {
181         if (tableSchema.name.empty() || tableSchema.sharedTableName.empty()) {
182             continue;
183         }
184         bool hasPrimaryKey = DBCommon::HasPrimaryKey(tableSchema.fields);
185         auto sharedTableFields = tableSchema.fields;
186         Field ownerField = { CloudDbConstant::CLOUD_OWNER, TYPE_INDEX<std::string>, hasPrimaryKey, true };
187         Field privilegeField = { CloudDbConstant::CLOUD_PRIVILEGE, TYPE_INDEX<std::string>, false, true };
188         sharedTableFields.push_back(ownerField);
189         sharedTableFields.push_back(privilegeField);
190         TableSchema sharedTableSchema = { tableSchema.sharedTableName, tableSchema.sharedTableName, sharedTableFields };
191         cloudSharedSchema.tables.push_back(sharedTableSchema);
192     }
193     for (const auto &sharedTableSchema : cloudSharedSchema.tables) {
194         cloudSchema.tables.push_back(sharedTableSchema);
195     }
196     cloudSchema_ = std::make_shared<DataBaseSchema>(cloudSchema);
197 }
198 
GetCloudDbSchema()199 std::shared_ptr<DataBaseSchema> SchemaMgr::GetCloudDbSchema()
200 {
201     return cloudSchema_;
202 }
203 
GetCloudTableSchema(const TableName & tableName,TableSchema & retSchema)204 int SchemaMgr::GetCloudTableSchema(const TableName &tableName, TableSchema &retSchema)
205 {
206     if (cloudSchema_ == nullptr) {
207         return -E_SCHEMA_MISMATCH;
208     }
209     for (const TableSchema &tableSchema : cloudSchema_->tables) {
210         if (DBCommon::CaseInsensitiveCompare(tableSchema.name, tableName)) {
211             retSchema = tableSchema;
212             return E_OK;
213         }
214     }
215     return -E_NOT_FOUND;
216 }
217 
IsSharedTable(const std::string & tableName)218 bool SchemaMgr::IsSharedTable(const std::string &tableName)
219 {
220     if (cloudSchema_ == nullptr) {
221         return false;
222     }
223     if (sharedTableMap_.find(tableName) != sharedTableMap_.end()) {
224         return sharedTableMap_[tableName];
225     }
226     for (const auto &tableSchema : (*cloudSchema_).tables) {
227         if (DBCommon::CaseInsensitiveCompare(tableName, tableSchema.sharedTableName)) {
228             sharedTableMap_[tableName] = true;
229             return true;
230         }
231     }
232     sharedTableMap_[tableName] = false;
233     return false;
234 }
235 
GetSharedTableOriginNames()236 std::map<std::string, std::string> SchemaMgr::GetSharedTableOriginNames()
237 {
238     if (cloudSchema_ == nullptr) {
239         LOGW("[SchemaMgr] Not found cloud schema!");
240         return {};
241     }
242     std::map<std::string, std::string> res;
243     for (const auto &item : cloudSchema_->tables) {
244         if (item.sharedTableName.empty() || CloudStorageUtils::IsSharedTable(item)) {
245             continue;
246         }
247         res[item.name] = item.sharedTableName;
248     }
249     return res;
250 }
251 } // namespace DistributedDB