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 
19 #include "db_common.h"
20 #include "db_constant.h"
21 #include "distributeddb_data_generate_unit_test.h"
22 #include "distributeddb_tools_unit_test.h"
23 #include "log_print.h"
24 #include "platform_specific.h"
25 #include "sqlite_import.h"
26 #include "store_types.h"
27 
28 using namespace testing::ext;
29 using namespace DistributedDB;
30 using namespace DistributedDBUnitTest;
31 
32 namespace {
33     std::string g_testDir;
34     std::string g_identifier;
35 
36     KvStoreDelegateManager g_mgr(APP_ID, USER_ID);
37     DBStatus g_kvNbDelegateStatus = INVALID_ARGS;
38     KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr;
39     auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback,
40         std::placeholders::_1, std::placeholders::_2, std::ref(g_kvNbDelegateStatus), std::ref(g_kvNbDelegatePtr));
41 
42     const std::string BASE_SCHEMA_STRING = "{\"SCHEMA_VERSION\" : \"1.0\","
43         "\"SCHEMA_MODE\" : \"COMPATIBLE\","
44         "\"SCHEMA_DEFINE\" : {"
45            "\"name\" : \"STRING\","
46            "\"id\" : \"INTEGER\","
47            "\"father\" :  {"
48                    "\"name\" : \"STRING\","
49                    "\"id\" : \"INTEGER\""
50                "},"
51            "\"phone\" : \"INTEGER\""
52        "},"
53        "\"SCHEMA_INDEXES\" : ";
54 
55     const std::string JSON_VALUE ="{\"name\":\"Tom\","
56         "\"id\":10,"
57         "\"father\":{\"name\":\"Jim\", \"id\":20},"
58         "\"phone\":20}";
59 
GenerateSchemaString(std::string & schema,const std::string & indexString)60     void GenerateSchemaString(std::string &schema, const std::string &indexString)
61     {
62         schema = BASE_SCHEMA_STRING + indexString + "}";
63     }
64 
GetKvStoreDirectory(const std::string & userId,const std::string & appId,const std::string & storeId)65     std::string GetKvStoreDirectory(const std::string &userId, const std::string &appId, const std::string &storeId)
66     {
67         string identifier = DBCommon::GenerateIdentifierId(storeId, appId, userId);
68         string hashIdentifierName = DBCommon::TransferHashString(identifier);
69         string identifierName = DBCommon::TransferStringToHex(hashIdentifierName);
70         string filePath = g_testDir + "/" + identifierName + "/" + DBConstant::SINGLE_SUB_DIR + "/main/";
71         filePath += DBConstant::SINGLE_VER_DATA_STORE + DBConstant::DB_EXTENSION;
72         return filePath;
73     }
74 
CheckIndexFromDbFile(const::std::string & filePath,const std::string & indexName)75     bool CheckIndexFromDbFile(const::std::string &filePath, const std::string &indexName)
76     {
77         sqlite3 *db = nullptr;
78         if (sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE, nullptr) != SQLITE_OK) {
79             LOGD("DB open failed %s", filePath.c_str());
80             if (db != nullptr) {
81                 (void)sqlite3_close_v2(db);
82             }
83             return false;
84         }
85 
86         std::string querySQL = "select sql from sqlite_master where name = '" + indexName + "'";
87         int errCode = sqlite3_exec(db, querySQL.c_str(), nullptr, nullptr, nullptr);
88         (void)sqlite3_close_v2(db);
89         if (errCode == SQLITE_OK) {
90             return true;
91         }
92         return false;
93     }
94 }
95 
96 class DistributedDBStorageIndexOptimizeTest : public testing::Test {
97 public:
98     static void SetUpTestCase(void);
99     static void TearDownTestCase(void);
100     void SetUp();
101     void TearDown();
102 };
103 
SetUpTestCase(void)104 void DistributedDBStorageIndexOptimizeTest::SetUpTestCase(void)
105 {
106     DistributedDBToolsUnitTest::TestDirInit(g_testDir);
107     std::string origIdentifier = USER_ID + "-" + APP_ID + "-" + STORE_ID_1;
108     std::string identifier = DBCommon::TransferHashString(origIdentifier);
109     g_identifier = DBCommon::TransferStringToHex(identifier);
110     std::string dir = g_testDir + g_identifier + "/" + DBConstant::SINGLE_SUB_DIR;
111     DIR *dirTmp = opendir(dir.c_str());
112     if (dirTmp == nullptr) {
113         OS::MakeDBDirectory(dir);
114     } else {
115         closedir(dirTmp);
116     }
117 
118     KvStoreConfig config;
119     config.dataDir = g_testDir;
120     g_mgr.SetKvStoreConfig(config);
121 }
122 
TearDownTestCase(void)123 void DistributedDBStorageIndexOptimizeTest::TearDownTestCase(void)
124 {
125     if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) {
126         LOGE("rm test db files error!");
127     }
128 }
129 
SetUp(void)130 void DistributedDBStorageIndexOptimizeTest::SetUp(void)
131 {
132     DistributedDBToolsUnitTest::PrintTestCaseInfo();
133 }
134 
TearDown(void)135 void DistributedDBStorageIndexOptimizeTest::TearDown(void)
136 {
137 }
138 
139 /**
140   * @tc.name: ParseAndCheckUnionIndex001
141   * @tc.desc: Test the Json union index parse and check function Open function
142   * @tc.type: FUNC
143   * @tc.require: AR000F3OPD
144   * @tc.author: xushaohua
145   */
146 HWTEST_F(DistributedDBStorageIndexOptimizeTest, ParseAndCheckUnionIndex001, TestSize.Level1)
147 {
148     /**
149      * @tc.steps: step1. Create a correct shema string include a correct union index.
150      */
151     std::string schema1;
152     GenerateSchemaString(schema1, "[[\"name\", \"father.name\", \"father.id\", \"id\", \"phone\"]]");
153 
154     /**
155      * @tc.steps: step2. Call SchemaObject.ParseFromSchemaString to parse the string.
156      * @tc.expected: step2. Expect return E_OK.
157      */
158     SchemaObject so1;
159     EXPECT_EQ(so1.ParseFromSchemaString(schema1), E_OK);
160 
161     /**
162      * @tc.steps: step3. Create a correct shema string include a single index and a union index
163      */
164     std::string schema2;
165     GenerateSchemaString(schema2, "[[\"name\", \"father.name\", \"father.id\", \"id\", \"phone\"], \"id\"]");
166 
167     /**
168      * @tc.steps: step4. Call SchemaObject.ParseFromSchemaString to parse the string.
169      * @tc.expected: step4. Expect return E_OK.
170      */
171     SchemaObject so2;
172     EXPECT_EQ(so2.ParseFromSchemaString(schema2), E_OK);
173 
174     /**
175      * @tc.steps: step5. Create a shema string include a single index and a union index, and the two index has
176                          the same sort column.
177      */
178     std::string schema3;
179     GenerateSchemaString(schema3, "[[\"name\", \"father.name\", \"father.id\", \"id\", \"phone\"], \"name\"]");
180 
181     /**
182      * @tc.steps: step6. Call SchemaObject.ParseFromSchemaString to parse the string.
183      * @tc.expected: step6. Expect return E_SCHEMA_PARSE_FAIL.
184      */
185     SchemaObject so3;
186     EXPECT_EQ(so3.ParseFromSchemaString(schema3), -E_SCHEMA_PARSE_FAIL);
187 
188     /**
189      * @tc.steps: step7. Create a shema string include a single index with a not exist column
190      */
191     std::string schema4;
192     GenerateSchemaString(schema4, "[[\"name\", \"father.name\", \"father.id\", \"id\", \"tel\"]]");
193 
194     /**
195      * @tc.steps: step8. Call SchemaObject.ParseFromSchemaString to parse the string.
196      * @tc.expected: step8. Expect return -E_SCHEMA_PARSE_FAIL.
197      */
198     SchemaObject so4;
199     EXPECT_EQ(so4.ParseFromSchemaString(schema4), -E_SCHEMA_PARSE_FAIL);
200 
201     /**
202      * @tc.steps: step9. Create a shema string include a single index with all columns not exists
203      */
204     std::string schema5;
205     GenerateSchemaString(schema5, "[[\"name1\", \"father.name2\", \"father1.id\", \"id2\", \"tel\"]]");
206 
207     /**
208      * @tc.steps: step10. Call SchemaObject.ParseFromSchemaString to parse the string.
209      * @tc.expected: step10. Expect return -E_SCHEMA_PARSE_FAIL.
210      */
211     SchemaObject so5;
212     EXPECT_EQ(so5.ParseFromSchemaString(schema5), -E_SCHEMA_PARSE_FAIL);
213 }
214 
215 /**
216   * @tc.name: UnionIndexCreatTest001
217   * @tc.desc: Test the Json uoin index create function
218   * @tc.type: FUNC
219   * @tc.require: AR000F3OPD
220   * @tc.author: xushaohua
221   */
222 HWTEST_F(DistributedDBStorageIndexOptimizeTest, UnionIndexCreatTest001, TestSize.Level1)
223 {
224     /**
225      * @tc.steps: step1. Create a correct shema string include a correct union index.
226      */
227     std::string schema;
228     GenerateSchemaString(schema, "[[\"name\", \"father.name\"]]");
229 
230     /**
231      * @tc.steps: step2. Create a kvStore with the schema string.
232      */
233     KvStoreNbDelegate::Option option;
234     option.schema = schema;
235     g_mgr.GetKvStore(STORE_ID_1, option, g_kvNbDelegateCallback);
236     ASSERT_TRUE(g_kvNbDelegatePtr != nullptr);
237     g_mgr.CloseKvStore(g_kvNbDelegatePtr);
238     g_kvNbDelegatePtr = nullptr;
239     EXPECT_TRUE(CheckIndexFromDbFile(GetKvStoreDirectory(USER_ID, APP_ID, STORE_ID_1), "$.name"));
240 }
241 
242 /**
243   * @tc.name: SuggestIndexTest001
244   * @tc.desc: Test the Suggest index verify function
245   * @tc.type: FUNC
246   * @tc.require: AR000F3OPE
247   * @tc.author: xushaohua
248   */
249 HWTEST_F(DistributedDBStorageIndexOptimizeTest, SuggestIndexTest001, TestSize.Level1)
250 {
251     std::string schema;
252     GenerateSchemaString(schema, "[\"name\", \"id\"]");
253     SchemaObject schemaObject1;
254     ASSERT_EQ(schemaObject1.ParseFromSchemaString(schema), E_OK);
255 
256     /**
257      * @tc.steps: step1. Create a Query and call GreaterThan().SuggestIndex(), then check the query1
258      * @tc.expected: step1. query1 is valid.
259      */
260     Query query1 = Query::Select().GreaterThan("id", 1).SuggestIndex("id");
261     QueryObject queryObject1(query1);
262     queryObject1.SetSchema(schemaObject1);
263     queryObject1.Init();
264     EXPECT_TRUE(queryObject1.IsValid());
265 
266     /**
267      * @tc.steps: step2. Create a Query and call SuggestIndex().SuggestIndex(), then check the query2
268      * @tc.expected: step2. query1 is invalid.
269      */
270     SchemaObject schemaObject2;
271     ASSERT_EQ(schemaObject2.ParseFromSchemaString(schema), E_OK);
272     Query query2 = Query::Select().SuggestIndex("id").SuggestIndex("id");
273     QueryObject queryObject2(query2);
274     queryObject2.SetSchema(schemaObject2);
275     queryObject2.Init();
276     EXPECT_FALSE(queryObject2.IsValid());
277 
278     /**
279      * @tc.steps: step3. Create a Query and call SuggestIndex().GreaterThan(), then check the query3
280      * @tc.expected: step4. query3 is invalid.
281      */
282     SchemaObject schemaObject3;
283     ASSERT_EQ(schemaObject3.ParseFromSchemaString(schema), E_OK);
284     Query query3 = Query::Select().SuggestIndex("id").GreaterThan("id", 1);
285     QueryObject queryObject3(query3);
286     queryObject3.SetSchema(schemaObject3);
287     queryObject3.Init();
288     EXPECT_FALSE(queryObject3.IsValid());
289 }
290 
291 /**
292   * @tc.name: SuggestIndexTest002
293   * @tc.desc: Test the Query parse sql the SuggestIndex
294   * @tc.type: FUNC
295   * @tc.require: AR000F3OPE
296   * @tc.author: xushaohua
297   */
298 HWTEST_F(DistributedDBStorageIndexOptimizeTest, SuggestIndexTest002, TestSize.Level1)
299 {
300     /**
301      * @tc.steps: step1. Create a schema include index name, id, phone.
302      */
303     std::string schema;
304     GenerateSchemaString(schema, "[\"name\", \"id\", \"phone\"]");
305 
306     /**
307      * @tc.steps: step2. Create Query,call GreaterThan("id").GreaterThan("phone").SuggestIndex("id")
308      */
309     SchemaObject schemaObject1;
310     ASSERT_EQ(schemaObject1.ParseFromSchemaString(schema), E_OK);
311     Query query1 = Query::Select().GreaterThan("id", 1).And().GreaterThan("phone", 1).SuggestIndex("id");
312 
313     /**
314      * @tc.steps: step3. Create QueryObject with query1 and call GetQuerySql to check the sql
315      * @tc.expected: step3. the sql contains "INDEXED BY $.id".
316      */
317     QueryObject queryObject1(query1);
318     queryObject1.SetSchema(schemaObject1);
319     int errCode = E_OK;
320     SqliteQueryHelper helper = queryObject1.GetQueryHelper(errCode);
321     ASSERT_EQ(errCode, E_OK);
322     std::string sql1;
323     ASSERT_EQ(helper.GetQuerySql(sql1, false), E_OK);
324     size_t pos = sql1.find("INDEXED BY '$.id'", 0);
325     ASSERT_TRUE(pos != std::string::npos);
326 
327     SqliteQueryHelper helper1 = queryObject1.GetQueryHelper(errCode);
328     ASSERT_EQ(errCode, E_OK);
329     /**
330      * @tc.steps: step4. Create a kvStore with the schema string.
331      */
332     KvStoreNbDelegate::Option option;
333     option.schema = schema;
334     g_mgr.GetKvStore(STORE_ID_1, option, g_kvNbDelegateCallback);
335     ASSERT_TRUE(g_kvNbDelegatePtr != nullptr);
336 
337     /**
338      * @tc.steps: step5. put a valid value
339      */
340     Value value(JSON_VALUE.begin(), JSON_VALUE.end());
341     ASSERT_EQ(g_kvNbDelegatePtr->Put(KEY_1, value), OK);
342     std::vector<Entry> entries;
343 
344     /**
345      * @tc.steps: step6.  GetEntries with the query1
346      * @tc.expected: step6. GetEntries return OK, and the out value is the given value.
347      */
348     ASSERT_EQ(errCode, E_OK);
349     ASSERT_EQ(g_kvNbDelegatePtr->GetEntries(query1, entries), OK);
350     EXPECT_TRUE(value == entries[0].value);
351     ASSERT_EQ(errCode, E_OK);
352     EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK);
353     g_kvNbDelegatePtr = nullptr;
354     SqliteQueryHelper helper2 = queryObject1.GetQueryHelper(errCode);
355     ASSERT_EQ(errCode, E_OK);
356     /**
357      * @tc.steps: step7. Create Query,call GreaterThan("id").SuggestIndex("car")
358      */
359     SchemaObject schemaObject3;
360     ASSERT_EQ(schemaObject3.ParseFromSchemaString(schema), E_OK);
361     Query query3 = Query::Select().GreaterThan("id", 1).SuggestIndex("car");
362 
363     /**
364      * @tc.steps: step8. Create QueryObject with query3 and call GetQuerySql to check the sql
365      * @tc.expected: step8. the sql not contains "INDEXED BY $.car".
366      */
367     QueryObject queryObject3(query3);
368     queryObject3.SetSchema(schemaObject3);
369     SqliteQueryHelper helper3 = queryObject1.GetQueryHelper(errCode);
370     ASSERT_EQ(errCode, E_OK);
371     std::string sql3;
372     ASSERT_EQ(helper3.GetQuerySql(sql3, false), E_OK);
373     pos = sql3.find("INDEXED BY '$.car'", 0);
374     ASSERT_TRUE(pos == std::string::npos);
375 }
376 #endif