From 2e27ee2d35de00e74cba3b162f53291ac8243cdd Mon Sep 17 00:00:00 2001 From: imotai Date: Wed, 9 Aug 2023 11:08:50 +0800 Subject: [PATCH] Feat/get (#602) * feat: add doc store * feat: add get doc by id * test: add test case --- sdk/package.json | 2 +- sdk/src/index.ts | 3 ++- sdk/src/provider/indexer_provider.ts | 15 ++++++++++++++ sdk/src/store/document_v2.ts | 30 ++++++++++++++++++++++++++++ sdk/tests/client_v2.test.ts | 17 ++++++++++++++++ src/node/src/indexer_impl.rs | 19 ++++++++++++++++-- src/proto/proto/db3_indexer.proto | 11 ++++++++++ src/storage/src/db_store_v2.rs | 24 ++++++++++++++++++++++ src/storage/src/doc_store.rs | 15 ++++++-------- 9 files changed, 123 insertions(+), 13 deletions(-) diff --git a/sdk/package.json b/sdk/package.json index dcc2b530..11d127ed 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "db3.js", - "version": "0.4.1", + "version": "0.4.6", "description": "DB3 Network Javascript API", "author": "dbpunk labs", "keywords": [ diff --git a/sdk/src/index.ts b/sdk/src/index.ts index aaba2b9b..904cd043 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -22,6 +22,7 @@ export { createFromExternal, } from './account/db3_account' export type { Client, ReadClient } from './client/types' +export type { DocumentData, DocumentEntry } from './client/base' export type { Database, Collection, @@ -30,7 +31,7 @@ export type { MutationResult, QueryResult, } from './store/types' -export { addDoc, updateDoc, deleteDoc, queryDoc } from './store/document_v2' +export { addDoc, updateDoc, deleteDoc, queryDoc, getDoc } from './store/document_v2' export { SystemConfig, SystemStatus, Version } from './proto/db3_base' export { diff --git a/sdk/src/provider/indexer_provider.ts b/sdk/src/provider/indexer_provider.ts index b7a92cdb..fd14076d 100644 --- a/sdk/src/provider/indexer_provider.ts +++ b/sdk/src/provider/indexer_provider.ts @@ -25,6 +25,7 @@ import { RunQueryRequest, GetContractSyncStatusRequest, GetCollectionOfDatabaseRequest, + GetDocRequest, } from '../proto/db3_indexer' import { SetupRequest, GetSystemStatusRequest } from '../proto/db3_system' import { Query } from '../proto/db3_database_v2' @@ -61,6 +62,20 @@ export class IndexerProvider { throw new DB3Error(e as RpcError) } } + + async getDoc(db: string, colName: string, id: string) { + const request: GetDocRequest = { + dbAddr: db, + colName, + id, + } + try { + const { response } = await this.client.getDoc(request) + return response + } catch (e) { + throw new DB3Error(e as RpcError) + } + } async setup(signature: string, payload: string) { try { const request: SetupRequest = { diff --git a/sdk/src/store/document_v2.ts b/sdk/src/store/document_v2.ts index c3bee860..9c9baef6 100644 --- a/sdk/src/store/document_v2.ts +++ b/sdk/src/store/document_v2.ts @@ -40,6 +40,7 @@ async function runQueryInternal(col: Collection, query: Query) { id: doc.id, } as DocumentEntry }) + return { docs: entries, collection: col, @@ -115,6 +116,35 @@ export async function queryDoc( } } +/** + * + * This function gets a document from the database by its ID. + * + * ```ts + * const doc = await getDoc(collection, "10") + * const id = doc.id + * const content = doc.doc + * ``` + * @param col - the instance of collection + * @param id - the id of document + * @returns the {@link DocumentEntry} if the document was found. Otherwise, raises an error. + **/ +export async function getDoc(col: Collection, id: string) { + const response = await col.db.client.indexer.getDoc( + col.db.addr, + col.name, + id + ) + if (response.document) { + return { + doc: JSON.parse(response.document.doc) as T, + id: response.document.id, + } as DocumentEntry + } else { + throw new Error('no document was found with id ' + id) + } +} + export async function deleteDoc(col: Collection, ids: string[]) { const documentMutation: DocumentMutation = { collectionName: col.name, diff --git a/sdk/tests/client_v2.test.ts b/sdk/tests/client_v2.test.ts index 982e392f..0452fe85 100644 --- a/sdk/tests/client_v2.test.ts +++ b/sdk/tests/client_v2.test.ts @@ -36,6 +36,7 @@ import { deleteDoc, updateDoc, queryDoc, + getDoc } from '../src/store/document_v2' import { createFromPrivateKey, @@ -370,6 +371,21 @@ describe('test db3.js client module', () => { age: 1, }) await new Promise((r) => setTimeout(r, 1000)) + { + const doc = await getDoc(collection, doc2Ret.id) + expect(doc).toBeDefined() + expect(doc.id).toBe(doc2Ret.id) + expect(doc.doc.city).toBe('beijing2') + expect(doc.doc.author).toBe('imotai1') + expect(doc.doc.age).toBe(1) + } + { + try { + const doc = await getDoc(collection, 1000000000000) + except(1).toBe(0) + } catch(e) {} + } + { const queryStr = '/[city = beijing]' const resultSet = await queryDoc( @@ -465,6 +481,7 @@ describe('test db3.js client module', () => { age: 10, }) await new Promise((r) => setTimeout(r, 2000)) + { const queryStr = '/[city = beijing]' const resultSet = await queryDoc( diff --git a/src/node/src/indexer_impl.rs b/src/node/src/indexer_impl.rs index 7737108e..4ddb9f56 100644 --- a/src/node/src/indexer_impl.rs +++ b/src/node/src/indexer_impl.rs @@ -23,7 +23,8 @@ use db3_event::event_processor::EventProcessorConfig; use db3_proto::db3_indexer_proto::indexer_node_server::IndexerNode; use db3_proto::db3_indexer_proto::{ ContractSyncStatus, GetCollectionOfDatabaseRequest, GetCollectionOfDatabaseResponse, - GetContractSyncStatusRequest, GetContractSyncStatusResponse, RunQueryRequest, RunQueryResponse, + GetContractSyncStatusRequest, GetContractSyncStatusResponse, GetDocRequest, GetDocResponse, + RunQueryRequest, RunQueryResponse, }; use db3_proto::db3_mutation_v2_proto::MutationAction; use db3_proto::db3_storage_proto::block_response::MutationWrapper; @@ -367,6 +368,21 @@ impl IndexerNode for IndexerNodeImpl { })) } + async fn get_doc( + &self, + request: Request, + ) -> std::result::Result, Status> { + let r = request.into_inner(); + let addr = DB3Address::from_hex(r.db_addr.as_str()).map_err(|e| { + Status::invalid_argument(format!("fail to parse the db address for {e}")) + })?; + let document = self + .db_store + .get_doc(&addr, r.col_name.as_str(), r.id) + .map_err(|e| Status::internal(format!("{e}")))?; + Ok(Response::new(GetDocResponse { document })) + } + async fn run_query( &self, request: Request, @@ -376,7 +392,6 @@ impl IndexerNode for IndexerNodeImpl { Status::invalid_argument(format!("fail to parse the db address for {e}")) })?; if let Some(q) = &r.query { - info!("query str {} q {:?}", q.query_str, q); let (documents, count) = self .db_store .query_docs(&addr, r.col_name.as_str(), q) diff --git a/src/proto/proto/db3_indexer.proto b/src/proto/proto/db3_indexer.proto index 18eafc44..e406d2ec 100644 --- a/src/proto/proto/db3_indexer.proto +++ b/src/proto/proto/db3_indexer.proto @@ -61,9 +61,20 @@ message GetContractSyncStatusResponse { message GetContractSyncStatusRequest {} +message GetDocRequest { + string db_addr = 1; + string col_name = 2; + int64 id = 3; +} + +message GetDocResponse { + db3_database_v2_proto.Document document = 1; +} + service IndexerNode { rpc GetContractSyncStatus(GetContractSyncStatusRequest) returns (GetContractSyncStatusResponse) {} rpc GetCollectionOfDatabase(GetCollectionOfDatabaseRequest) returns (GetCollectionOfDatabaseResponse) {} // method for query document rpc RunQuery(RunQueryRequest) returns (RunQueryResponse) {} + rpc GetDoc(GetDocRequest) returns (GetDocResponse) {} } diff --git a/src/storage/src/db_store_v2.rs b/src/storage/src/db_store_v2.rs index ea5bdfa7..e31b3209 100644 --- a/src/storage/src/db_store_v2.rs +++ b/src/storage/src/db_store_v2.rs @@ -845,6 +845,30 @@ impl DBStoreV2 { Ok(()) } + pub fn get_doc( + &self, + db_addr: &DB3Address, + col_name: &str, + doc_id: i64, + ) -> Result> { + if !self.is_db_collection_exist(db_addr, col_name)? { + return Err(DB3Error::CollectionNotFound( + col_name.to_string(), + db_addr.to_hex(), + )); + } + if self.config.enable_doc_store { + let doc = self.doc_store.get_doc(db_addr, col_name, doc_id)?; + if let Some(d) = doc { + Ok(Some(Document { id: doc_id, doc: d })) + } else { + Ok(None) + } + } else { + Ok(None) + } + } + pub fn get_doc_key_from_doc_id(&self, doc_id: i64) -> Result> { let doc_owner_store_cf_handle = self .se diff --git a/src/storage/src/doc_store.rs b/src/storage/src/doc_store.rs index db734f86..d83d0700 100644 --- a/src/storage/src/doc_store.rs +++ b/src/storage/src/doc_store.rs @@ -237,18 +237,15 @@ impl DocStore { } } - pub fn get_doc(&self, db_addr: &DB3Address, col_name: &str, id: i64) -> Result { + pub fn get_doc(&self, db_addr: &DB3Address, col_name: &str, id: i64) -> Result> { let db_opt = self.get_db_ref(db_addr); if let Some(db) = db_opt { let opt = db .get::(col_name, id) .map_err(|e| DB3Error::WriteStoreError(format!("{e}")))?; - Ok(opt) + Ok(Some(opt)) } else { - Err(DB3Error::WriteStoreError(format!( - "no database found with addr {}", - db_addr.to_hex() - ))) + Ok(None) } } @@ -349,7 +346,7 @@ mod tests { fn doc_get_test() { let (doc_store, id) = prepare_the_dataset(); let doc_str = r#"{"f2":"f2", "f1":"f1"}"#; - if let Ok(value) = doc_store.get_doc(&DB3Address::ZERO, "col1", id) { + if let Ok(Some(value)) = doc_store.get_doc(&DB3Address::ZERO, "col1", id) { let value: serde_json::Value = serde_json::from_str(value.as_str()).unwrap(); let right: serde_json::Value = serde_json::from_str(doc_str).unwrap(); assert_eq!(value, right); @@ -417,7 +414,7 @@ mod tests { fn doc_store_smoke_test() { let (doc_store, id) = prepare_the_dataset(); let db_id = DB3Address::ZERO; - if let Ok(value) = doc_store.get_doc(&db_id, "col1", id) { + if let Ok(Some(value)) = doc_store.get_doc(&db_id, "col1", id) { println!("{}", value.as_str()); let value: serde_json::Value = serde_json::from_str(value.as_str()).unwrap(); assert_eq!(value["f1"].as_str(), Some("f1")); @@ -475,7 +472,7 @@ mod tests { assert!(false); } - if let Ok(value) = doc_store.get_doc(&db_id, "col1", id) { + if let Ok(Some(value)) = doc_store.get_doc(&db_id, "col1", id) { let value: serde_json::Value = serde_json::from_str(value.as_str()).unwrap(); assert_eq!(value["test"].as_str(), Some("v2")); } else {