Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ itoa = "1"
libc = "0.2"
memchr = "2"
pyo3 = { version = "0.28", features = ["extension-module"] }
log = "0.4"
pyo3-log = "0.13"
50 changes: 47 additions & 3 deletions meta_memcache_socket.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Any, Optional, Tuple, Union
import socket
from typing import Any, Optional, Tuple, Union

RESPONSE_VALUE: int # 1 - VALUE (VA)
RESPONSE_SUCCESS: int # 2 - SUCCESS (OK or HD)
Expand Down Expand Up @@ -151,7 +151,6 @@ class ResponseFlags:
opaque: Optional[bytes] = None,
) -> None: ...
def __str__(self) -> str: ...

@staticmethod
def from_success_header(header: bytes) -> "ResponseFlags":
"""Parse response flags from a success (HD) header."""
Expand Down Expand Up @@ -305,4 +304,49 @@ class MemcacheSocket:
def close(self) -> None: ...
def sendall(self, data: bytes, with_noop: bool) -> None: ...
def get_response(self) -> Union[Value, Success, Miss, NotStored, Conflict]: ...
def get_value(self, size: int) -> bytes: ...
# send_meta_* methods (for pipelining — send only, read later with get_response())
# Mutations automatically inject NOOP when no_reply is set in request_flags.
def send_meta_get(
self,
key: bytes,
request_flags: Optional[RequestFlags] = None,
) -> None: ...
def send_meta_set(
self,
key: bytes,
value: bytes,
request_flags: Optional[RequestFlags] = None,
) -> None: ...
def send_meta_delete(
self,
key: bytes,
request_flags: Optional[RequestFlags] = None,
) -> None: ...
def send_meta_arithmetic(
self,
key: bytes,
request_flags: Optional[RequestFlags] = None,
) -> None: ...

# meta_* methods (blocking — send + recv in one call)
def meta_get(
self,
key: bytes,
request_flags: Optional[RequestFlags] = None,
) -> Union[Value, Success, Miss, NotStored, Conflict]: ...
def meta_set(
self,
key: bytes,
value: bytes,
request_flags: Optional[RequestFlags] = None,
) -> Union[Value, Success, Miss, NotStored, Conflict]: ...
def meta_delete(
self,
key: bytes,
request_flags: Optional[RequestFlags] = None,
) -> Union[Value, Success, Miss, NotStored, Conflict]: ...
def meta_arithmetic(
self,
key: bytes,
request_flags: Optional[RequestFlags] = None,
) -> Union[Value, Success, Miss, NotStored, Conflict]: ...
19 changes: 14 additions & 5 deletions src/impl_build_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ use crate::RequestFlags;
const MAX_KEY_LEN: usize = 250;
const MAX_BIN_KEY_LEN: usize = 187; // 250 * 3 / 4 due to b64 encoding

pub struct BuiltCmd {
pub buf: Vec<u8>,
pub no_reply: bool,
}

pub fn impl_build_cmd(
cmd: &[u8],
key: &[u8],
size: Option<u32>,
request_flags: Option<&RequestFlags>,
legacy_size_format: bool,
) -> Option<Vec<u8>> {
allow_no_reply_flag: bool,
) -> Option<BuiltCmd> {
if key.is_empty() || key.len() >= MAX_KEY_LEN {
return None;
}
Expand Down Expand Up @@ -55,10 +61,13 @@ pub fn impl_build_cmd(
buf.push(b' ');
buf.push(b'b');
}
if let Some(request_flags) = request_flags {
request_flags.push_bytes(&mut buf);
}
let no_reply = if let Some(request_flags) = request_flags {
request_flags.push_bytes(&mut buf, allow_no_reply_flag);
allow_no_reply_flag && request_flags.is_no_reply()
} else {
false
};
buf.push(b'\r');
buf.push(b'\n');
Some(buf)
Some(BuiltCmd { buf, no_reply })
}
52 changes: 28 additions & 24 deletions src/impl_build_cmd_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ mod tests {
Some(b'A'), // mode (APPEND)
);

let result = impl_build_cmd(cmd, key, None, Some(&request_flags), false).unwrap();
let string = String::from_utf8_lossy(&result);
let built = impl_build_cmd(cmd, key, None, Some(&request_flags), false, true).unwrap();
let string = String::from_utf8_lossy(&built.buf);
println!("{:?}", string);
assert!(built.no_reply);
assert_eq!(
result,
built.buf,
b"mg key q f c v t s l h k u I T111 R222 N333 F444 J555 D666 C777 Oopaque MA\r\n"
);
}
Expand Down Expand Up @@ -66,10 +67,11 @@ mod tests {
None, // mode
);

