Important References
IMPORTANT: You will need the service_account.json, the API key, and the URL for the database sent in your purchase confirmation email.
The complete API is defined in the proto file found under the gRPC code examples. These are labeled by rpc in the RogueDB service. All responses are the Response message. All inputs are captured in the parentheses (Insert, Update, Remove, Search).
Complete Guide
The code examples covers authentication, all 4 CRUD APIs, and schema change with detailed notes on usage. Included are functions for JWT token generation used for authentication and file detection.
1import datetime
2import json
3import jwt
4import time
5import os
6from pathlib import Path
7
8# REST and JSON Imports
9import requests
10
11# External Dependency Requirements:
12# PyJWT, requests
13
14def create_jwt(service_account: str, expire_minutes: int = 60) -> str:
15 # NOTE: Replace with file path to service_account.json
16 # from purchase confirmation email.
17 with open(service_account) as input_file:
18 key_data = json.load(input_file)
19
20 now = datetime.datetime.now(datetime.timezone.utc)
21 iat = int(time.mktime(now.timetuple()))
22 exp = int(time.mktime((now + datetime.timedelta(minutes=expire_minutes)).timetuple()))
23
24 payload = {
25 "iat": iat,
26 "exp": exp,
27 "iss": key_data['client_email'],
28 "aud": f"{key_data['client_email'].split('@')[0]}.roguedb.dev",
29 "sub": key_data['client_email'] }
30
31 headers = {
32 "kid": key_data['private_key_id'],
33 "alg": "RS256", # Google Cloud service accounts typically use RS256
34 "typ": "JWT" }
35
36 return jwt.encode(
37 payload, key_data['private_key'],
38 algorithm="RS256", headers=headers)
39
40def detect_files(directories: list[str]) -> list[Path]:
41 files = []
42 for directory in directories:
43 with os.scandir(directory) as scan:
44 for item in scan:
45 path = Path(item.path)
46 if path.is_file() and path.suffix == ".proto":
47 files.append(path)
48 elif path.is_dir():
49 for subitem in detect_files(directory=[item.path, ]):
50 files.append(subitem)
51 return files
52
53if __name__ == "__main__":
54 # See purchase confirmation emails for details and service_account.json.
55 url = "c-[YOUR_IDENTIFIER_FIRST_28_CHARACTERS].roguedb.dev"
56 api_key = "YOUR_API_KEY"
57 encoded_jwt = create_jwt("path/to/service_account.json")
58
59 headers = {
60 "Authorization": f"Bearer {encoded_jwt}",
61 "Content-Type": "application/json" }
62
63 ################################################
64 #### Insert, Update, and Remove API Example ####
65 ################################################
66
67 # NOTE: See queries.proto for all API definitions.
68 # Creating an Insert, Update, or Remove API with JSON.
69 request = {
70 # api_key and messages match Insert definition in queries.proto
71 "api_key" : api_key,
72 "messages": [
73 {
74 # Part after '/' will always be the proto package and message name
75 "@type": "type.googleapis.com/rogue.services.Test",
76
77 # Matches the field names for the message
78 "attribute1" : 10,
79 "attribute2" : 5,
80 } ]}
81
82 # No response given. Errors reported in status code.
83 # REST call for Insert API.
84 response = requests.post(
85 f"https://{url}/rest/insert",
86 headers=headers,
87 data=request)
88
89 # REST call for Update API.
90 response = requests.patch(
91 f"https://{url}/rest/update",
92 headers=headers,
93 data=request)
94
95 # REST call for Remove API.
96 response = requests.delete(
97 f"https://{url}/rest/remove",
98 headers=headers,
99 data=request)
100
101
102 ###############################
103 ##### Search API Examples #####
104 ###############################
105
106 # NOTE: See queries.proto for full API.
107 # Example of a basic index query.
108 # For Test, attribute1, attribute2, and attribute3 form the index.
109 # Search Query:
110 # Test.attribute1 >= 1 and Test.attribute2 >= 1 and Test.attribute3 >= true
111 # AND
112 # Test.attribute1 <= 10 and Test.attribute2 <= 10 and Test.attribute3 <= true
113 search = {
114 "api_key": api_key,
115 "queries": [
116 {
117 "basic":
118 {
119 "comparisons": ["GREATER_EQUAL", "LESSER_EQUAL"],
120 "operands": [
121 {
122 # Part after '/' will always be the proto package and message name
123 "@type": "type.googleapis.com/rogue.services.Test",
124
125 # Matches the field names for the message
126 "attribute1" : 1,
127 "attribute2" : 1,
128 "attribute3" : True,
129 },
130 {
131 "@type": "type.googleapis.com/rogue.services.Test",
132 "attribute1" : 10,
133 "attribute2" : 10,
134 "attribute3" : True
135 }]
136 }
137 }] }
138
139 # All search query types use this URL
140 response = requests.get(
141 f"https://{url}/rest/search",
142 headers=headers,
143 data=search)
144
145 # Queries are zero-indexed.
146 # Results are mapped to that index.
147 for result in response.results[0]:
148 pass # Will be JSON objects
149
150 # Example of a basic non-indexed query.
151 # Search Query: Test.attribute1 < 1 Test.attribute2 != 10
152 search = {
153 "api_key": api_key,
154 "queries": [
155 {
156 "basic":
157 {
158 "comparisons": ["LESSER", "NOT_EQUAL"],
159 "fields" : [1, 2], # Corresponds to field ids in test.proto
160 "operands": [
161 {
162 # Part after '/' will always be the proto package and message name
163 "@type": "type.googleapis.com/rogue.services.Test",
164
165 # Matches the field names for the message
166 "attribute1" : 1,
167 },
168 {
169 "@type": "type.googleapis.com/rogue.services.Test",
170 "attribute2" : 10,
171 }]
172 }
173 }] }
174
175 #####################################
176 ##### Schema Change API Example #####
177 #####################################
178
179 proto_file_definitions = []
180 # Proto file definitions. No modifications required.
181 for file in detect_files(directories=[
182 "absolute/path/to/protos/directory1",
183 "absolute/path/to/protos/directory2"]):
184 with open(file) as input_file:
185 proto_file_definitions.schemas.append(input_file.read())
186
187 # Any schemas excluded will have associated data deleted.
188 # Schema change failure results in no changes applied.
189 response = requests.post(
190 f"https://{url}/rest/search",
191 headers=headers,
192 data={
193 "api_key" : api_key,
194 "schemas" : proto_file_definitions
195 })1#include <format>
2#include <fstream>
3
4// External dependencies. Single header files.
5#include <cpr/cpr.h>
6#include <jwt-cpp/jwt.h>
7
8std::string createJwt()
9{
10 // Values found in service_account.json.
11 const std::string SERVICE_ACCOUNT_EMAIL{ "YOUR_SERVICE_ACCOUNT_EMAIL" };
12 const std::string PRIVATE_KEY_ID{ "YOUR_PRIVATE_KEY_ID" };
13 const std::string PRIVATE_KEY{ "YOUR_PRIVATE_KEY" };
14 const auto now{ std::chrono::system_clock::now() };
15 return std::string{ jwt::create()
16 .set_issuer(SERVICE_ACCOUNT_EMAIL)
17 .set_subject(SERVICE_ACCOUNT_EMAIL)
18 .set_audience(std::format(
19 "{}.roguedb.dev",
20 SERVICE_ACCOUNT_EMAIL.substr(
21 0, SERVICE_ACCOUNT_EMAIL.find("@"))))
22 .set_issued_at(now)
23 .set_expires_at(now + std::chrono::hours(1))
24 .set_header_claim("kid", jwt::claim(PRIVATE_KEY_ID))
25 .sign(jwt::algorithm::rs256{ PRIVATE_KEY }) };
26}
27
28std::vector<std::filesystem::path> detectFiles(
29 const std::filesystem::path& directory)
30{
31 std::vector<std::filesystem::path> files{};
32 for(const auto& entry : std::filesystem::directory_iterator{directory})
33 {
34 if(!std::filesystem::is_directory(entry))
35 {
36 if(entry.path().extension() == ".proto")
37 {
38 files.emplace_back(std::move(entry.path()));
39 }
40 }
41 else
42 {
43 for(auto& filename : detectFiles(entry.path()))
44 {
45 files.emplace_back(std::move(filename));
46 }
47 }
48 }
49 return files;
50}
51
52int main(int argc, char** argv)
53{
54 // DISCLAIMER: We do not currently test C++ with REST.
55 // Below code is best effort from documentation of popular
56 // single header libraries for REST and JWT.
57
58 // See purchase confirmation emails for details and service_account.json.
59 const std::string API_KEY{ "YOUR_API_KEY" };
60 const std::string URL{ "c-[YOUR_IDENTIFIER_FIRST_28_CHARACTERS].roguedb.dev" };
61 const std::string ENCODED_JWT{ createJwt() };
62
63 ////////////////////////////////////////////////////////
64 /////// Insert, Update, and Remove API Example ///////
65 ////////////////////////////////////////////////////////
66
67
68 // No response given. Errors reported in status code.
69 // REST call for Insert API.
70
71 std::string request{ "{\n\t\"api_key\": \"" };
72 request.append(API_KEY);
73 request.append(R"(",
74 "messages": [
75 {
76 "@type": "type.googleapis.com/rogue.services.Test",
77 "attribute1": 10,
78 "attribute2": 5
79 }
80 ]
81})");
82 cpr::Response response = cpr::Post(
83 cpr::Url{ std::format("{}/rest/insert", URL) },
84 cpr::Bearer{ ENCODED_JWT },
85 cpr::Header{{ "Content-Type", "application/json" }},
86 cpr::Body{ request });
87
88 // REST call for Update API.
89 response = cpr::Patch(
90 cpr::Url{ std::format("{}/rest/update", URL) },
91 cpr::Bearer{ ENCODED_JWT },
92 cpr::Header{{ "Content-Type", "application/json" }},
93 cpr::Body{ request });
94
95 // REST call for Remove API.
96 response = cpr::Delete(
97 cpr::Url{ std::format("{}/rest/remove", URL) },
98 cpr::Bearer{ ENCODED_JWT },
99 cpr::Header{{ "Content-Type", "application/json" }},
100 cpr::Body{ request });
101
102 //////////////////////////////////////
103 //////// Search API Example ////////
104 //////////////////////////////////////
105
106 // NOTE: See queries.proto for full API.
107 // Example of a basic index query.
108 // For Test, attribute1, attribute2, and attribute3 form the index.
109 // Search Query:
110 // Test.attribute1 >= 1 and Test.attribute2 >= 1 and Test.attribute3 >= true
111 // AND
112 // Test.attribute1 <= 10 and Test.attribute2 <= 10 and Test.attribute3 <= true
113 request = "{\n\"api_key";
114 request.append(API_KEY);
115
116 // For @type, '/' will always be the proto package and message name
117 // attribute1, attribute2, and attribute3 match the field names in test.proto
118 request.append(R"(",
119 "queries": [
120 {
121 "basic": {
122 "comparisons": ["GREATER_EQUAL", "LESSER_EQUAL"],
123 "operands": [
124 {
125 "@type": "type.googleapis.com/rogue.services.Test",
126 "attribute1": 1,
127 "attribute2": 1,
128 "attribute3": true
129 },
130 {
131 "@type": "type.googleapis.com/rogue.services.Test",
132 "attribute1": 10,
133 "attribute2": 10,
134 "attribute3": true
135 }
136 ]
137 }
138 }
139 ]
140})");
141
142 // All search query types use this URL
143 // Queries are zero-indexed.
144 // Results are mapped to results field.
145 // All messages are stored in the messages field.
146 response = cpr::Get(
147 cpr::Url{ std::format("{}/rest/search", URL) },
148 cpr::Bearer{ ENCODED_JWT },
149 cpr::Header{{ "Content-Type", "application/json" }},
150 cpr::Body{ request });
151
152 // Example of a basic non-indexed query.
153 // Search Query: Test.attribute1 < 1 and Test.attribute2 != 10
154 request = "{\n\"api_key";
155 request.append(API_KEY);
156
157 // Fields corresponds to the field ids in test.proto
158 request.append(R"(",
159 "queries": [
160 {
161 "basic": {
162 "comparisons": ["GREATER_EQUAL", "LESSER_EQUAL"],
163 "fields": [1, 2],
164 "operands": [
165 {
166 "@type": "type.googleapis.com/rogue.services.Test",
167 "attribute1": 1,
168 },
169 {
170 "@type": "type.googleapis.com/rogue.services.Test",
171 "attribute1": 10,
172 }
173 ]
174 }
175 }
176 ]
177})");
178
179 ///////////////////////////////////
180 //// Schema Change API Example ////
181 ///////////////////////////////////
182
183 request = "{\n\"api_key";
184 request.append(API_KEY);
185 request.append(R"("
186 "schemas": [)");
187
188 // All proto files should be sent in a list of
189 // their contents. No modifications required.
190 for(const auto& file : detectFiles("absolute/path/to/protos/directory"))
191 {
192 std::ifstream inputFile{ file.native() };
193 std::stringstream buffer{};
194 buffer << inputFile.rdbuf();
195 request.append(std::format("\"{}\",", buffer.str()));
196 }
197 request.pop_back();
198 request.append("]\n}");
199
200 // Any schemas excluded will have associated data deleted.
201 // Schema change failure results in no changes applied.
202 response = cpr::Post(
203 cpr::Url{ std::format("{}/rest/subscribe", URL) },
204 cpr::Bearer{ ENCODED_JWT },
205 cpr::Header{{ "Content-Type", "application/json" }},
206 cpr::Body{ request });
207}1package main
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "os"
10 "path/filepath"
11 "strings"
12 "time"
13
14 "github.com/golang-jwt/jwt/v5"
15)
16
17func createJWT() string {
18 // Values found in service_account.json
19 const SERVICE_ACCOUNT_EMAIL = "YOUR_SERVICE_ACCOUNT_EMAIL"
20 const PRIVATE_KEY_ID = "YOUR_PRIVATE_KEY_ID"
21 const PRIVATE_KEY = "YOUR_PRIVATE_KEY"
22
23 now := time.Now()
24
25 // Create JWT token
26 token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
27 "iss": SERVICE_ACCOUNT_EMAIL,
28 "sub": SERVICE_ACCOUNT_EMAIL,
29 "aud": fmt.Sprintf("%s.roguedb.dev", SERVICE_ACCOUNT_EMAIL[:len(SERVICE_ACCOUNT_EMAIL)-len(SERVICE_ACCOUNT_EMAIL[strings.LastIndex(SERVICE_ACCOUNT_EMAIL, "@")+1:])]),
30 "iat": now.Unix(),
31 "exp": now.Add(time.Hour).Unix(),
32 "header": map[string]interface{}{
33 "kid": PRIVATE_KEY_ID,
34 },
35 })
36
37 tokenString, err := token.SignedString([]byte(PRIVATE_KEY))
38 if err != nil {
39 panic(err)
40 }
41
42 return tokenString
43}
44
45func detectFiles(directories []string) []string {
46 var files []string
47
48 for _, directory := range directories {
49 err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
50 if err != nil {
51 return err
52 }
53
54 // Skip directories
55 if !info.IsDir() {
56 files = append(files, path)
57 }
58
59 return nil
60 })
61
62 if err != nil {
63 panic(err)
64 }
65 }
66
67 return files
68}
69
70func main() {
71 // See purchase confirmation emails for details and service_account.json
72 const API_KEY = "YOUR_API_KEY"
73 const URL = "c-[YOUR_IDENTIFIER_FIRST_28_CHARACTERS].roguedb.dev"
74 encodedJWT := createJWT()
75
76 ////////////////////////////////////////////////////////
77 /////// Insert, Update, and Remove API Example ///////
78 ////////////////////////////////////////////////////////
79
80 // NOTE: See queries.proto for all API definitions.
81 // Creating an Insert, Update, or Remove API with JSON.
82 payload := map[string]interface{}{
83 "api_key": API_KEY,
84 "messages": []map[string]interface{}{
85 map[string]interface{}{
86 // Part after '/' will always be the proto package and message name
87 "@type": "type.googleapis.com/rogue.services.Test",
88
89 // Matches the field names for the message
90 "attribute1": 10,
91 "attribute2": 5}},
92 }
93
94 jsonPayload, err := json.Marshal(payload)
95 if err != nil {
96 panic(err)
97 }
98
99 request, err := http.NewRequest("POST", URL+"/rest/insert", bytes.NewBuffer(jsonPayload)) // Insert API
100 // request, err := http.NewRequest("PATCH", URL+"/rest/update", bytes.NewBuffer(jsonPayload)) // Update API
101 // request, err := http.NewRequest("DELETE", URL+"/rest/remove", bytes.NewBuffer(jsonPayload)) // Remove API
102 if err != nil {
103 panic(err)
104 }
105
106 request.Header.Set("Authorization", "Bearer "+encodedJWT)
107 request.Header.Set("Content-Type", "application/json")
108
109 client := &http.Client{Timeout: 10 * time.Second}
110
111 // No response given. Errors reported in status code.
112 response, err := client.Do(request)
113 response.Body.Close()
114
115 //////////////////////////////////////
116 //////// Search API Example ////////
117 //////////////////////////////////////
118
119 // Example of a baisc index query.
120 // For Test, attribute1, attribute2, and attribute3 form the index.
121 // Search Query:
122 // Test.attribute1 >= 1 and Test.attribute2 >= 1 and Test.attribute3 >= true
123 // AND
124 // Test.attribute1 <= 10 and Test.attribute2 <= 10 and Test.attribute3 <= true
125 payload = map[string]interface{}{
126 "api_key": API_KEY,
127 "queries": []map[string]interface{}{
128 map[string]interface{}{
129 "basic": map[string]interface{}{
130 "comparisons": []string{"GREATER_EQUAL", "LESSER_EQUAL"},
131 "operands": []map[string]interface{}{
132 map[string]interface{}{
133 // Part after '/' will always be the proto package and message name
134 "@type": "type.googleapis.com/rogue.services.Test",
135
136 // Matches the field names for the message
137 "attribute1": 1,
138 "attribute2": 1,
139 "attribute3": true},
140 map[string]interface{}{
141 "@type": "type.googleapis.com/rogue.services.Test",
142 "attribute1": 10,
143 "attribute2": 10,
144 "attribute3": true}}}}}}
145
146 jsonPayload, err = json.Marshal(payload)
147 if err != nil {
148 panic(err)
149 }
150
151 // All search query types use this URL
152 request, err = http.NewRequest("GET", URL+"/rest/search", bytes.NewBuffer(jsonPayload))
153 if err != nil {
154 panic(err)
155 }
156
157 request.Header.Set("Authorization", "Bearer "+encodedJWT)
158 request.Header.Set("Content-Type", "application/json")
159
160 response, err = client.Do(request)
161 body, err := io.ReadAll(response.Body)
162 if err != nil {
163 panic(err)
164 }
165
166 // Queries are zero-indexed.
167 // Results are mapped to results field.
168 // All messages are stored in the messages field.
169 fmt.Print(body)
170 response.Body.Close()
171
172 // Example of a basic non-indexed query
173 // Search Query:
174 // Test.attribute1 >= 1 && Test.attribute2 >= 1 && Test.attribute3 >= true
175 // AND
176 // Test.attribute1 <= 10 && Test.attribute2 <= 10 && Test.attribute3 <= true
177 payload = map[string]interface{}{
178 "api_key": API_KEY,
179 "queries": []map[string]interface{}{
180 map[string]interface{}{
181 "basic": map[string]interface{}{
182 "comparisons": []string{"GREATER_EQUAL", "LESSER_EQUAL"},
183 "fields": []int{1, 2}, // Corresponds to field ids in test.proto
184 "operands": []map[string]interface{}{
185 map[string]interface{}{
186 // Part after '/' will always be the proto package and message name
187 "@type": "type.googleapis.com/rogue.services.Test",
188
189 // Matches the field names for the message
190 "attribute1": 1},
191 map[string]interface{}{
192 "@type": "type.googleapis.com/rogue.services.Test",
193 "attribute2": 10}}}}}}
194
195 ///////////////////////////////////
196 //// Schema Change API Example ////
197 ///////////////////////////////////
198
199 // All proto files should be sent in a list of
200 // their contents. No modifications required.
201 var protoFileDefinitions []string
202 for _, file := range detectFiles([]string{"path/to/proto/directory1", "path/to/proto/directory2"}) {
203 content, err := os.ReadFile(file)
204 if err != nil {
205 panic(err)
206 }
207 protoFileDefinitions = append(protoFileDefinitions, string(content))
208 }
209
210 // Any schemas excluded will have associated data deleted.
211 // Schema change failure results in no changes applied.
212 payload = map[string]interface{}{
213 "api_key": API_KEY,
214 "schemas": protoFileDefinitions}
215
216 jsonPayload, err = json.Marshal(payload)
217 if err != nil {
218 panic(err)
219 }
220
221 request, err = http.NewRequest("POST", URL+"/rest/subscribe", bytes.NewBuffer(jsonPayload))
222 if err != nil {
223 panic(err)
224 }
225
226 request.Header.Set("Authorization", "Bearer "+encodedJWT)
227 request.Header.Set("Content-Type", "application/json")
228
229 // No response given. Errors reported in status code.
230 response, err = client.Do(request)
231 response.Body.Close()
232}1import datetime
2import json
3import jwt
4import time
5import os
6from pathlib import Path
7
8# gRPC and Protocol Buffers Imports
9import grpc
10from grpc_status import rpc_status
11
12from roguedb.roguedb_pb2_grpc import RogueDBStub
13from roguedb.queries_pb2 import (
14 Insert, Update, Remove, Basic, Search,
15 LogicalOperator, ComparisonOperator, Subscribe)
16from roguedb.test_pb2 import Test
17
18# External Dependency Requirements:
19# grpcio, protobuf, PyJWT
20
21def create_jwt(service_account: str, expire_minutes: int = 60) -> str:
22 # NOTE: Replace with file path to service_account.json
23 # from purchase confirmation email.
24 with open(service_account) as input_file:
25 key_data = json.load(input_file)
26
27 now = datetime.datetime.now(datetime.timezone.utc)
28 iat = int(time.mktime(now.timetuple()))
29 exp = int(time.mktime((
30 now + datetime.timedelta(minutes=expire_minutes)).timetuple()))
31
32 payload = {
33 "iat": iat,
34 "exp": exp,
35 "iss": key_data['client_email'],
36 "aud": f"{key_data['client_email'].split('@')[0]}.roguedb.dev",
37 "sub": key_data['client_email'] }
38
39 headers = {
40 "kid": key_data['private_key_id'],
41 "alg": "RS256", # Google Cloud service accounts typically use RS256
42 "typ": "JWT" }
43
44 return jwt.encode(
45 payload, key_data['private_key'],
46 algorithm="RS256", headers=headers)
47
48def detect_files(directories: list[str]) -> list[Path]:
49 files = []
50 for directory in directories:
51 with os.scandir(directory) as scan:
52 for item in scan:
53 path = Path(item.path)
54 if path.is_file() and path.suffix == ".proto":
55 files.append(path)
56 elif path.is_dir():
57 for subitem in detect_files(directory=[item.path, ]):
58 files.append(subitem)
59 return files
60
61if __name__ == "__main__":
62 # See purchase confirmation emails for details and service_account.json.
63 url = "c-[YOUR_IDENTIFIER_FIRST_28_CHARACTERS].roguedb.dev"
64 api_key = "YOUR_API_KEY"
65 encoded_jwt = create_jwt("path/to/service_account.json")
66
67 ################################################
68 #### Insert, Update, and Remove API Example ####
69 ################################################
70
71 request = Insert(api_key=api_key) # Insert API
72 # update = Update(api_key=api_key) # Update API
73 # remove = Remove(api_key=api_key) # Remove API
74
75 # Insert, Update, and Remove are identical in use.
76 request.messages.add()
77
78 # See test.proto for the schema definition.
79 test = Test(attribute1=10)
80 request.messages[0].Pack(test)
81
82 roguedb = RogueDBStub(
83 channel=grpc.secure_channel(f"{url}:443",
84 grpc.ssl_channel_credentials()))
85
86 def yield_request():
87 yield request
88
89 try:
90 for _ in roguedb.insert(
91 # for _ in roguedb.update( # Update API call
92 # for _ in roguedb.remove( # Remove API call
93 yield_request(),
94 metadata=[("authorization", f"Bearer {encoded_jwt}")]):
95 pass
96 except grpc.RpcError as rpc_error:
97 # Any errors get reported in status.
98 status = rpc_status.from_call(rpc_error)
99
100 ###############################
101 ##### Search API Examples #####
102 ###############################
103
104 # Example of a basic index query.
105 # For Test, attribute1, attribute2, and attribute3 form the index.
106 # Search Query:
107 # Test.attribute1 >= 1 and Test.attribute2 >= 1 and Test.attribute3 >= true
108 # AND
109 # Test.attribute1 <= 10 and Test.attribute2 <= 10 and Test.attribute3 <= true
110 search = Search(api_key=api_key)
111 expression = search.queries.add().basic
112 expression.logical_operator = LogicalOperator.AND
113
114 expression.comparisons.append(ComparisonOperator.GREATER_EQUAL)
115 expression.operands.add().Pack(Test(attribute1=1, attribute2=1, attribute3=True))
116
117 expression.comparisons.append(ComparisonOperator.LESSER_EQUAL)
118 expression.operands.add().Pack(Test(attribute2=10, attribute2=10, attribute3=True))
119
120 def yield_search():
121 yield search
122
123 for response in roguedb.search(
124 yield_search(),
125 metadata=[("authorization", f"Bearer {encoded_jwt}")]):
126 results = []
127
128 # Queries are zero-indexed. Partial results get sent
129 # and mapped to that index.
130 for result in response.results[0].messages:
131 test = Test()
132 response.results[0].Unpack(test)
133
134 # Each response sends a list of the query ids finished
135 # with processing.
136 if 0 in response.finished:
137 pass # Finished processing.
138
139 # Example of a basic non-indexed query.
140 # Search Query: Test.attribute1 < 1 AND Test.attribute2 != 10
141 search = Search(api_key=api_key)
142 expression = search.queries.add().basic
143 expression.logical_operator = LogicalOperator.AND
144
145 expression.comparisons.append(ComparisonOperator.LESSER)
146 expression.operands.add().Pack(Test(attribute1=1))
147 expression.fields.append(1) # Corresponds to field id in test.proto
148
149 expression.comparisons.append(ComparisonOperator.NOT_EQUAL)
150 expression.operands.add().Pack(Test(attribute2=10))
151 expression.fields.append(2) # Corresponds to field id in test.proto
152
153 #####################################
154 ##### Schema Change API Example #####
155 #####################################
156
157 subscribe = Subscribe(api_key=api_key)
158
159 # All proto files should be sent in a list of
160 # their contents. No modifications required.
161 for file in detect_files(directories=[
162 "absolute/path/to/protos/directory1",
163 "absolute/path/to/protos/directory2"]):
164 with open(file) as input_file:
165 subscribe.schemas.append(input_file.read())
166
167 # Any schemas excluded will have associated data deleted.
168 # Schema change failure results in no changes applied.
169 response = roguedb.subscribe(subscribe)1#include <format>
2#include <fstream>
3#include <grpcpp/grpcpp.h>
4#include <jwt-cpp/jwt.h>
5
6#include "getting_started/roguedb.grpc.pb.h"
7
8std::string createJwt()
9{
10 // Values found in service_account.json.
11 const std::string SERVICE_ACCOUNT_EMAIL{ "YOUR_SERVICE_ACCOUNT_EMAIL" };
12 const std::string PRIVATE_KEY_ID{ "YOUR_PRIVATE_KEY_ID" };
13 const std::string PRIVATE_KEY{ "YOUR_PRIVATE_KEY" };
14 const auto now{ std::chrono::system_clock::now() };
15 return std::string{ jwt::create()
16 .set_issuer(SERVICE_ACCOUNT_EMAIL)
17 .set_subject(SERVICE_ACCOUNT_EMAIL)
18 .set_audience(std::format(
19 "{}.roguedb.dev",
20 SERVICE_ACCOUNT_EMAIL.substr(
21 0, SERVICE_ACCOUNT_EMAIL.find("@"))))
22 .set_issued_at(now)
23 .set_expires_at(now + std::chrono::hours(1))
24 .set_header_claim("kid", jwt::claim(PRIVATE_KEY_ID))
25 .sign(jwt::algorithm::rs256{ PRIVATE_KEY }) };
26}
27
28std::vector<std::filesystem::path> detectFiles(
29 const std::filesystem::path& directory)
30{
31 std::vector<std::filesystem::path> files{};
32 for(const auto& entry : std::filesystem::directory_iterator{directory})
33 {
34 if(!std::filesystem::is_directory(entry))
35 {
36 if(entry.path().extension() == ".proto")
37 {
38 files.emplace_back(std::move(entry.path()));
39 }
40 }
41 else
42 {
43 for(auto& filename : detectFiles(entry.path()))
44 {
45 files.emplace_back(std::move(filename));
46 }
47 }
48 }
49 return files;
50}
51
52int main(int argc, char** argv)
53{
54 // See purchase confirmation emails for details and service_account.json.
55 const std::string API_KEY{ "YOUR_API_KEY" };
56 const std::string URL{ "c-[YOUR_IDENTIFIER_FIRST_28_CHARACTERS].roguedb.dev" };
57 const std::string ENCODED_JWT{ createJwt() };
58
59 ////////////////////////////////////////////////////////
60 /////// Insert, Update, and Remove API Example ///////
61 ////////////////////////////////////////////////////////
62
63 rogue::services::Test test{};
64 test.set_attribute1(10);
65
66 rogue::services::Insert request{}; // Insert API
67 // rogue::services::Update update{}; // Update API
68 // rogue::services::Remove remove{}; // Remove API
69
70 request.set_api_key(API_KEY);
71
72 // Insert, Update, and Remove are identical in use.
73 request.add_messages()->PackFrom(test);
74
75 // gRPC Connection
76 std::unique_ptr<rogue::services::RogueDB::Stub> roguedb{ rogue::services::RogueDB::NewStub(
77 grpc::CreateChannel(
78 std::format("{}:443", URL),
79 grpc::SslCredentials(grpc::SslCredentialsOptions()))) };
80
81 grpc::ClientContext context{};
82 context.AddMetadata("Authorization", std::format("Bearer {}", ENCODED_JWT));
83 auto stream{ roguedb->insert(&context) };
84 // auto stream{ roguedb->update(&context) }; // Update API
85 // auto stream{ roguedb->remove(&context) }; // Remove API
86
87 stream->Write(request);
88 stream->WritesDone(); // Signal all queries sent. Otherwise, blocks.
89
90 // No response is given for Insert, Update, and Remove
91 // Any errors get reported in status.
92 grpc::Status status{ stream->Finish() };
93
94 //////////////////////////////////////
95 //////// Search API Example ////////
96 //////////////////////////////////////
97
98 {
99 // Example of a baisc index query.
100 rogue::services::Search search{};
101 search.set_api_key(API_KEY);
102
103 // For Test, attribute1, attribute2, and attribute3 form the index.
104 // Search Query:
105 // Test.attribute1 >= 1 and Test.attribute2 >= 1 and Test.attribute3 >= true
106 // AND
107 // Test.attribute1 <= 10 and Test.attribute2 <= 10 and Test.attribute3 <= true
108 rogue::services::Basic& expression{ *search.add_queries()->mutable_basic() };
109 expression.set_logical_operator(rogue::services::LogicalOperator::AND);
110
111 test = rogue::services::Test{};
112 expression.add_comparisons(rogue::services::ComparisonOperator::GREATER_EQUAL);
113 test.set_attribute1(1);
114 test.set_attribute2(1);
115 test.set_attribute3(true);
116 (*expression.add_operands()).PackFrom(test);
117
118 expression.add_comparisons(rogue::services::ComparisonOperator::LESSER_EQUAL);
119 test.set_attribute1(10);
120 test.set_attribute2(10);
121 test.set_attribute3(true);
122 (*expression.add_operands()).PackFrom(test);
123
124 grpc::ClientContext context{};
125 rogue::services::Response response{};
126 std::shared_ptr<grpc::ClientReaderWriter<
127 rogue::services::Search, rogue::services::Response>> stream{
128 roguedb->search(&context) };
129
130 stream->Write(search);
131 stream->WritesDone(); // Signal all queries sent. Otherwise, blocks.
132
133 // Multiple queries or large queries should be processed on a separate thread
134 // to prevent blocking the database.
135 stream->Read(&response);
136 status = stream->Finish(); // Signal all queries sent. Blocks otherwise.
137
138 std::vector<rogue::services::Test> results{};
139 // Queries are zero-indexed. Partial results get sent
140 // and mapped to that index.
141 for(const auto& result : response.results().at(0).messages())
142 {
143 result.UnpackTo(&results.emplace_back());
144 }
145
146 // Each response sends a list of the query ids finished
147 // with processing.
148 for(const auto& finishedId : response.finished())
149 {
150 if(finishedId == 0)
151 {}
152 }
153 }
154 {
155 // Example of a baisc non-indexed query.
156 rogue::services::Search search{};
157 search.set_api_key(API_KEY);
158
159 // Search Query:
160 // Test.attribute1 >= 1 && Test.attribute2 <= 10
161 rogue::services::Basic& expression{ *search.add_queries()->mutable_basic() };
162 expression.set_logical_operator(rogue::services::LogicalOperator::AND);
163
164 test = rogue::services::Test{};
165 test.set_attribute1(1);
166 expression.add_fields(1); // Corresponds to field id in test.proto
167 (*expression.add_operands()).PackFrom(test);
168 expression.add_comparisons(rogue::services::ComparisonOperator::GREATER_EQUAL);
169
170 test.set_attribute2(10);
171 expression.add_fields(2); // Corresponds to field id in test.proto
172 (*expression.add_operands()).PackFrom(test);
173 expression.add_comparisons(rogue::services::ComparisonOperator::LESSER_EQUAL);
174 }
175
176 ///////////////////////////////////
177 //// Schema Change API Example ////
178 ///////////////////////////////////
179
180 rogue::services::Subscribe subscribe{};
181 subscribe.set_api_key(API_KEY);
182
183 // All proto files should be sent in a list of
184 // their contents. No modifications required.
185 for(const auto& file : detectFiles("absolute/path/to/protos/directory"))
186 {
187 std::ifstream inputFile{ file.native() };
188 std::stringstream buffer{};
189 buffer << inputFile.rdbuf();
190 subscribe.add_schemas(buffer.str());
191 }
192
193 rogue::services::Response response{};
194 // Any schemas excluded will have associated data deleted.
195 // Schema change failure results in no changes applied.
196 roguedb->subscribe(&context, subscribe, &response);
197}1package main
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "os"
8 "path/filepath"
9 "slices"
10 "strings"
11 "time"
12
13 "github.com/golang-jwt/jwt/v5"
14 "github.com/roguedb/roguedb-open-source/roguedb/core_schemas"
15 "google.golang.org/grpc"
16 "google.golang.org/grpc/credentials"
17 "google.golang.org/grpc/metadata"
18 "google.golang.org/protobuf/types/known/anypb"
19)
20
21func createJWT() string {
22 // Values found in service_account.json
23 const SERVICE_ACCOUNT_EMAIL = "YOUR_SERVICE_ACCOUNT_EMAIL"
24 const PRIVATE_KEY_ID = "YOUR_PRIVATE_KEY_ID"
25 const PRIVATE_KEY = "YOUR_PRIVATE_KEY"
26
27 now := time.Now()
28
29 // Create JWT token
30 token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
31 "iss": SERVICE_ACCOUNT_EMAIL,
32 "sub": SERVICE_ACCOUNT_EMAIL,
33 "aud": fmt.Sprintf("%s.roguedb.dev", SERVICE_ACCOUNT_EMAIL[:len(SERVICE_ACCOUNT_EMAIL)-len(SERVICE_ACCOUNT_EMAIL[strings.LastIndex(SERVICE_ACCOUNT_EMAIL, "@")+1:])]),
34 "iat": now.Unix(),
35 "exp": now.Add(time.Hour).Unix(),
36 "header": map[string]interface{}{
37 "kid": PRIVATE_KEY_ID,
38 },
39 })
40
41 tokenString, err := token.SignedString([]byte(PRIVATE_KEY))
42 if err != nil {
43 panic(err)
44 }
45
46 return tokenString
47}
48
49func detectFiles(directories []string) []string {
50 var files []string
51
52 for _, directory := range directories {
53 err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
54 if err != nil {
55 return err
56 }
57
58 // Skip directories
59 if !info.IsDir() {
60 files = append(files, path)
61 }
62
63 return nil
64 })
65
66 if err != nil {
67 panic(err)
68 }
69 }
70
71 return files
72}
73
74func main() {
75 // See purchase confirmation emails for details and service_account.json
76 const API_KEY = "YOUR_API_KEY"
77 const URL = "c-[YOUR_IDENTIFIER_FIRST_28_CHARACTERS].roguedb.dev"
78 encodedJWT := createJWT()
79
80 ////////////////////////////////////////////////////////
81 /////// Insert, Update, and Remove API Example ///////
82 ////////////////////////////////////////////////////////
83
84 connection, err := grpc.NewClient(URL, grpc.WithTransportCredentials(credentials.NewTLS(nil)))
85 defer connection.Close()
86 client := core_schemas.NewRogueDBClient(connection)
87
88 md := metadata.Pairs("authorization", fmt.Sprintf("Bearer: %s", encodedJWT))
89 ctx := metadata.NewOutgoingContext(context.Background(), md)
90
91 stream, err := client.Insert(ctx) // Insert API
92 // stream, err = client.Update(ctx) // Update API
93 // stream, err = client.Remove(ctx) // Remove API
94 if err != nil {
95 panic(err)
96 }
97
98 complete := make(chan struct{})
99
100 // No response is given for Insert, Update, and Remove
101 // Any errors get reported in status.
102 go func() {
103 for {
104 _, err := stream.Recv()
105 if err == io.EOF {
106 close(complete)
107 return
108 }
109
110 if err != nil {
111 panic(err)
112 }
113 }
114 }()
115
116 request := core_schemas.Insert{ApiKey: API_KEY}
117 // request := core_schemas.Update{ApiKey: API_KEY} // Update API
118 // request := core_schemas.Remove{ApiKey: API_KEY} // Remove API
119
120 // Insert, Update, and Remove are identical in use.
121 request.Messages = append(request.Messages, &anypb.Any{})
122 test := core_schemas.Test{Attribute1: 10}
123 err = request.Messages[0].MarshalFrom(&test)
124 if err != nil {
125 panic(err)
126 }
127
128 err = stream.Send(&request)
129 if err != nil {
130 panic(err)
131 }
132
133 err = stream.CloseSend() // Signal all queries sent. Otherwise, blocks.
134 if err != nil {
135 panic(err)
136 }
137
138 <-complete
139
140 //////////////////////////////////////
141 //////// Search API Example ////////
142 //////////////////////////////////////
143
144 // Example of a baisc index query.
145 searchStream, err := client.Search(ctx)
146 if err != nil {
147 panic(err)
148 }
149
150 // Search Query:
151 // Test.attribute1 >= 1 && Test.attribute2 >= 1 && Test.attribute3 >= true
152 // AND
153 // Test.attribute1 <= 10 && Test.attribute2 <= 10 && Test.attribute3 <= true
154 search := core_schemas.Search{ApiKey: API_KEY}
155 search.Queries = append(search.Queries, &core_schemas.Expression{})
156 basic := search.Queries[0].GetBasic()
157 basic.LogicalOperator = core_schemas.LogicalOperator_AND
158
159 basic.Comparisons = append(basic.Comparisons, core_schemas.ComparisonOperator_GREATER_EQUAL)
160 basic.Operands = append(basic.Operands, &anypb.Any{})
161 test = core_schemas.Test{Attribute1: 1, Attribute2: 1, Attribute3: true}
162 basic.Operands[0].MarshalFrom(&test)
163
164 basic.Comparisons = append(basic.Comparisons, core_schemas.ComparisonOperator_LESSER_EQUAL)
165 basic.Operands = append(basic.Operands, &anypb.Any{})
166 test = core_schemas.Test{Attribute1: 10, Attribute2: 10, Attribute3: true}
167 basic.Operands[1].MarshalFrom(&test)
168
169 complete = make(chan struct{})
170
171 // Multiple queries or large queries should be processed on a separate thread
172 // to prevent blocking the database.
173 go func() {
174 for {
175 response, err := stream.Recv()
176 if err == io.EOF {
177 close(complete)
178 return
179 }
180
181 if err != nil {
182 panic(err)
183 }
184
185 // Queries are zero-indexed. Partial results get sent
186 // and mapped to that index.
187 for _, message := range response.Results[0].Messages {
188 temp := core_schemas.Test{}
189 message.UnmarshalTo(&temp)
190 }
191
192 // Each response sends a list of the query ids finished
193 // with processing.
194 if slices.Contains(response.Finished, 0) {
195 }
196 }
197 }()
198
199 err = searchStream.Send(&search)
200 if err != nil {
201 panic(err)
202 }
203 err = searchStream.CloseSend() // Signal all queries sent. Otherwise, blocks.
204 if err != nil {
205 panic(err)
206 }
207 <-complete
208
209 // Example of a baisc non-indexed query.
210 search = core_schemas.Search{ApiKey: API_KEY}
211 search.Queries = nil
212
213 // Search Query:
214 // Test.attribute1 >= 1 && Test.attribute2 <= 10
215 search.Queries = append(search.Queries, &core_schemas.Expression{})
216 basic = search.Queries[0].GetBasic()
217 basic.LogicalOperator = core_schemas.LogicalOperator_AND
218 basic.Fields = append(basic.Fields, 1, 2) // Corresponds to field ids in test.proto
219
220 basic.Comparisons = append(basic.Comparisons, core_schemas.ComparisonOperator_GREATER_EQUAL)
221 basic.Operands = append(basic.Operands, &anypb.Any{})
222 test = core_schemas.Test{Attribute1: 1}
223 basic.Operands[0].MarshalFrom(&test)
224
225 basic.Comparisons = append(basic.Comparisons, core_schemas.ComparisonOperator_LESSER_EQUAL)
226 basic.Operands = append(basic.Operands, &anypb.Any{})
227 test = core_schemas.Test{Attribute2: 10}
228 basic.Operands[1].MarshalFrom(&test)
229
230 ///////////////////////////////////
231 //// Schema Change API Example ////
232 ///////////////////////////////////
233
234 subscribe := core_schemas.Subscribe{ApiKey: API_KEY}
235
236 // All proto files should be sent in a list of
237 // their contents. No modifications required.
238 for _, file := range detectFiles([]string{"path/to/proto/directory1", "path/to/proto/directory2"}) {
239 content, err := os.ReadFile(file)
240 if err != nil {
241 panic(err)
242 }
243 subscribe.Schemas = append(subscribe.Schemas, string(content))
244 }
245
246 // Any schemas excluded will have associated data deleted.
247 // Schema change failure results in no changes applied.
248 _, err = client.Subscribe(ctx, &subscribe)
249 if err != nil {
250 panic(err)
251 }
252}1syntax = "proto3";
2
3import "google/api/annotations.proto";
4import "google/protobuf/any.proto";
5
6package rogue.services;
7option go_package = "roguedb/core_schemas";
8
9service RogueDB
10{
11 // gRPC APIs
12 rpc insert(stream Insert) returns(stream Response) {}
13 rpc update(stream Update) returns(stream Response) {}
14 rpc remove(stream Remove) returns(stream Response) {}
15 rpc search(stream Search) returns(stream Response) {}
16 rpc subscribe(Subscribe) returns(Response) {
17 option (google.api.http) = {
18 post: "/rest/subscribe",
19 body: "*" }; }
20
21 // REST APIs
22 rpc rest_insert(Insert) returns(Response) {
23 option (google.api.http) = {
24 post: "/rest/insert",
25 body: "*" }; }
26
27 rpc rest_update(Update) returns(Response) {
28 option (google.api.http) = {
29 patch: "/rest/update",
30 body: "*" }; }
31
32 rpc rest_remove(Remove) returns(Response) {
33 option (google.api.http) = {
34 delete: "/rest/remove",
35 body: "*" }; }
36
37 rpc rest_search(Search) returns(stream Response) {
38 option (google.api.http) = {
39 get: "/rest/search",
40 body: "*" }; }
41
42 // NOTE: Only used for benchmarking.
43 rpc complete(Insert) returns(Response) {}
44}
45
46message Update
47{
48 string api_key = 1;
49 repeated google.protobuf.Any messages = 2;
50}
51
52message Remove
53{
54 string api_key = 1;
55 repeated google.protobuf.Any messages = 2;
56}
57
58message Insert
59{
60 string api_key = 1;
61 repeated google.protobuf.Any messages = 2;
62}
63
64message Search
65{
66 string api_key = 1;
67 repeated Expression queries = 2;
68}
69
70message Subscribe
71{
72 string api_key = 1;
73 repeated string schemas = 2;
74}
75
76message Response
77{
78 map<uint64, Messages> results = 1;
79 repeated uint64 finished = 2;
80}
81
82message Messages
83{
84 repeated google.protobuf.Any messages = 1;
85}
86
87enum LogicalOperator
88{
89 AND = 0;
90 OR = 1;
91}
92enum ComparisonOperator
93{
94 COMPARISON_OPERATOR_UNKNOWN = 0;
95 EQUAL = 1;
96 NOT_EQUAL = 2;
97 LESSER = 3;
98 LESSER_EQUAL = 4;
99 GREATER = 5;
100 GREATER_EQUAL = 6;
101}
102
103message Basic
104{
105 LogicalOperator logical_operator = 1;
106 repeated ComparisonOperator comparisons = 2;
107 repeated google.protobuf.Any operands = 3;
108 repeated int32 fields = 4; // Future feature.
109}
110
111// The below will be available in the upcoming release.
112// Tentative design outlined below.
113// Subject to change without notice.
114message Complex
115{
116 LogicalOperator logical_operator = 1;
117 repeated Expression expressions = 2;
118}
119
120message Expression
121{
122 oneof expression
123 {
124 Basic basic = 1;
125 Complex complex = 2;
126 Compositional compositional = 3;
127 }
128}
129
130message Mapping
131{
132 string identifier = 1;
133 repeated uint32 input_fields = 2;
134 repeated uint32 output_fields = 3;
135 Expression expression = 4;
136}
137
138message Compositional
139{
140 repeated Mapping mappings = 1;
141}
142
143enum Permission
144{
145 NO_PERMISSIONS = 0;
146 EXECUTE = 1;
147 WRITE = 2;
148 WRITE_EXECUTE = 3;
149 READ = 4;
150 READ_EXECUTE = 5;
151 READ_WRITE = 6;
152 READ_WRITE_EXECUTE = 7;
153}
154
155message User
156{
157 string api_key = 1; // index-1
158 map<string, Permission> permissions = 2;
159 bool admin = 3;
160}
161
162// Use this for testing a connection.
163message Test
164{
165 int32 attribute1 = 1; // index-1
166 int64 attribute2 = 2; // index-2
167 bool attribute3 = 3; // index-3
168 string attribute4 = 4;
169 uint32 attribute5 = 5;
170 uint64 attribute6 = 6;
171 double attribute7 = 7;
172 float attribute8 = 8;
173 sint32 attribute9 = 9;
174 sint64 attribute10 = 10;
175 fixed32 attribute11 = 11;
176 fixed64 attribute12 = 12;
177 sfixed32 attribute13 = 13;
178 sfixed64 attribute14 = 14;
179 bytes attribute15 = 15;
180 repeated int32 attribute16 = 16;
181 map<uint32, string> attribute17 = 17;
182}
Schema Management
RogueDB uses Protocol Buffers for schema definitions.
1syntax = "proto3";
2
3import "google/protobuf/timestamp.proto";
4
5// Every message must be indexed to use with the CRUD APIs.
6// This includes nested messages, excluding Google messages.
7// Test, User, and Unregistered are reserved for internal use.
8// Message names must be unique, but are case sensitive.
9message Aggregate
10{
11 string symbol = 1; // index-1 This part is not included for index order.
12 google.protobuf.Timestamp timestamp = 2; // index-3z Non-numerical characters allowed.
13 uint32 aggregate_period = 3; //index-2 No space between "//" and "index" is also valid.
14 float open = 4;
15 float close = 5;
16 float low = 6;
17 float high = 7;
18 uint32 volume = 8;
19}
20
21// NOTE: The order of evaluation for the index is symbol -> aggregate_period -> timestamp. See Index Performance below for general guidelines on the best way to create an index.
Special Note: Using Protocol Buffer's compiler protoc to generate messages in your target language allows validation locally before applying changes to the database.
Authentication and Identification
The complete example includes everything needed for secure connection with HTTPS using a JWT token for authentication and an API key for identification.
No additional setup is required.
Secure connections are the only allowed connections. Any connection not containing all three components are rejected by default.
User Roles and Permissions
The API key from the purchase confirmation email has all permissions by default and cannot be revoked.
RogueDB supports the creation of users and assigning of permissions per schema. Users have no permissions by default. The following permissions can be assigned:
- Read: Permission for the Search API.
- Write: Permission for the Create, Update, and Remove APIs.
- Execute: Permission for the Subscribe API.
- Admin: Grants all permissions and overrides any existing permission.
Manage users through the CRUD APIs:
1from roguedb.queries_pb2 import Insert
2from roguedb.user_pb2 import User, Permission
3
4# See queries.proto and user.proto for the full API.
5insert = Insert(api_key=api_key)
6user = User(api_key="CUSTOMER_MANAGED_KEY", admin=False)
7
8# The key is the schema and case sensitive.
9# Assign any combination of the three permissions.
10user.permissions["Test"] = Permission.READ_WRITE_EXECUTE
11insert.messages.add().Pack(user)
12
13def yield_insert():
14 yield insert
15
16response = roguedb.insert(
17 yield_insert(),
18 metadata=[("authorization", f"Bearer {encoded_jwt}")])
19
20response = requests.post(
21 f"https://{url}/rest/insert",
22 headers=headers,
23 data={
24 "api_key" : api_key,
25 "messages" : [
26 {
27 "@type": "type.googleapis.com/rogue.services.User",
28 "api_key" : "USER_MANAGED_API_KEY",
29 "permissions": { "Test": "READ_WRITE_EXECUTE" }
30 }
31 ]
32 })1// #include "roguedb/queries.pb.h"
2// #include "roguedb/user.pb.h"
3
4Insert insert{};
5*insert.mutable_credentials() = credentials;
6
7Rogue::Services::User user{};
8user.set_api_key("CUSTOMER_MANAGED_KEY");
9user.set_admin(false);
10(*user.mutable_permissions())["user"] = Rogue::Services::Permission::READ;
11
12*insert.add_messages() = std::move(user);
13
14grpc::Context context{};
15std::unique_ptr<grpc::ClientReaderWriter<rogue::services::Insert, rogue::services::Response>> stream{
16 writeStub->insert(&context) };
17stream->Write(query);Index Performance
- Partitioning: Example - Use stock symbol over timestamp as the first index.
- RogueDB optimizes insertion for "bucketed" data versus incremental based index strategies.
- Granularity: Example - Use stock symbol over aggregate period as the first index.
- RogueDB optimizes for multi-field indexing with negligible effects on performance.
- Minimal: Example - Use the smallest reasonable number of fields to form a unique index.
- RogueDB optimizes memory footprint with internal metadata utilizing the subset of fields identified for indexing.
- Order: Arrange categorical fields first and incremental fields last. Only include fields used in all indexed search queries.
Practical use cases means swapping the order typically used in other databases for the fields being indexed. Categorization fields go first. Incrementing fields go last. By doing this, RogueDB can leverage the natural bucketing of data to parallelize operations.
Permission Propogation
Permissions flow downwards from the parent schema. For instance, consider the following example:
1syntax = "proto3";
2
3message MedicalRecords
4{
5 // Stores HIPPA related items of a customer.
6}
7
8message FinancialRecords
9{
10 // Stores financial related items of a customer.
11}
12
13message Customer
14{
15 MedicalRecords medical = 1;
16 FinancialRecords financial = 2;
17 // Stores additional personally identifiable information (PII).
18}Now presume we have a user, Foo, with the following permissions:
- MedicalRecords: No permissions by default.
- FinancialRecords: Read permissions.
- Customer: Write and Read permissions.
Due to the composition of Customer including MedicalRecords and FinancialRecords, Foo can do the following when using Customer as the record:
- MedicalRecords: No permissions by default => Write and Read permissions.
- FinancialRecords: Read permissions => Write and Read permissions.
Careful consideration needs to be made when designing schemas to ensure permissions do not propagate in unexpected ways. As such, the Execute permission and Admin role should be given sparingly, if at all, and under additional security mechanisms.
Design Suggestion
Creation of a unique ID per customer with storage of MedicalRecords, FinancialRecords, and PII as separate schemas in the database prevents propagation of permissions. This further streamlines efficient storage and retrieval of data to only the required components versus the entirety of a record. UUIDs are an excellent mechanism for unique identifiers.
Consider the following as a more secure alternative:
1syntax = "proto3";
2
3message MedicalRecords
4{
5 string customer_id = 1;
6 // Stores HIPPA related items of a customer.
7}
8
9message FinancialRecords
10{
11 string customer_id = 1;
12 // Stores financial related items of a customer.
13}
14
15message PII
16{
17 string customer_id = 1;
18 // Stores additional personally identifiable information (PII).
19}Special Considerations
- Duplicate Data: RogueDB automatically removes all duplicate data based on the index.
- Highly Nested Messages: Consider separating into smaller individual components.
- RogueDB does not intend to support queries that allow specification into nested message fields. If the field's indexing strategy does not cover intended use case, the message will need to be separated.
- Protobuf serialization becomes slower for deeply nested messages due to the recursive nature. Simpler messages means faster performance.
- CRUD Operations: Round robin scheduling of CRUD operations.
- RogueDB utilitizes batching of operations to increase throughput.
- Order of execution: Create, Update, Remove, Basic Queries, Complex Queries, Compositional Queries
- Order Guarantees:
- Ordering is not guaranteed between CRUD operations or requests.
- Duplicate messages based on index in a request only uses the first message.
