Skip to content

Commit a5d247e

Browse files
authored
*: Support API v2 no prefix (#518)
Signed-off-by: Ping Yu <[email protected]>
1 parent 1c1b2e0 commit a5d247e

File tree

6 files changed

+128
-26
lines changed

6 files changed

+128
-26
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
env:
6161
CARGO_INCREMENTAL: 0
6262
NEXTEST_PROFILE: ci
63-
TIKV_VERSION: v8.5.1
63+
TIKV_VERSION: v8.5.5
6464
RUST_LOG: info
6565
runs-on: ubuntu-latest
6666
steps:

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ prometheus = ["prometheus/push", "prometheus/process"]
1414
# Enable integration tests with a running TiKV and PD instance.
1515
# Use $PD_ADDRS, comma separated, to set the addresses the tests use.
1616
integration-tests = []
17+
apiv2-no-prefix = []
1718

1819
[lib]
1920
name = "tikv_client"

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ export RUSTFLAGS=-Dwarnings
55
export PD_ADDRS ?= 127.0.0.1:2379
66
export MULTI_REGION ?= 1
77

8-
ALL_FEATURES := integration-tests
8+
ALL_FEATURES := integration-tests apiv2-no-prefix
99

1010
NEXTEST_ARGS := --config-file $(shell pwd)/config/nextest.toml
1111

12-
INTEGRATION_TEST_ARGS := --features "integration-tests" --test-threads 1
12+
INTEGRATION_TEST_ARGS := --features "integration-tests apiv2-no-prefix" --test-threads 1
1313

1414
RUN_INTEGRATION_TEST := cargo nextest run ${NEXTEST_ARGS} --all ${INTEGRATION_TEST_ARGS}
1515

src/raw/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ impl Client<PdRpcClient> {
112112
let rpc =
113113
Arc::new(PdRpcClient::connect(&pd_endpoints, config.clone(), enable_codec).await?);
114114
let keyspace = match config.keyspace {
115-
Some(keyspace) => {
116-
let keyspace = rpc.load_keyspace(&keyspace).await?;
115+
Some(name) => {
116+
let keyspace = rpc.load_keyspace(&name).await?;
117117
Keyspace::Enable {
118118
keyspace_id: keyspace.id,
119119
}

src/request/keyspace.rs

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,16 @@ pub const KEYSPACE_PREFIX_LEN: usize = 4;
1515
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1616
pub enum Keyspace {
1717
Disable,
18-
Enable { keyspace_id: u32 },
18+
Enable {
19+
keyspace_id: u32,
20+
},
21+
/// Use API V2 without adding or removing the API V2 keyspace/key-mode prefix.
22+
///
23+
/// This mode is intended for **server-side embedding** use cases (e.g. embedding this client in
24+
/// `tikv-server`) where keys are already in API V2 "logical key bytes" form and must be passed
25+
/// through unchanged.
26+
#[cfg(feature = "apiv2-no-prefix")]
27+
ApiV2NoPrefix,
1928
}
2029

2130
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -29,6 +38,8 @@ impl Keyspace {
2938
match self {
3039
Keyspace::Disable => kvrpcpb::ApiVersion::V1,
3140
Keyspace::Enable { .. } => kvrpcpb::ApiVersion::V2,
41+
#[cfg(feature = "apiv2-no-prefix")]
42+
Keyspace::ApiV2NoPrefix => kvrpcpb::ApiVersion::V2,
3243
}
3344
}
3445
}
@@ -43,12 +54,10 @@ pub trait TruncateKeyspace {
4354

4455
impl EncodeKeyspace for Key {
4556
fn encode_keyspace(mut self, keyspace: Keyspace, key_mode: KeyMode) -> Self {
46-
let prefix = match keyspace {
47-
Keyspace::Disable => {
48-
return self;
49-
}
50-
Keyspace::Enable { keyspace_id } => keyspace_prefix(keyspace_id, key_mode),
57+
let Keyspace::Enable { keyspace_id } = keyspace else {
58+
return self;
5159
};
60+
let prefix = keyspace_prefix(keyspace_id, key_mode);
5261

5362
prepend_bytes(&mut self.0, &prefix);
5463

@@ -68,28 +77,25 @@ impl EncodeKeyspace for BoundRange {
6877
self.from = match self.from {
6978
Bound::Included(key) => Bound::Included(key.encode_keyspace(keyspace, key_mode)),
7079
Bound::Excluded(key) => Bound::Excluded(key.encode_keyspace(keyspace, key_mode)),
71-
Bound::Unbounded => {
72-
let key = Key::from(vec![]);
73-
Bound::Included(key.encode_keyspace(keyspace, key_mode))
74-
}
80+
Bound::Unbounded => Bound::Included(Key::EMPTY.encode_keyspace(keyspace, key_mode)),
7581
};
82+
7683
self.to = match self.to {
7784
Bound::Included(key) if !key.is_empty() => {
7885
Bound::Included(key.encode_keyspace(keyspace, key_mode))
7986
}
8087
Bound::Excluded(key) if !key.is_empty() => {
8188
Bound::Excluded(key.encode_keyspace(keyspace, key_mode))
8289
}
83-
_ => {
84-
let key = Key::from(vec![]);
85-
let keyspace = match keyspace {
86-
Keyspace::Disable => Keyspace::Disable,
87-
Keyspace::Enable { keyspace_id } => Keyspace::Enable {
90+
_ => match keyspace {
91+
Keyspace::Enable { keyspace_id } => Bound::Excluded(Key::EMPTY.encode_keyspace(
92+
Keyspace::Enable {
8893
keyspace_id: keyspace_id + 1,
8994
},
90-
};
91-
Bound::Excluded(key.encode_keyspace(keyspace, key_mode))
92-
}
95+
key_mode,
96+
)),
97+
_ => Bound::Excluded(Key::EMPTY),
98+
},
9399
};
94100
self
95101
}
@@ -106,7 +112,7 @@ impl EncodeKeyspace for Mutation {
106112

107113
impl TruncateKeyspace for Key {
108114
fn truncate_keyspace(mut self, keyspace: Keyspace) -> Self {
109-
if let Keyspace::Disable = keyspace {
115+
if !matches!(keyspace, Keyspace::Enable { .. }) {
110116
return self;
111117
}
112118

@@ -133,6 +139,9 @@ impl TruncateKeyspace for Range<Key> {
133139

134140
impl TruncateKeyspace for Vec<Range<Key>> {
135141
fn truncate_keyspace(mut self, keyspace: Keyspace) -> Self {
142+
if !matches!(keyspace, Keyspace::Enable { .. }) {
143+
return self;
144+
}
136145
for range in &mut self {
137146
take_mut::take(range, |range| range.truncate_keyspace(keyspace));
138147
}
@@ -142,6 +151,9 @@ impl TruncateKeyspace for Vec<Range<Key>> {
142151

143152
impl TruncateKeyspace for Vec<KvPair> {
144153
fn truncate_keyspace(mut self, keyspace: Keyspace) -> Self {
154+
if !matches!(keyspace, Keyspace::Enable { .. }) {
155+
return self;
156+
}
145157
for pair in &mut self {
146158
take_mut::take(pair, |pair| pair.truncate_keyspace(keyspace));
147159
}
@@ -151,6 +163,9 @@ impl TruncateKeyspace for Vec<KvPair> {
151163

152164
impl TruncateKeyspace for Vec<crate::proto::kvrpcpb::LockInfo> {
153165
fn truncate_keyspace(mut self, keyspace: Keyspace) -> Self {
166+
if !matches!(keyspace, Keyspace::Enable { .. }) {
167+
return self;
168+
}
154169
for lock in &mut self {
155170
take_mut::take(&mut lock.key, |key| {
156171
Key::from(key).truncate_keyspace(keyspace).into()
@@ -277,4 +292,66 @@ mod tests {
277292
let expected_key = Key::from(vec![0xBE, 0xEF]);
278293
assert_eq!(key.truncate_keyspace(keyspace), expected_key);
279294
}
295+
296+
#[cfg(feature = "apiv2-no-prefix")]
297+
#[test]
298+
fn test_apiv2_no_prefix_api_version() {
299+
assert_eq!(
300+
Keyspace::ApiV2NoPrefix.api_version(),
301+
kvrpcpb::ApiVersion::V2
302+
);
303+
}
304+
305+
#[cfg(feature = "apiv2-no-prefix")]
306+
#[test]
307+
fn test_apiv2_no_prefix_encode_is_noop() {
308+
let keyspace = Keyspace::ApiV2NoPrefix;
309+
let key_mode = KeyMode::Txn;
310+
311+
let key = Key::from(vec![b'x', 0, 0, 0, b'k']);
312+
assert_eq!(key.clone().encode_keyspace(keyspace, key_mode), key);
313+
314+
let pair = KvPair(Key::from(vec![b'x', 0, 0, 0, b'k']), vec![b'v']);
315+
assert_eq!(pair.clone().encode_keyspace(keyspace, key_mode), pair);
316+
317+
let bound: BoundRange =
318+
(Key::from(vec![b'x', 0, 0, 0, b'a'])..Key::from(vec![b'x', 0, 0, 0, b'b'])).into();
319+
assert_eq!(bound.clone().encode_keyspace(keyspace, key_mode), bound);
320+
321+
let mutation = Mutation::Put(Key::from(vec![b'x', 0, 0, 0, b'k']), vec![1, 2, 3]);
322+
assert_eq!(
323+
mutation.clone().encode_keyspace(keyspace, key_mode),
324+
mutation
325+
);
326+
}
327+
328+
#[cfg(feature = "apiv2-no-prefix")]
329+
#[test]
330+
fn test_apiv2_no_prefix_truncate_is_noop() {
331+
let keyspace = Keyspace::ApiV2NoPrefix;
332+
333+
let key = Key::from(vec![b'x', 0, 0, 0, b'k']);
334+
assert_eq!(key.clone().truncate_keyspace(keyspace), key);
335+
336+
let pair = KvPair(Key::from(vec![b'x', 0, 0, 0, b'k']), vec![b'v']);
337+
assert_eq!(pair.clone().truncate_keyspace(keyspace), pair);
338+
339+
let range = Range {
340+
start: Key::from(vec![b'x', 0, 0, 0, b'a']),
341+
end: Key::from(vec![b'x', 0, 0, 0, b'b']),
342+
};
343+
assert_eq!(range.clone().truncate_keyspace(keyspace), range);
344+
345+
let pairs = vec![pair];
346+
assert_eq!(pairs.clone().truncate_keyspace(keyspace), pairs);
347+
348+
let lock = crate::proto::kvrpcpb::LockInfo {
349+
key: vec![b'x', 0, 0, 0, b'k'],
350+
primary_lock: vec![b'x', 0, 0, 0, b'p'],
351+
secondaries: vec![vec![b'x', 0, 0, 0, b's']],
352+
..Default::default()
353+
};
354+
let locks = vec![lock];
355+
assert_eq!(locks.clone().truncate_keyspace(keyspace), locks);
356+
}
280357
}

src/transaction/client.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ impl Client {
110110
let pd_endpoints: Vec<String> = pd_endpoints.into_iter().map(Into::into).collect();
111111
let pd = Arc::new(PdRpcClient::connect(&pd_endpoints, config.clone(), true).await?);
112112
let keyspace = match config.keyspace {
113-
Some(keyspace) => {
114-
let keyspace = pd.load_keyspace(&keyspace).await?;
113+
Some(name) => {
114+
let keyspace = pd.load_keyspace(&name).await?;
115115
Keyspace::Enable {
116116
keyspace_id: keyspace.id,
117117
}
@@ -121,6 +121,30 @@ impl Client {
121121
Ok(Client { pd, keyspace })
122122
}
123123

124+
/// Create a transactional [`Client`] that uses API V2 without adding or removing any API V2
125+
/// keyspace/key-mode prefix, with a custom configuration.
126+
///
127+
/// This is intended for **server-side embedding** use cases. `config.keyspace` must be unset.
128+
#[cfg(feature = "apiv2-no-prefix")]
129+
pub async fn new_with_config_api_v2_no_prefix<S: Into<String>>(
130+
pd_endpoints: Vec<S>,
131+
config: Config,
132+
) -> Result<Client> {
133+
if config.keyspace.is_some() {
134+
return Err(crate::Error::StringError(
135+
"config.keyspace must be unset when using api-v2-no-prefix mode".to_owned(),
136+
));
137+
}
138+
139+
debug!("creating new transactional client (api-v2-no-prefix)");
140+
let pd_endpoints: Vec<String> = pd_endpoints.into_iter().map(Into::into).collect();
141+
let pd = Arc::new(PdRpcClient::connect(&pd_endpoints, config.clone(), true).await?);
142+
Ok(Client {
143+
pd,
144+
keyspace: Keyspace::ApiV2NoPrefix,
145+
})
146+
}
147+
124148
/// Creates a new optimistic [`Transaction`].
125149
///
126150
/// Use the transaction to issue requests like [`get`](Transaction::get) or

0 commit comments

Comments
 (0)