1use crate::aggregate_rows::Cell;
6use crate::planner::{family_id, pids_of};
7use crate::{access, normalize, reconstruct, Expr, FieldPred, Json, QueryError, QuerySnapshot};
8use rushdb_lmpg::reader::TryRead;
9use rushdb_types::value::ID_FIELD;
10use serde_json::Map;
11
12pub struct PointFields(Vec<(String, Source)>);
27
28enum Source {
30 Id,
32 Pids(Vec<u64>),
34}
35
36impl PointFields {
37 pub fn resolve<'n>(
44 snap: &QuerySnapshot,
45 tenant: rushdb_types::ids::TenantId,
46 names: impl IntoIterator<Item = &'n str>,
47 ) -> Result<Option<Self>, QueryError> {
48 Self::resolve_in(snap, tenant, &[], names)
49 }
50
51 pub fn resolve_in<'n>(
57 snap: &QuerySnapshot,
58 tenant: rushdb_types::ids::TenantId,
59 labels: &[String],
60 names: impl IntoIterator<Item = &'n str>,
61 ) -> Result<Option<Self>, QueryError> {
62 let mut fields = Vec::new();
63 for name in names {
64 let source = if normalize(name) == ID_FIELD {
65 Source::Id
66 } else {
67 let Some(pids) = scalar_pids(snap, tenant, labels, name)? else {
68 return Ok(None);
69 };
70 Source::Pids(pids)
71 };
72 fields.push((name.to_owned(), source));
73 }
74 Ok(Some(Self(fields)))
75 }
76
77 pub(super) fn for_predicate(
80 snap: &QuerySnapshot,
81 tenant: rushdb_types::ids::TenantId,
82 labels: &[String],
83 predicate: &Expr,
84 ) -> Result<Option<Self>, QueryError> {
85 let mut names = std::collections::BTreeSet::new();
86 if !predicate_fields(predicate, &mut names) {
87 return Ok(None);
88 }
89 Self::resolve_in(snap, tenant, labels, names.iter().map(String::as_str))
90 }
91
92 pub fn row(
98 &self,
99 snap: &QuerySnapshot,
100 tenant: rushdb_types::ids::TenantId,
101 ord: u64,
102 ) -> Result<Map<String, Json>, QueryError> {
103 let mut row = Map::new();
104 for (field, source) in &self.0 {
105 match source {
106 Source::Id => {
107 if let Some(id) = record_id(snap, tenant, ord)? {
108 row.insert(field.clone(), Json::String(id));
109 }
110 }
111 Source::Pids(pids) => {
112 if let Some(occ) = held(snap, tenant, ord, pids)? {
113 row.insert(
114 field.clone(),
115 reconstruct::decode_value(occ.value, &occ.payload)?,
116 );
117 }
118 }
119 }
120 }
121 Ok(row)
122 }
123
124 pub(crate) fn cells(
127 &self,
128 snap: &QuerySnapshot,
129 tenant: rushdb_types::ids::TenantId,
130 ord: u64,
131 out: &mut Vec<Option<Cell>>,
132 ) -> Result<(), QueryError> {
133 out.clear();
134 for (_, source) in &self.0 {
135 out.push(match source {
136 Source::Id => record_id(snap, tenant, ord)?.map(Cell::Str),
137 Source::Pids(pids) => match held(snap, tenant, ord, pids)? {
138 Some(occ) => Some(Cell::decode(occ.value, &occ.payload)?),
139 None => None,
140 },
141 });
142 }
143 Ok(())
144 }
145}
146
147fn record_id(
149 snap: &QuerySnapshot,
150 tenant: rushdb_types::ids::TenantId,
151 ord: u64,
152) -> Result<Option<String>, QueryError> {
153 Ok(access::record_by_ord(snap, tenant, ord)?
154 .map(|header| reconstruct::id_string(&header.record_id)))
155}
156
157fn held(
159 snap: &QuerySnapshot,
160 tenant: rushdb_types::ids::TenantId,
161 ord: u64,
162 pids: &[u64],
163) -> Result<Option<access::Occurrence>, QueryError> {
164 for &pid in pids {
165 if let Some(occ) = access::occurrence(
166 snap,
167 tenant,
168 rushdb_types::ids::OwnerKind::Record,
169 ord,
170 pid,
171 0,
172 )? {
173 return Ok(Some(occ));
174 }
175 }
176 Ok(None)
177}
178
179pub(super) fn predicate_fields(expr: &Expr, out: &mut std::collections::BTreeSet<String>) -> bool {
183 match expr {
184 Expr::And(items) | Expr::Or(items) | Expr::Nor(items) => {
185 items.iter().all(|item| predicate_fields(item, out))
186 }
187 Expr::Xor(a, b) => predicate_fields(a, out) && predicate_fields(b, out),
188 Expr::Not(inner) => predicate_fields(inner, out),
189 Expr::Related(_) => true,
190 Expr::Field(FieldPred::Id(_)) => false,
191 Expr::Field(
192 FieldPred::Eq(name, _)
193 | FieldPred::Ne(name, _)
194 | FieldPred::Gt(name, _)
195 | FieldPred::Gte(name, _)
196 | FieldPred::Lt(name, _)
197 | FieldPred::Lte(name, _)
198 | FieldPred::Contains(name, _)
199 | FieldPred::StartsWith(name, _)
200 | FieldPred::EndsWith(name, _)
201 | FieldPred::Exists(name, _)
202 | FieldPred::IsNull(name, _)
203 | FieldPred::In(name, _)
204 | FieldPred::Nin(name, _)
205 | FieldPred::Type(name, _)
206 | FieldPred::DatetimeComponent { field: name, .. },
207 ) => {
208 out.insert(name.clone());
209 true
210 }
211 }
212}
213
214fn scalar_pids<R: TryRead>(
219 snap: &R,
220 tenant: rushdb_types::ids::TenantId,
221 labels: &[String],
222 name: &str,
223) -> Result<Option<Vec<u64>>, QueryError> {
224 let norm = normalize(name);
225 if norm.is_empty() || rushdb_types::value::is_reserved_name(&norm) {
226 return Ok(None);
227 }
228 let Some(fid) = family_id(snap, tenant, &norm)? else {
229 return Ok(Some(Vec::new()));
230 };
231 let kind = rushdb_types::ids::OwnerKind::Record;
232 let mut by_support = Vec::new();
233 for pid in pids_of(snap, tenant, fid)? {
234 let (arrays, _) = rushdb_lmpg::derived::array_markers(snap, tenant, kind, pid)?;
235 if arrays {
236 return Ok(None);
237 }
238 let support = if labels.is_empty() {
239 rushdb_lmpg::derived::prop_support_total(snap, tenant, kind, pid)?
240 } else {
241 let mut support = 0u64;
242 let distinct: std::collections::BTreeSet<&String> = labels.iter().collect();
243 for label in distinct {
244 support = support.saturating_add(rushdb_lmpg::derived::prop_support_label(
245 snap, tenant, kind, pid, label,
246 )?);
247 }
248 support
249 };
250 by_support.push((std::cmp::Reverse(support), pid));
251 }
252 by_support.sort_unstable();
253 Ok(Some(by_support.into_iter().map(|(_, pid)| pid).collect()))
254}
255
256pub(super) fn collect_selection(
261 spec: &crate::CollectSpec,
262) -> Option<std::collections::HashSet<String>> {
263 if spec.fields.is_empty() {
264 return None;
265 }
266 let mut names = std::collections::BTreeSet::new();
267 if let Some(filter) = &spec.filter {
268 if !predicate_fields(filter, &mut names) {
269 return None;
270 }
271 }
272 for (_, source) in &spec.fields {
273 names.insert(source.strip_prefix("$record.").unwrap_or(source).to_owned());
274 }
275 for (field, _) in &spec.order_by {
276 names.insert(field.clone());
277 }
278 Some(
279 names
280 .into_iter()
281 .flat_map(|name| [normalize(&name), name])
282 .collect(),
283 )
284}