1 /*
2  * Copyright (c) 2021 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 #ifndef OMIT_JSON
17 #include <gtest/gtest.h>
18 #include "distributeddb_tools_unit_test.h"
19 #include "kv_store_delegate_manager.h"
20 #include "schema_constant.h"
21 #include "schema_object.h"
22 #include "schema_utils.h"
23 
24 using namespace testing::ext;
25 using namespace DistributedDB;
26 using namespace DistributedDBUnitTest;
27 
28 namespace {
29     const std::string APP_ID = "SCHEMA";
30     const std::string USER_ID = "UPGRADE";
31     std::string g_testDir;
32     KvStoreDelegateManager g_manager(APP_ID, USER_ID);
33 
34     DBStatus g_kvDelegateStatus = INVALID_ARGS;
35     KvStoreNbDelegate *g_kvDelegatePtr = nullptr;
36     auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback,
37         std::placeholders::_1, std::placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr));
38 
39     std::string g_baseSchema;
40     DBStatus g_expectError = SCHEMA_VIOLATE_VALUE;
41 
StringEraser(const std::string & oriString,const std::string & toErase)42     std::string StringEraser(const std::string &oriString, const std::string &toErase)
43     {
44         std::string resStr = oriString;
45         auto iter = std::search(resStr.begin(), resStr.end(), toErase.begin(), toErase.end());
46         resStr.erase(iter, iter + toErase.size());
47         return resStr;
48     }
StringReplacer(const std::string & oriString,const std::string & toErase,const std::string & toRepalce)49     std::string StringReplacer(const std::string &oriString, const std::string &toErase, const std::string &toRepalce)
50     {
51         std::string resStr = oriString;
52         auto iter = std::search(resStr.begin(), resStr.end(), toErase.begin(), toErase.end());
53         resStr.replace(iter, iter + toErase.size(), toRepalce);
54         return resStr;
55     }
56 
57     const std::string SCHEMA_INC_FIELD = "{\"SCHEMA_VERSION\":\"1.0\","
58         "\"SCHEMA_MODE\":\"COMPATIBLE\","
59         "\"SCHEMA_DEFINE\":{"
60             "\"field_1\":\"LONG, NOT NULL, DEFAULT 100\","
61             "\"field_2\":{"
62                 "\"field_3\":\"STRING, DEFAULT 'OpenHarmony'\","
63                 "\"field_4\":\"INTEGER\""
64             "}"
65         "},"
66         "\"SCHEMA_INDEXES\":[\"field_1\", [\"field_2.field_3\", \"field_2.field_4\"]]}";
67     const std::string SCHEMA_BASE = StringReplacer(StringEraser(SCHEMA_INC_FIELD, ",\"field_4\":\"INTEGER\""),
68         "[\"field_2.field_3\", \"field_2.field_4\"]", "\"field_2.field_3\"");
69     const std::string SCHEMA_INC_FIELD_NOTNULL = StringReplacer(SCHEMA_INC_FIELD, "\"INTEGER\"",
70         "\"INTEGER, NOT NULL\"");
71     const std::string SCHEMA_INC_FIELD_DEFAULT = StringReplacer(SCHEMA_INC_FIELD, "\"INTEGER\"",
72         "\"INTEGER, DEFAULT 88\"");
73     const std::string SCHEMA_INC_FIELD_NOTNULL_DEFAULT = StringReplacer(SCHEMA_INC_FIELD, "\"INTEGER\"",
74         "\"INTEGER, NOT NULL, DEFAULT 88\"");
75 
76     const std::string VALUE_BASE_LACK = "{\"field_1\":LONG_VAL}";
77     const std::string VALUE_BASE = "{\"field_1\":LONG_VAL,\"field_2\":{\"field_3\":STR_VAL}}";
78     const std::string VALUE_FIELD_FULL = "{\"field_1\":LONG_VAL,\"field_2\":{\"field_3\":STR_VAL,\"field_4\":INT_VAL}}";
79 
SchemaSwitchMode(const std::string & oriSchemaStr)80     std::string SchemaSwitchMode(const std::string &oriSchemaStr)
81     {
82         std::string resStr = oriSchemaStr;
83         auto iterStrict = std::search(resStr.begin(), resStr.end(), SchemaConstant::KEYWORD_MODE_STRICT.begin(),
84             SchemaConstant::KEYWORD_MODE_STRICT.end());
85         auto iterCompatible = std::search(resStr.begin(), resStr.end(), SchemaConstant::KEYWORD_MODE_COMPATIBLE.begin(),
86             SchemaConstant::KEYWORD_MODE_COMPATIBLE.end());
87         if (iterStrict != resStr.end()) {
88             resStr.replace(iterStrict, iterStrict + SchemaConstant::KEYWORD_MODE_STRICT.size(),
89                 SchemaConstant::KEYWORD_MODE_COMPATIBLE.begin(), SchemaConstant::KEYWORD_MODE_COMPATIBLE.end());
90             return resStr;
91         }
92         if (iterCompatible != resStr.end()) {
93             resStr.replace(iterCompatible, iterCompatible + SchemaConstant::KEYWORD_MODE_COMPATIBLE.size(),
94                 SchemaConstant::KEYWORD_MODE_STRICT.begin(), SchemaConstant::KEYWORD_MODE_STRICT.end());
95             return resStr;
96         }
97         return oriSchemaStr;
98     }
SchemaChecker(const std::string & schema)99     bool SchemaChecker(const std::string &schema)
100     {
101         SchemaObject schemaObj;
102         return (schemaObj.ParseFromSchemaString(schema) == E_OK);
103     }
ToVec(const std::string & inStr)104     std::vector<uint8_t> ToVec(const std::string &inStr)
105     {
106         std::vector<uint8_t> outVec(inStr.begin(), inStr.end());
107         return outVec;
108     }
ToStr(const std::vector<uint8_t> & inVec)109     std::string ToStr(const std::vector<uint8_t> &inVec)
110     {
111         std::string outStr(inVec.begin(), inVec.end());
112         return outStr;
113     }
114 
115     const std::map<std::string, std::vector<uint8_t>> VALUE_MAP {
116         {"LACK", ToVec(StringReplacer(VALUE_BASE_LACK, "LONG_VAL", "1"))},
117         {"BASE", ToVec(StringReplacer(StringReplacer(VALUE_BASE, "LONG_VAL", "2"), "STR_VAL", "\"OS\""))},
118         {"FULL", ToVec(StringReplacer(StringReplacer(StringReplacer(VALUE_FIELD_FULL, "LONG_VAL", "3"), "STR_VAL",
119             "\"TEST\""), "INT_VAL", "33"))},
120         {"BASE_WRONG_TYPE", ToVec(StringReplacer(StringReplacer(VALUE_BASE, "LONG_VAL", "2"), "STR_VAL", "10086"))},
121         {"FULL_NULL", ToVec(StringReplacer(StringReplacer(StringReplacer(VALUE_FIELD_FULL, "LONG_VAL", "3"),
122             "STR_VAL", "\"TEST\""), "INT_VAL", "null"))},
123         {"FULL_WRONG_TYPE", ToVec(StringReplacer(StringReplacer(StringReplacer(VALUE_FIELD_FULL, "LONG_VAL", "3"),
124             "STR_VAL", "\"TEST\""), "INT_VAL", "\"UT\""))},
125     };
126 
127     // Key begin from "KEY_1"
InsertPresetEntry(KvStoreNbDelegate & delegate,const std::vector<std::string> & selection)128     void InsertPresetEntry(KvStoreNbDelegate &delegate, const std::vector<std::string> &selection)
129     {
130         int count = 0;
131         for (const auto &eachSel : selection) {
132             ASSERT_NE(VALUE_MAP.count(eachSel), 0ul);
133             DBStatus ret = delegate.Put(ToVec(std::string("KEY_") + std::to_string(++count)), VALUE_MAP.at(eachSel));
134             ASSERT_EQ(ret, OK);
135         }
136     }
137 }
138 
139 class DistributedDBInterfacesSchemaDatabaseUpgradeTest : public testing::Test {
140 public:
141     static void SetUpTestCase(void);
142     static void TearDownTestCase(void);
143     void SetUp();
144     void TearDown();
145 };
146 
SetUpTestCase(void)147 void DistributedDBInterfacesSchemaDatabaseUpgradeTest::SetUpTestCase(void)
148 {
149     DistributedDBToolsUnitTest::TestDirInit(g_testDir);
150     KvStoreConfig config{g_testDir};
151     g_manager.SetKvStoreConfig(config);
152     ASSERT_EQ(SchemaChecker(SCHEMA_BASE), true);
153     ASSERT_EQ(SchemaChecker(SCHEMA_INC_FIELD), true);
154     ASSERT_EQ(SchemaChecker(SCHEMA_INC_FIELD_NOTNULL), true);
155     ASSERT_EQ(SchemaChecker(SCHEMA_INC_FIELD_DEFAULT), true);
156     ASSERT_EQ(SchemaChecker(SCHEMA_INC_FIELD_NOTNULL_DEFAULT), true);
157 }
158 
TearDownTestCase(void)159 void DistributedDBInterfacesSchemaDatabaseUpgradeTest::TearDownTestCase(void)
160 {
161     if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) {
162         LOGE("[TestSchemaUpgrade] Remove test directory error.");
163     }
164 }
165 
SetUp(void)166 void DistributedDBInterfacesSchemaDatabaseUpgradeTest::SetUp(void)
167 {
168     DistributedDBToolsUnitTest::PrintTestCaseInfo();
169     g_kvDelegateStatus = INVALID_ARGS;
170     g_kvDelegatePtr = nullptr;
171 }
172 
TearDown(void)173 void DistributedDBInterfacesSchemaDatabaseUpgradeTest::TearDown(void)
174 {
175     if (g_kvDelegatePtr != nullptr) {
176         ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK);
177         g_kvDelegatePtr = nullptr;
178     }
179 }
180 
181 /**
182   * @tc.name: UpgradeFromKv001
183   * @tc.desc: Schema database upgrade from kv database, exist value match compatible schema(mismatch strict schema)
184   * @tc.type: FUNC
185   * @tc.require: AR000F3OPD
186   * @tc.author: xiaozhenjian
187   */
188 HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromKv001, TestSize.Level1)
189 {
190     /**
191      * @tc.steps: step1. Prepare kv database with value match compatible schema(mismatch strict schema) then close
192      * @tc.expected: step1. E_OK
193      */
194     std::string storeId = "UpgradeFromKv001";
195     KvStoreNbDelegate::Option option;
196     g_manager.GetKvStore(storeId, option, g_kvDelegateCallback);
197     ASSERT_TRUE(g_kvDelegatePtr != nullptr);
198     ASSERT_EQ(g_kvDelegateStatus, OK);
199 
200     InsertPresetEntry(*g_kvDelegatePtr, std::vector<std::string>{"LACK", "BASE", "LACK", "BASE", "FULL"});
201     DBStatus ret = g_kvDelegatePtr->Delete(ToVec("KEY_4"));
202     ASSERT_EQ(ret, OK);
203     ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK);
204     g_kvDelegatePtr = nullptr;
205 
206     /**
207      * @tc.steps: step2. Upgrade to schema(strict) database
208      * @tc.expected: step2. SCHEMA_VIOLATE_VALUE
209      */
210     option.schema = SchemaSwitchMode(SCHEMA_BASE);
211     g_manager.GetKvStore(storeId, option, g_kvDelegateCallback);
212     ASSERT_TRUE(g_kvDelegatePtr == nullptr);
213     ASSERT_EQ(g_kvDelegateStatus, SCHEMA_VIOLATE_VALUE);
214 
215     /**
216      * @tc.steps: step3. Upgrade to schema(compatible) database
217      * @tc.expected: step3. E_OK
218      */
219     option.schema = SCHEMA_BASE;
220     g_manager.GetKvStore(storeId, option, g_kvDelegateCallback);
221     ASSERT_TRUE(g_kvDelegatePtr != nullptr);
222     ASSERT_EQ(g_kvDelegateStatus, OK);
223 
224     /**
225      * @tc.steps: step4. Query field_2.field_3 equal OpenHarmony
226      * @tc.expected: step4. E_OK, KEY_1
227      */
228     Query query = Query::Select().EqualTo("field_2.field_3", "OpenHarmony");
229     std::vector<Entry> entries;
230     EXPECT_EQ(g_kvDelegatePtr->GetEntries(query, entries), OK);
231     ASSERT_EQ(entries.size(), 2ul);
232     EXPECT_EQ(ToStr(entries[0].key), std::string("KEY_1"));
233     EXPECT_EQ(ToStr(entries[1].key), std::string("KEY_3"));
234     std::string valStr = ToStr(entries[0].value);
235     std::string defaultVal = "OpenHarmony";
236     auto iter = std::search(valStr.begin(), valStr.end(), defaultVal.begin(), defaultVal.end());
237     EXPECT_TRUE(iter != valStr.end());
238 }
239 
240 /**
241   * @tc.name: UpgradeFromKv002
242   * @tc.desc: Schema database upgrade from kv database, exist value mismatch compatible schema
243   * @tc.type: FUNC
244   * @tc.require: AR000F3OPD
245   * @tc.author: xiaozhenjian
246   */
247 HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromKv002, TestSize.Level1)
248 {
249     /**
250      * @tc.steps: step1. Prepare kv database with value mismatch compatible schema then close
251      * @tc.expected: step1. E_OK
252      */
253     std::string storeId = "UpgradeFromKv002";
254     KvStoreNbDelegate::Option option;
255     g_manager.GetKvStore(storeId, option, g_kvDelegateCallback);
256     ASSERT_TRUE(g_kvDelegatePtr != nullptr);
257     ASSERT_EQ(g_kvDelegateStatus, OK);
258 
259     InsertPresetEntry(*g_kvDelegatePtr, std::vector<std::string>{"BASE_WRONG_TYPE"});
260     ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK);
261     g_kvDelegatePtr = nullptr;
262 
263     /**
264      * @tc.steps: step2. Upgrade to schema(compatible) database
265      * @tc.expected: step2. SCHEMA_VIOLATE_VALUE
266      */
267     option.schema = SCHEMA_BASE;
268     g_manager.GetKvStore(storeId, option, g_kvDelegateCallback);
269     ASSERT_TRUE(g_kvDelegatePtr == nullptr);
270     ASSERT_EQ(g_kvDelegateStatus, SCHEMA_VIOLATE_VALUE);
271 }
272 
273 namespace {
TestUpgradeFromSchema(const std::string & storeId,const std::vector<std::string> & selection,const std::string & newSchema,bool expectMatch,uint32_t expectCount)274 void TestUpgradeFromSchema(const std::string &storeId, const std::vector<std::string> &selection,
275     const std::string &newSchema, bool expectMatch, uint32_t expectCount)
276 {
277     LOGI("[TestUpgradeFromSchema] StoreId=%s", storeId.c_str());
278     /**
279      * @tc.steps: step1. Prepare kv database with value then close
280      * @tc.expected: step1. E_OK
281      */
282     KvStoreNbDelegate::Option option;
283     option.schema = g_baseSchema;
284     g_manager.GetKvStore(storeId, option, g_kvDelegateCallback);
285     ASSERT_TRUE(g_kvDelegatePtr != nullptr);
286     ASSERT_EQ(g_kvDelegateStatus, OK);
287 
288     InsertPresetEntry(*g_kvDelegatePtr, selection);
289     ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK);
290     g_kvDelegatePtr = nullptr;
291 
292     /**
293      * @tc.steps: step2. Upgrade to schema database
294      * @tc.expected: step2. OK or SCHEMA_VIOLATE_VALUE
295      */
296     option.schema = newSchema;
297     g_manager.GetKvStore(storeId, option, g_kvDelegateCallback);
298     if (expectMatch) {
299         ASSERT_TRUE(g_kvDelegatePtr != nullptr);
300         ASSERT_EQ(g_kvDelegateStatus, OK);
301     } else {
302         ASSERT_TRUE(g_kvDelegatePtr == nullptr);
303         ASSERT_EQ(g_kvDelegateStatus, g_expectError);
304     }
305 
306     /**
307      * @tc.steps: step3. Query field_2.field_3
308      * @tc.expected: step3. E_OK
309      */
310     if (expectMatch) {
311         Query query = Query::Select().EqualTo("field_2.field_4", 88); // 88 is the default value in the testcase.
312         std::vector<Entry> entries;
313         if (expectCount == 0) {
314             EXPECT_EQ(g_kvDelegatePtr->GetEntries(query, entries), NOT_FOUND);
315         } else {
316             ASSERT_EQ(g_kvDelegatePtr->GetEntries(query, entries), OK);
317             EXPECT_EQ(entries.size(), expectCount);
318         }
319         ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK);
320         g_kvDelegatePtr = nullptr;
321     }
322 }
323 }
324 
325 /**
326   * @tc.name: UpgradeFromSchema001
327   * @tc.desc: Schema database upgrade from kv database, exist value match new schema
328   * @tc.type: FUNC
329   * @tc.require: AR000F3OPD
330   * @tc.author: xiaozhenjian
331   */
332 HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromSchema001, TestSize.Level1)
333 {
334     g_baseSchema = SCHEMA_BASE;
335     g_expectError = SCHEMA_VIOLATE_VALUE;
336     TestUpgradeFromSchema("UpgradeFromSchema001_1", std::vector<std::string>{"LACK", "BASE", "FULL", "FULL_NULL"},
337         SCHEMA_INC_FIELD, true, 0);
338     TestUpgradeFromSchema("UpgradeFromSchema001_2", std::vector<std::string>{"LACK", "BASE", "FULL", "FULL_NULL"},
339         SCHEMA_INC_FIELD_DEFAULT, true, 2);
340     TestUpgradeFromSchema("UpgradeFromSchema001_3", std::vector<std::string>{"LACK", "BASE", "FULL"},
341         SCHEMA_INC_FIELD_NOTNULL_DEFAULT, true, 2);
342 }
343 
344 /**
345   * @tc.name: UpgradeFromSchema002
346   * @tc.desc: Schema database upgrade from kv database, exist value mismatch new schema
347   * @tc.type: FUNC
348   * @tc.require: AR000F3OPD
349   * @tc.author: xiaozhenjian
350   */
351 HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromSchema002, TestSize.Level1)
352 {
353     g_baseSchema = SCHEMA_BASE;
354     g_expectError = SCHEMA_VIOLATE_VALUE;
355     TestUpgradeFromSchema("UpgradeFromSchema002_1", std::vector<std::string>{"LACK", "BASE", "FULL", "FULL_WRONG_TYPE"},
356         SCHEMA_INC_FIELD, false, 0);
357     TestUpgradeFromSchema("UpgradeFromSchema002_2", std::vector<std::string>{"LACK", "BASE", "FULL", "FULL_WRONG_TYPE"},
358         SCHEMA_INC_FIELD_DEFAULT, false, 0);
359     TestUpgradeFromSchema("UpgradeFromSchema002_3", std::vector<std::string>{"LACK", "BASE", "FULL", "FULL_NULL"},
360         SCHEMA_INC_FIELD_NOTNULL_DEFAULT, false, 0);
361 }
362 
363 /**
364   * @tc.name: UpgradeFromSchema003
365   * @tc.desc: Schema database upgrade from kv database, new schema incompatible with old schema
366   * @tc.type: FUNC
367   * @tc.require: AR000F3OPD
368   * @tc.author: xiaozhenjian
369   */
370 HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromSchema003, TestSize.Level1)
371 {
372     // Compatible schema can increase field, but must not be null without default.
373     g_baseSchema = SCHEMA_BASE;
374     g_expectError = SCHEMA_MISMATCH;
375     TestUpgradeFromSchema("UpgradeFromSchema003_1", std::vector<std::string>{"LACK", "BASE", "FULL", "FULL_NULL"},
376         SCHEMA_INC_FIELD_NOTNULL, false, 0);
377     // Strict schema can not incrase field
378     g_baseSchema = SchemaSwitchMode(SCHEMA_BASE);
379     TestUpgradeFromSchema("UpgradeFromSchema003_2", std::vector<std::string>{"LACK", "BASE"},
380         SchemaSwitchMode(SCHEMA_INC_FIELD), false, 0);
381 }
382 #endif