1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
impl TieredStore {
/// Query TDG records by git commit SHA (Sprint 65 Phase 3)
///
/// Searches both warm and cold storage for records matching the specified commit.
/// Supports full SHA, short SHA (7 chars), or git tags.
pub async fn get_by_commit(&self, commit_ref: &str) -> Result<Vec<FullTdgRecord>> {
let mut results = Vec::new();
// Search warm storage
for item in self.warm_backend.iter()? {
let (_key, value) = item?;
let decompressed = decompress_size_prepended(&value)?;
// NOTE: Using serde_json instead of bincode (see store() method)
if let Ok(record) = serde_json::from_slice::<FullTdgRecord>(&decompressed) {
if let Some(git_ctx) = &record.git_context {
if git_ctx.commit_sha == commit_ref
|| git_ctx.commit_sha_short == commit_ref
|| git_ctx.tags.contains(&commit_ref.to_string())
{
results.push(record);
}
}
}
}
// Search cold storage
for item in self.cold_backend.iter()? {
let (_key, value) = item?;
// NOTE: Using serde_json instead of bincode (see store() method)
if let Ok(record) = serde_json::from_slice::<FullTdgRecord>(&value) {
if let Some(git_ctx) = &record.git_context {
if git_ctx.commit_sha == commit_ref
|| git_ctx.commit_sha_short == commit_ref
|| git_ctx.tags.contains(&commit_ref.to_string())
{
results.push(record);
}
}
}
}
Ok(results)
}
/// Query TDG records in a git commit range (Sprint 65 Phase 3)
///
/// Returns all records with git context, allowing the caller to filter by commit range.
/// The actual git range resolution happens in the handler (using git2).
pub async fn get_all_with_git_context(&self) -> Result<Vec<FullTdgRecord>> {
let mut results = Vec::new();
// Search warm storage
for item in self.warm_backend.iter()? {
let (_key, value) = item?;
let decompressed = decompress_size_prepended(&value)?;
// NOTE: Using serde_json instead of bincode (see store() method)
if let Ok(record) = serde_json::from_slice::<FullTdgRecord>(&decompressed) {
if record.git_context.is_some() {
results.push(record);
}
}
}
// Search cold storage
for item in self.cold_backend.iter()? {
let (_key, value) = item?;
// NOTE: Using serde_json instead of bincode (see store() method)
if let Ok(record) = serde_json::from_slice::<FullTdgRecord>(&value) {
if record.git_context.is_some() {
results.push(record);
}
}
}
// Sort by commit timestamp (newest first)
results.sort_by(|a, b| {
let a_time = a
.git_context
.as_ref()
.map(|g| g.commit_timestamp.timestamp())
.unwrap_or(0);
let b_time = b
.git_context
.as_ref()
.map(|g| g.commit_timestamp.timestamp())
.unwrap_or(0);
b_time.cmp(&a_time) // Reverse order (newest first)
});
Ok(results)
}
/// Query TDG records by file path (Sprint 65 Phase 3)
///
/// Filters records to only those matching the specified file path.
pub async fn get_by_path(&self, target_path: &Path) -> Result<Vec<FullTdgRecord>> {
let mut results = Vec::new();
// Search warm storage
for item in self.warm_backend.iter()? {
let (_key, value) = item?;
let decompressed = decompress_size_prepended(&value)?;
// NOTE: Using serde_json instead of bincode (see store() method)
if let Ok(record) = serde_json::from_slice::<FullTdgRecord>(&decompressed) {
if record.identity.path == target_path {
results.push(record);
}
}
}
// Search cold storage
for item in self.cold_backend.iter()? {
let (_key, value) = item?;
// NOTE: Using serde_json instead of bincode (see store() method)
if let Ok(record) = serde_json::from_slice::<FullTdgRecord>(&value) {
if record.identity.path == target_path {
results.push(record);
}
}
}
// Sort by commit timestamp (newest first)
results.sort_by(|a, b| {
let a_time = a
.git_context
.as_ref()
.map(|g| g.commit_timestamp.timestamp())
.unwrap_or(0);
let b_time = b
.git_context
.as_ref()
.map(|g| g.commit_timestamp.timestamp())
.unwrap_or(0);
b_time.cmp(&a_time) // Reverse order (newest first)
});
Ok(results)
}
}
/// Factory for creating tiered storage instances
pub struct TieredStorageFactory;
impl TieredStorageFactory {
/// Create storage instance with default configuration
pub fn create_default() -> Result<TieredStore> {
let home_dir = dirs::home_dir().ok_or_else(|| anyhow!("Could not find home directory"))?;
TieredStore::new(home_dir)
}
/// Create storage instance at specific path
pub fn create_at_path(path: impl AsRef<Path>) -> Result<TieredStore> {
TieredStore::new(path)
}
/// Create in-memory storage for testing
#[must_use]
pub fn create_in_memory() -> TieredStore {
TieredStore::in_memory()
}
}