let result = impl_build_cmd(cmd, key, None, Some(&request_flags), false).unwrap();
let string = String::from_utf8_lossy(&result);
let built = impl_build_cmd(cmd, key, None, Some(&request_flags), false, true).unwrap();
let string = String::from_utf8_lossy(&built.buf);
println!("{:?}", string);
assert_eq!(result, b"mg key\r\n");
assert!(!built.no_reply);
assert_eq!(built.buf, b"mg key\r\n");
}

#[test]
Expand Down Expand Up @@ -99,10 +101,10 @@ mod tests {
None, // mode
);

let result = impl_build_cmd(cmd, key, None, Some(&request_flags), false).unwrap();
let string = String::from_utf8_lossy(&result);
let built = impl_build_cmd(cmd, key, None, Some(&request_flags), false, true).unwrap();
let string = String::from_utf8_lossy(&built.buf);
println!("{:?}", string);
assert_eq!(result, b"mg S2V5X3dpdGhfYmluYXJ5AA== b\r\n");
assert_eq!(built.buf, b"mg S2V5X3dpdGhfYmluYXJ5AA== b\r\n");
}

#[test]
Expand Down Expand Up @@ -132,50 +134,50 @@ mod tests {
None, // mode
);

let result = impl_build_cmd(cmd, key, None, Some(&request_flags), false).unwrap();
let string = String::from_utf8_lossy(&result);
let built = impl_build_cmd(cmd, key, None, Some(&request_flags), false, true).unwrap();
let string = String::from_utf8_lossy(&built.buf);
println!("{:?}", string);
assert_eq!(result, b"mg S2V5IHdpdGggc3BhY2Vz b\r\n");
assert_eq!(built.buf, b"mg S2V5IHdpdGggc3BhY2Vz b\r\n");
}

#[test]
fn test_empty_key_rejected() {
assert!(impl_build_cmd(b"mg", b"", None, None, false).is_none());
assert!(impl_build_cmd(b"mg", b"", None, None, false, true).is_none());
}

#[test]
fn test_key_at_max_length() {
// 249 bytes is OK (< 250)
let key = &vec![b'X'; 249];
assert!(impl_build_cmd(b"mg", key, None, None, false).is_some());
assert!(impl_build_cmd(b"mg", key, None, None, false, true).is_some());
}

#[test]
fn test_key_at_exact_max_length() {
// 250 bytes is rejected (>= MAX_KEY_LEN)
let key = &vec![b'X'; 250];
assert!(impl_build_cmd(b"mg", key, None, None, false).is_none());
assert!(impl_build_cmd(b"mg", key, None, None, false, true).is_none());
}

#[test]
fn test_binary_key_at_max_length() {
// 186 binary bytes is OK (< 187 = MAX_BIN_KEY_LEN)
let key = &vec![0x00u8; 186];
assert!(impl_build_cmd(b"mg", key, None, None, false).is_some());
assert!(impl_build_cmd(b"mg", key, None, None, false, true).is_some());
}

#[test]
fn test_binary_key_at_exact_max_length() {
// 187 binary bytes is rejected (>= MAX_BIN_KEY_LEN)
let key = &vec![0x00u8; 187];
assert!(impl_build_cmd(b"mg", key, None, None, false).is_none());
assert!(impl_build_cmd(b"mg", key, None, None, false, true).is_none());
}

#[test]
fn test_impl_build_cmd_large_key() {
let cmd = b"mg";
let key = &vec![b'X'; 250];
assert!(impl_build_cmd(cmd, key, None, None, false).is_none());
assert!(impl_build_cmd(cmd, key, None, None, false, true).is_none());
}

#[test]
Expand Down Expand Up @@ -206,10 +208,11 @@ mod tests {
None, // mode
);

let result = impl_build_cmd(cmd, key, Some(size), Some(&request_flags), false).unwrap();
let string = String::from_utf8_lossy(&result);
let built =
impl_build_cmd(cmd, key, Some(size), Some(&request_flags), false, true).unwrap();
let string = String::from_utf8_lossy(&built.buf);
println!("{:?}", string);
assert_eq!(result, b"ms key 123 T111\r\n");
assert_eq!(built.buf, b"ms key 123 T111\r\n");
}

#[test]
Expand All @@ -218,9 +221,10 @@ mod tests {
let key = b"key";
let size = 123;

let result = impl_build_cmd(cmd, key, Some(size), None, true).unwrap();
let string = String::from_utf8_lossy(&result);
let built = impl_build_cmd(cmd, key, Some(size), None, true, true).unwrap();
let string = String::from_utf8_lossy(&built.buf);
println!("{:?}", string);
assert_eq!(result, b"ms key S123\r\n");
assert!(!built.no_reply);
assert_eq!(built.buf, b"ms key S123\r\n");
}
}
Loading
Loading