1use super::unified::*;
4use crate::storage::entity::{EntityData, EntityKind, RefType, UnifiedEntity};
5use reddb_types::Value;
6use std::collections::BTreeSet;
7
8mod document_body {
9 pub use reddb_types::document_body_codec::decode_container_to_json;
10 pub fn is_binary_container(bytes: &[u8]) -> bool {
11 bytes.starts_with(reddb_types::document_body_codec::MAGIC)
12 }
13 pub fn read_body_field(bytes: &[u8], name: &str) -> Option<reddb_types::Value> {
14 reddb_types::document_body_codec::read_field_by_name(bytes, name)
15 .ok()
16 .flatten()
17 }
18 pub fn body_fields(bytes: &[u8]) -> Option<Vec<(String, reddb_types::Value)>> {
19 reddb_types::document_body_codec::decode(bytes).ok()
20 }
21}
22
23pub const TIMESERIES_TAG_JSON_PREFIX: char = '\u{1}';
31
32pub fn decode_stored_tag_value(stored: &str) -> crate::json::Value {
44 if let Some(suffix) = stored.strip_prefix(TIMESERIES_TAG_JSON_PREFIX) {
45 crate::json::from_str(suffix)
46 .unwrap_or_else(|_| crate::json::Value::String(suffix.to_string()))
47 } else {
48 crate::json::Value::String(stored.to_string())
49 }
50}
51
52fn sys_schema_with_row_columns(
58 schema: &std::sync::Arc<Vec<String>>,
59) -> std::sync::Arc<Vec<std::sync::Arc<str>>> {
60 use std::cell::RefCell;
61 thread_local! {
62 static CACHE: RefCell<Option<(usize, std::sync::Arc<Vec<std::sync::Arc<str>>>)>> =
63 const { RefCell::new(None) };
64 }
65 let key = std::sync::Arc::as_ptr(schema) as usize;
66 CACHE.with(|c| {
67 let mut slot = c.borrow_mut();
68 if let Some((k, v)) = slot.as_ref() {
69 if *k == key {
70 return std::sync::Arc::clone(v);
71 }
72 }
73 let mut out: Vec<std::sync::Arc<str>> = Vec::with_capacity(6 + schema.len());
74 for name in schema.iter() {
75 out.push(std::sync::Arc::from(name.as_str()));
76 }
77 out.push(sys_key_rid());
78 out.push(sys_key_collection());
79 out.push(sys_key_kind());
80 out.push(sys_key_tenant());
81 out.push(sys_key_created_at());
82 out.push(sys_key_updated_at());
83 let arc = std::sync::Arc::new(out);
84 *slot = Some((key, std::sync::Arc::clone(&arc)));
85 arc
86 })
87}
88
89fn runtime_row_tenant_value(row: &crate::storage::entity::RowData) -> Value {
90 for key in ["tenant_id", "tenant"] {
91 if let Some(value) = row.get_field(key) {
92 return value.clone();
93 }
94 }
95 Value::Null
96}
97
98fn set_public_row_envelope(
99 record: &mut UnifiedRecord,
100 entity: &UnifiedEntity,
101 row: &crate::storage::entity::RowData,
102) {
103 record.set_arc(
104 sys_key_rid(),
105 Value::UnsignedInteger(entity.logical_id().raw()),
106 );
107 record.set_arc(
108 sys_key_collection(),
109 Value::text(entity.kind.collection().to_string()),
110 );
111 record.set_arc(
112 sys_key_kind(),
113 Value::text(public_row_kind(row).to_string()),
114 );
115 record.set_arc(sys_key_tenant(), runtime_row_tenant_value(row));
116 record.set_arc_if_absent(
119 sys_key_created_at(),
120 Value::UnsignedInteger(entity.created_at),
121 );
122 record.set_arc_if_absent(
123 sys_key_updated_at(),
124 Value::UnsignedInteger(entity.updated_at),
125 );
126}
127
128fn set_public_graph_envelope(record: &mut UnifiedRecord, entity: &UnifiedEntity, kind: &str) {
129 record.set_arc(
130 sys_key_rid(),
131 Value::UnsignedInteger(entity.logical_id().raw()),
132 );
133 record.set_arc(
134 sys_key_collection(),
135 Value::text(entity.kind.collection().to_string()),
136 );
137 record.set_arc(sys_key_kind(), Value::text(kind.to_string()));
138 record.set_arc(sys_key_tenant(), Value::Null);
139 record.set_arc(
140 sys_key_created_at(),
141 Value::UnsignedInteger(entity.created_at),
142 );
143 record.set_arc(
144 sys_key_updated_at(),
145 Value::UnsignedInteger(entity.updated_at),
146 );
147}
148
149fn graph_endpoint_rid_value(endpoint: &str) -> Value {
150 endpoint
151 .parse::<u64>()
152 .map(Value::UnsignedInteger)
153 .unwrap_or_else(|_| Value::text(endpoint.to_string()))
154}
155
156fn public_row_kind(row: &crate::storage::entity::RowData) -> &'static str {
157 if runtime_row_is_kv(row) {
158 "kv"
159 } else if runtime_row_has_document_capability(row) {
160 "document"
161 } else {
162 "row"
163 }
164}
165
166#[inline]
180pub fn runtime_table_record_lean_ref(entity: &UnifiedEntity) -> Option<UnifiedRecord> {
181 let created_at = entity.created_at;
182 let updated_at = entity.updated_at;
183 let row = match &entity.data {
184 EntityData::Row(row) => row,
185 _ => return runtime_any_record_from_entity_ref(entity),
189 };
190 if let Some(named) = &row.named {
191 let mut record = UnifiedRecord::with_capacity(6 + named.len());
192 for (key, value) in named.iter() {
193 record.set(key, value.clone());
194 }
195 set_public_row_envelope(&mut record, entity, row);
196 Some(record)
197 } else if let Some(schema) = &row.schema {
198 let sys_schema = sys_schema_with_row_columns(schema);
202 let mut values: Vec<Value> = Vec::with_capacity(sys_schema.len());
203 values.extend(row.columns.iter().cloned());
204 values.push(Value::UnsignedInteger(entity.logical_id().raw()));
205 values.push(Value::text(entity.kind.collection().to_string()));
206 values.push(Value::text(public_row_kind(row).to_string()));
207 values.push(runtime_row_tenant_value(row));
208 values.push(Value::UnsignedInteger(created_at));
209 values.push(Value::UnsignedInteger(updated_at));
210 Some(UnifiedRecord::from_columnar(sys_schema, values))
211 } else {
212 let mut record = UnifiedRecord::with_capacity(6 + row.columns.len());
213 for (i, value) in row.columns.iter().enumerate() {
214 record.set(&format!("c{i}"), value.clone());
215 }
216 set_public_row_envelope(&mut record, entity, row);
217 Some(record)
218 }
219}
220
221#[inline]
222pub fn runtime_table_record_lean(entity: UnifiedEntity) -> Option<UnifiedRecord> {
223 if !matches!(entity.data, EntityData::Row(_)) {
225 return runtime_any_record_from_entity(entity);
226 }
227 let created_at = entity.created_at;
228 let updated_at = entity.updated_at;
229 let logical_id = entity.logical_id().raw();
230 let row = match entity.data {
231 EntityData::Row(row) => row,
232 _ => unreachable!(),
233 };
234 let collection = entity.kind.collection().to_string();
235 let tenant = runtime_row_tenant_value(&row);
236 let kind = public_row_kind(&row).to_string();
237 let doc_body = document_row_body_bytes(&row);
239 if let Some(named) = row.named {
240 let mut record = UnifiedRecord::with_capacity(6 + named.len());
241 for (key, value) in named {
245 record.set_owned(key, value);
246 }
247 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
248 record.set_arc(sys_key_collection(), Value::text(collection));
249 record.set_arc(sys_key_kind(), Value::text(kind));
250 record.set_arc(sys_key_tenant(), tenant);
251 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
252 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
253 fill_document_body_fields(&mut record, None, doc_body.as_deref());
255 Some(record)
256 } else if let Some(ref schema) = row.schema {
257 let mut record = UnifiedRecord::with_capacity(6 + schema.len());
258 for (name, value) in schema.iter().zip(row.columns) {
259 record.set(name, value);
260 }
261 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
262 record.set_arc(sys_key_collection(), Value::text(collection));
263 record.set_arc(sys_key_kind(), Value::text(kind));
264 record.set_arc(sys_key_tenant(), tenant);
265 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
266 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
267 Some(record)
268 } else {
269 let mut record = UnifiedRecord::with_capacity(6 + row.columns.len());
270 for (i, value) in row.columns.into_iter().enumerate() {
271 record.set(&format!("c{i}"), value);
272 }
273 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
274 record.set_arc(sys_key_collection(), Value::text(collection));
275 record.set_arc(sys_key_kind(), Value::text(kind));
276 record.set_arc(sys_key_tenant(), tenant);
277 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
278 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
279 Some(record)
280 }
281}
282
283pub fn runtime_table_record_lean_in_collection(
284 entity: UnifiedEntity,
285 collection: &str,
286) -> Option<UnifiedRecord> {
287 let is_graph = matches!(entity.data, EntityData::Node(_) | EntityData::Edge(_));
288 let mut record = runtime_table_record_lean(entity)?;
289 if is_graph {
290 record.set_arc(sys_key_collection(), Value::text(collection.to_string()));
291 }
292 Some(record)
293}
294
295#[inline(never)]
296fn document_row_body_bytes(row: &crate::storage::entity::RowData) -> Option<Vec<u8>> {
302 match row.get_field("body") {
303 Some(Value::Json(bytes)) if document_body::is_binary_container(bytes) => {
304 Some(bytes.clone())
305 }
306 _ => None,
307 }
308}
309
310pub fn fill_document_body_fields(
316 record: &mut UnifiedRecord,
317 columns: Option<&[String]>,
318 body: Option<&[u8]>,
319) {
320 let Some(bytes) = body else {
321 return;
322 };
323 if let Some(body_json) = document_body::decode_container_to_json(bytes) {
324 if let Ok(json_bytes) = crate::json::to_vec(&body_json) {
325 record.set("body", Value::Json(json_bytes));
326 }
327 }
328 let fields: Vec<(String, Value)> = match columns {
329 None => match document_body::body_fields(bytes) {
330 Some(fields) => fields,
331 None => return,
332 },
333 Some(cols) => cols
334 .iter()
335 .filter_map(|col| {
336 document_body::read_body_field(bytes, col).map(|value| (col.clone(), value))
337 })
338 .collect(),
339 };
340 for (name, value) in fields {
341 record.set(&name, value);
342 }
343}
344
345pub fn runtime_table_record_from_entity(entity: UnifiedEntity) -> Option<UnifiedRecord> {
346 if !matches!(entity.data, EntityData::Row(_) | EntityData::TimeSeries(_)) {
348 return runtime_any_record_from_entity(entity);
349 }
350 let logical_id = entity.logical_id().raw();
351 match entity.data {
352 EntityData::Row(row) => {
353 let doc_body = document_row_body_bytes(&row);
355 let user_field_count = row
357 .named
358 .as_ref()
359 .map(|n| n.len())
360 .unwrap_or(row.columns.len());
361 let mut record = UnifiedRecord::with_capacity(6 + user_field_count);
362 let collection = entity.kind.collection().to_string();
363 let tenant = runtime_row_tenant_value(&row);
364 let kind = public_row_kind(&row).to_string();
365
366 if let Some(named) = row.named {
367 for (key, value) in named {
368 record.set(&key, value);
369 }
370 } else if let Some(ref schema) = row.schema {
371 for (name, value) in schema.iter().zip(row.columns) {
373 record.set(name, value);
374 }
375 } else {
376 for (index, value) in row.columns.into_iter().enumerate() {
377 record.set(&format!("c{index}"), value);
378 }
379 }
380 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
381 record.set_arc(sys_key_collection(), Value::text(collection));
382 record.set_arc(sys_key_kind(), Value::text(kind));
383 record.set_arc(sys_key_tenant(), tenant);
384 record.set_arc_if_absent(
385 sys_key_created_at(),
386 Value::UnsignedInteger(entity.created_at),
387 );
388 record.set_arc_if_absent(
389 sys_key_updated_at(),
390 Value::UnsignedInteger(entity.updated_at),
391 );
392
393 fill_document_body_fields(&mut record, None, doc_body.as_deref());
395
396 Some(record)
397 }
398 EntityData::TimeSeries(ts) => {
399 let mut record = UnifiedRecord::with_capacity(12 + ts.tags.len());
400 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
401 record.set(
402 "red_collection",
403 Value::text(entity.kind.collection().to_string()),
404 );
405 record.set(
406 "red_kind",
407 Value::text(entity.kind.storage_type().to_string()),
408 );
409 record.set_arc(
410 sys_key_created_at(),
411 Value::UnsignedInteger(entity.created_at),
412 );
413 record.set_arc(
414 sys_key_updated_at(),
415 Value::UnsignedInteger(entity.updated_at),
416 );
417 record.set(
418 "red_sequence_id",
419 Value::UnsignedInteger(entity.sequence_id),
420 );
421 record.set_arc(
422 sys_key_red_entity_type(),
423 Value::text("timeseries".to_string()),
424 );
425 record.set(
426 "red_capabilities",
427 Value::text("document,timeseries,metric,temporal".to_string()),
428 );
429 append_timeseries_record_fields(&mut record, &ts);
430 Some(record)
431 }
432 _ => None,
433 }
434}
435
436#[inline(never)]
439pub fn runtime_table_record_from_entity_ref(entity: &UnifiedEntity) -> Option<UnifiedRecord> {
440 runtime_table_record_from_entity_ref_with_schema(entity, None)
441}
442
443pub fn runtime_table_record_from_entity_ref_with_schema(
444 entity: &UnifiedEntity,
445 fallback_schema: Option<&std::sync::Arc<Vec<String>>>,
446) -> Option<UnifiedRecord> {
447 if !matches!(&entity.data, EntityData::Row(_) | EntityData::TimeSeries(_)) {
449 return runtime_any_record_from_entity_ref(entity);
450 }
451 match &entity.data {
452 EntityData::Row(row) => {
453 let user_field_count = row
454 .named
455 .as_ref()
456 .map(|n| n.len())
457 .unwrap_or(row.columns.len());
458 let mut record = UnifiedRecord::with_capacity(6 + user_field_count);
459
460 if let Some(named) = &row.named {
461 for (key, value) in named {
462 record.set(key, value.clone());
463 }
464 } else if let Some(schema) = row.schema.as_ref().or(fallback_schema) {
465 for (name, value) in schema.iter().zip(row.columns.iter()) {
466 record.set(name, value.clone());
467 }
468 } else {
469 for (index, value) in row.columns.iter().enumerate() {
470 record.set(&format!("c{index}"), value.clone());
471 }
472 }
473 set_public_row_envelope(&mut record, entity, row);
474
475 fill_document_body_fields(&mut record, None, document_row_body_bytes(row).as_deref());
477
478 Some(record)
479 }
480 EntityData::TimeSeries(ts) => {
481 let mut record = UnifiedRecord::with_capacity(12 + ts.tags.len());
482 record.set_arc(
483 sys_key_rid(),
484 Value::UnsignedInteger(entity.logical_id().raw()),
485 );
486 record.set(
487 "red_collection",
488 Value::text(entity.kind.collection().to_string()),
489 );
490 record.set(
491 "red_kind",
492 Value::text(entity.kind.storage_type().to_string()),
493 );
494 record.set_arc(
495 sys_key_created_at(),
496 Value::UnsignedInteger(entity.created_at),
497 );
498 record.set_arc(
499 sys_key_updated_at(),
500 Value::UnsignedInteger(entity.updated_at),
501 );
502 record.set(
503 "red_sequence_id",
504 Value::UnsignedInteger(entity.sequence_id),
505 );
506 record.set_arc(
507 sys_key_red_entity_type(),
508 Value::text("timeseries".to_string()),
509 );
510 record.set(
511 "red_capabilities",
512 Value::text("document,timeseries,metric,temporal".to_string()),
513 );
514 append_timeseries_record_fields(&mut record, ts);
515 Some(record)
516 }
517 _ => None,
518 }
519}
520
521#[inline(never)]
524pub fn runtime_table_record_from_entity_projected(
525 entity: UnifiedEntity,
526 columns: &[String],
527) -> Option<UnifiedRecord> {
528 if columns.is_empty() {
529 return runtime_table_record_from_entity(entity);
530 }
531 if !matches!(entity.data, EntityData::Row(_) | EntityData::TimeSeries(_)) {
535 return runtime_any_record_from_entity(entity);
536 }
537 let logical_id = entity.logical_id().raw();
538
539 match entity.data {
540 EntityData::Row(row) => {
541 let doc_body = document_row_body_bytes(&row);
543 let mut record = UnifiedRecord::with_capacity(6 + columns.len());
544 let collection = entity.kind.collection().to_string();
545 let tenant = runtime_row_tenant_value(&row);
546 let kind = public_row_kind(&row).to_string();
547
548 if let Some(named) = row.named {
549 for col in columns {
551 if let Some(value) = named.get(col) {
552 record.set(col, value.clone());
553 }
554 }
555 } else if let Some(ref schema) = row.schema {
556 for col in columns {
560 if let Some(idx) = schema.iter().position(|s| s == col) {
561 if let Some(value) = row.columns.get(idx) {
562 record.set(col, value.clone());
563 }
564 }
565 }
566 } else {
567 for col in columns {
569 if let Some(idx) = col.strip_prefix('c').and_then(|s| s.parse::<usize>().ok()) {
570 if let Some(value) = row.columns.get(idx) {
571 record.set(col, value.clone());
572 }
573 }
574 }
575 }
576 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
577 record.set_arc(sys_key_collection(), Value::text(collection));
578 record.set_arc(sys_key_kind(), Value::text(kind));
579 record.set_arc(sys_key_tenant(), tenant);
580 record.set_arc_if_absent(
581 sys_key_created_at(),
582 Value::UnsignedInteger(entity.created_at),
583 );
584 record.set_arc_if_absent(
585 sys_key_updated_at(),
586 Value::UnsignedInteger(entity.updated_at),
587 );
588
589 fill_document_body_fields(&mut record, Some(columns), doc_body.as_deref());
592
593 Some(record)
594 }
595 EntityData::TimeSeries(ts) => {
596 let mut record = UnifiedRecord::new();
597 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
598
599 for col in columns {
600 match col.as_str() {
601 "metric" => record.set("metric", Value::text(ts.metric.clone())),
602 "timestamp_ns" => {
603 record.set("timestamp_ns", Value::UnsignedInteger(ts.timestamp_ns))
604 }
605 "timestamp" => record.set("timestamp", Value::UnsignedInteger(ts.timestamp_ns)),
606 "time" => record.set("time", Value::UnsignedInteger(ts.timestamp_ns)),
607 "value" => record.set("value", Value::Float(ts.value)),
608 "tags" if !ts.tags.is_empty() => {
609 record.set("tags", timeseries_tags_value(&ts.tags));
610 }
611 _ => {
612 if let Some(value) = ts.fields.get(col) {
613 record.set(col, value.clone());
614 }
615 }
616 }
617 }
618
619 Some(record)
620 }
621 _ => None,
622 }
623}
624
625pub fn runtime_table_record_with_col_indices(
635 entity: &UnifiedEntity,
636 columns: &[String],
637 idx_map: &[(usize, usize)],
638) -> Option<UnifiedRecord> {
639 if idx_map.is_empty() {
640 return None;
641 }
642 let row = entity.data.as_row()?;
643 if row.named.is_some() || row.columns.is_empty() {
647 return None;
648 }
649 let mut record = UnifiedRecord::with_capacity(6 + idx_map.len());
650 for &(ci, si) in idx_map {
651 if let Some(value) = row.columns.get(si) {
652 record.set_owned(columns[ci].clone(), value.clone());
654 }
655 }
656 set_public_row_envelope(&mut record, entity, row);
657 Some(record)
658}
659
660pub fn runtime_table_record_from_entity_ref_projected(
664 entity: &UnifiedEntity,
665 columns: &[String],
666) -> Option<UnifiedRecord> {
667 if columns.is_empty() {
668 return None; }
670 if !matches!(&entity.data, EntityData::Row(_)) {
674 return runtime_any_record_from_entity_ref(entity);
675 }
676 let row = entity.data.as_row()?;
677
678 let mut record = UnifiedRecord::with_capacity(6 + columns.len());
679
680 if let Some(ref named) = row.named {
681 for col in columns {
682 if let Some(value) = named.get(col.as_str()) {
683 record.set(col, value.clone());
684 }
685 }
686 } else if let Some(ref schema) = row.schema {
687 for col in columns {
688 if let Some(idx) = schema.iter().position(|s| s == col) {
689 if let Some(value) = row.columns.get(idx) {
690 record.set(col, value.clone());
691 }
692 }
693 }
694 } else {
695 for col in columns {
696 if let Some(idx) = col.strip_prefix('c').and_then(|s| s.parse::<usize>().ok()) {
697 if let Some(value) = row.columns.get(idx) {
698 record.set(col, value.clone());
699 }
700 }
701 }
702 }
703 set_public_row_envelope(&mut record, entity, row);
704 fill_document_body_fields(
706 &mut record,
707 Some(columns),
708 document_row_body_bytes(row).as_deref(),
709 );
710 Some(record)
711}
712
713#[inline(never)]
714pub fn runtime_any_record_from_entity(entity: UnifiedEntity) -> Option<UnifiedRecord> {
715 let identity_entity = entity.clone();
716 let kind = entity.kind.clone();
717 let collection = kind.collection().to_string();
718 let storage_type = kind.storage_type().to_string();
719 let entity_id = entity.logical_id().raw();
720 let created_at = entity.created_at;
721 let updated_at = entity.updated_at;
722 let sequence_id = entity.sequence_id;
723
724 let (entity_type, capabilities, mut record) = match (kind, entity.data) {
725 (EntityKind::TableRow { row_id, .. }, EntityData::Row(row)) => {
726 let capabilities = runtime_row_capabilities(&row);
727 let entity_type = runtime_row_entity_type(&row);
728 let doc_body = document_row_body_bytes(&row);
729 let mut record = UnifiedRecord::new();
730 record.set_arc(sys_key_row_id(), Value::UnsignedInteger(row_id));
731 if let Some(named) = row.named {
732 for (key, value) in named {
733 record.set(&key, value);
734 }
735 } else {
736 for (index, value) in row.columns.into_iter().enumerate() {
737 record.set(&format!("c{index}"), value);
738 }
739 }
740 fill_document_body_fields(&mut record, None, doc_body.as_deref());
741 (entity_type, capabilities, record)
742 }
743 (EntityKind::GraphNode(node), EntityData::Node(node_data)) => {
744 let mut record = UnifiedRecord::new();
745 record.set("label", Value::text(node.label));
746 record.set("node_type", Value::text(node.node_type));
747 for (key, value) in node_data.properties {
748 record.set(&key, value);
749 }
750 set_public_graph_envelope(&mut record, &identity_entity, "node");
751 (
752 "graph_node",
753 runtime_record_capability_list(["graph", "graph_node"]),
754 record,
755 )
756 }
757 (EntityKind::GraphEdge(edge_kind), EntityData::Edge(edge)) => {
758 let mut record = UnifiedRecord::new();
759 record.set("label", Value::text(edge_kind.label));
760 record.set("from_rid", graph_endpoint_rid_value(&edge_kind.from_node));
761 record.set("to_rid", graph_endpoint_rid_value(&edge_kind.to_node));
762 record.set("weight", Value::Float(edge.weight as f64));
763 for (key, value) in edge.properties {
764 record.set(&key, value);
765 }
766 set_public_graph_envelope(&mut record, &identity_entity, "edge");
767 (
768 "graph_edge",
769 runtime_record_capability_list(["graph", "graph_edge"]),
770 record,
771 )
772 }
773 (EntityKind::Vector { .. }, EntityData::Vector(vector)) => {
774 let mut record = UnifiedRecord::new();
775 record.set(
776 "dimension",
777 Value::UnsignedInteger(vector.dense.len() as u64),
778 );
779 if let Some(content) = vector.content {
780 record.set("content", Value::text(content));
781 }
782 (
783 "vector",
784 runtime_record_capability_list(["vector", "similarity", "embedding"]),
785 record,
786 )
787 }
788 (EntityKind::TimeSeriesPoint(_), EntityData::TimeSeries(ts)) => {
789 let mut record = UnifiedRecord::new();
790 append_timeseries_record_fields(&mut record, &ts);
791 (
792 "timeseries",
793 runtime_record_capability_list(["document", "timeseries", "metric", "temporal"]),
794 record,
795 )
796 }
797 (EntityKind::QueueMessage { position, .. }, EntityData::QueueMessage(msg)) => {
798 let mut record = UnifiedRecord::new();
804 record.set("position", Value::UnsignedInteger(position));
805 record.set("payload", msg.payload);
806 record.set("attempts", Value::UnsignedInteger(msg.attempts as u64));
807 record.set("acked", Value::Boolean(msg.acked));
808 if let Some(priority) = msg.priority {
809 record.set("priority", Value::Integer(priority as i64));
810 }
811 (
812 "queue_message",
813 runtime_record_capability_list(["document", "queue", "message"]),
814 record,
815 )
816 }
817 _ => return None,
818 };
819
820 if let EntityData::Row(row) = &identity_entity.data {
821 set_public_row_envelope(&mut record, &identity_entity, row);
822 } else if matches!(
823 identity_entity.data,
824 EntityData::Node(_) | EntityData::Edge(_)
825 ) {
826 set_runtime_entity_metadata(&mut record, entity_type, capabilities);
827 apply_runtime_identity_hints(&mut record, &identity_entity);
828 } else {
829 record.set_arc(sys_key_rid(), Value::UnsignedInteger(entity_id));
833 record.set_arc(sys_key_red_collection(), Value::text(collection));
834 record.set_arc(sys_key_red_kind(), Value::text(storage_type));
835 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
836 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
837 record.set_arc(
838 sys_key_red_sequence_id(),
839 Value::UnsignedInteger(sequence_id),
840 );
841 set_runtime_entity_metadata(&mut record, entity_type, capabilities);
842 apply_runtime_identity_hints(&mut record, &identity_entity);
843 }
844
845 Some(record)
846}
847
848pub fn runtime_any_record_from_entity_ref(entity: &UnifiedEntity) -> Option<UnifiedRecord> {
849 let kind = &entity.kind;
850 let collection = kind.collection().to_string();
851 let storage_type = kind.storage_type().to_string();
852 let entity_id = entity.logical_id().raw();
853 let created_at = entity.created_at;
854 let updated_at = entity.updated_at;
855 let sequence_id = entity.sequence_id;
856
857 let (entity_type, capabilities, mut record) = match (kind, &entity.data) {
858 (EntityKind::TableRow { row_id, .. }, EntityData::Row(row)) => {
859 let capabilities = runtime_row_capabilities(row);
860 let entity_type = runtime_row_entity_type(row);
861 let doc_body = document_row_body_bytes(row);
862 let mut record = UnifiedRecord::new();
863 record.set_arc(sys_key_row_id(), Value::UnsignedInteger(*row_id));
864 if let Some(named) = row.named.as_ref() {
865 for (key, value) in named {
866 record.set(key, value.clone());
867 }
868 } else if let Some(schema) = row.schema.as_ref() {
869 for (name, value) in schema.iter().zip(row.columns.iter()) {
870 record.set(name, value.clone());
871 }
872 } else {
873 for (index, value) in row.columns.iter().enumerate() {
874 record.set(&format!("c{index}"), value.clone());
875 }
876 }
877 fill_document_body_fields(&mut record, None, doc_body.as_deref());
878 (entity_type, capabilities, record)
879 }
880 (EntityKind::GraphNode(node), EntityData::Node(node_data)) => {
881 let mut record = UnifiedRecord::new();
882 record.set("label", Value::text(node.label.clone()));
883 record.set("node_type", Value::text(node.node_type.clone()));
884 for (key, value) in &node_data.properties {
885 record.set(key, value.clone());
886 }
887 set_public_graph_envelope(&mut record, entity, "node");
888 (
889 "graph_node",
890 runtime_record_capability_list(["graph", "graph_node"]),
891 record,
892 )
893 }
894 (EntityKind::GraphEdge(edge_kind), EntityData::Edge(edge)) => {
895 let mut record = UnifiedRecord::new();
896 record.set("label", Value::text(edge_kind.label.clone()));
897 record.set("from_rid", graph_endpoint_rid_value(&edge_kind.from_node));
898 record.set("to_rid", graph_endpoint_rid_value(&edge_kind.to_node));
899 record.set("weight", Value::Float(edge.weight as f64));
900 for (key, value) in &edge.properties {
901 record.set(key, value.clone());
902 }
903 set_public_graph_envelope(&mut record, entity, "edge");
908 (
909 "graph_edge",
910 runtime_record_capability_list(["graph", "graph_edge"]),
911 record,
912 )
913 }
914 (EntityKind::Vector { .. }, EntityData::Vector(vector)) => {
915 let mut record = UnifiedRecord::new();
916 record.set(
917 "dimension",
918 Value::UnsignedInteger(vector.dense.len() as u64),
919 );
920 if let Some(content) = vector.content.as_ref() {
921 record.set("content", Value::text(content.clone()));
922 }
923 (
924 "vector",
925 runtime_record_capability_list(["vector", "similarity", "embedding"]),
926 record,
927 )
928 }
929 (EntityKind::TimeSeriesPoint(_), EntityData::TimeSeries(ts)) => {
930 let mut record = UnifiedRecord::new();
931 append_timeseries_record_fields(&mut record, ts);
932 (
933 "timeseries",
934 runtime_record_capability_list(["document", "timeseries", "metric", "temporal"]),
935 record,
936 )
937 }
938 (EntityKind::QueueMessage { position, .. }, EntityData::QueueMessage(msg)) => {
939 let mut record = UnifiedRecord::new();
940 record.set("position", Value::UnsignedInteger(*position));
941 record.set("payload", msg.payload.clone());
942 record.set("attempts", Value::UnsignedInteger(msg.attempts as u64));
943 record.set("acked", Value::Boolean(msg.acked));
944 if let Some(priority) = msg.priority {
945 record.set("priority", Value::Integer(priority as i64));
946 }
947 (
948 "queue_message",
949 runtime_record_capability_list(["document", "queue", "message"]),
950 record,
951 )
952 }
953 _ => return None,
954 };
955
956 if let EntityData::Row(row) = &entity.data {
957 set_public_row_envelope(&mut record, entity, row);
958 } else if matches!(entity.data, EntityData::Node(_) | EntityData::Edge(_)) {
959 set_runtime_entity_metadata(&mut record, entity_type, capabilities);
960 apply_runtime_identity_hints(&mut record, entity);
961 } else {
962 record.set_arc(sys_key_rid(), Value::UnsignedInteger(entity_id));
966 record.set_arc(sys_key_red_collection(), Value::text(collection));
967 record.set_arc(sys_key_red_kind(), Value::text(storage_type));
968 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
969 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
970 record.set_arc(
971 sys_key_red_sequence_id(),
972 Value::UnsignedInteger(sequence_id),
973 );
974 set_runtime_entity_metadata(&mut record, entity_type, capabilities);
975 apply_runtime_identity_hints(&mut record, entity);
976 }
977
978 Some(record)
979}
980
981fn append_timeseries_record_fields(
982 record: &mut UnifiedRecord,
983 ts: &crate::storage::entity::TimeSeriesData,
984) {
985 record.set("metric", Value::text(ts.metric.clone()));
986 record.set("timestamp_ns", Value::UnsignedInteger(ts.timestamp_ns));
987 record.set("timestamp", Value::UnsignedInteger(ts.timestamp_ns));
988 record.set("time", Value::UnsignedInteger(ts.timestamp_ns));
989 record.set("value", Value::Float(ts.value));
990 if !ts.tags.is_empty() {
991 record.set("tags", timeseries_tags_value(&ts.tags));
992 }
993 for (key, value) in &ts.fields {
994 record.set(key, value.clone());
995 }
996}
997
998fn timeseries_tags_value(tags: &std::collections::HashMap<String, String>) -> Value {
999 let object = tags
1000 .iter()
1001 .map(|(key, value)| (key.clone(), decode_stored_tag_value(value)))
1002 .collect();
1003 let json = crate::json::Value::Object(object);
1004 Value::Json(crate::json::to_vec(&json).unwrap_or_default())
1005}
1006
1007#[inline(never)]
1008pub fn set_runtime_entity_metadata(
1009 record: &mut UnifiedRecord,
1010 entity_type: &str,
1011 capabilities: BTreeSet<String>,
1012) {
1013 let capabilities_text = capabilities.into_iter().collect::<Vec<_>>().join(",");
1014 record.set_arc(
1015 sys_key_red_entity_type(),
1016 Value::text(entity_type.to_string()),
1017 );
1018 record.set_arc(sys_key_red_capabilities(), Value::text(capabilities_text));
1019}
1020
1021pub fn runtime_record_capability_list<const N: usize>(values: [&str; N]) -> BTreeSet<String> {
1022 values.into_iter().map(|value| value.to_string()).collect()
1023}
1024
1025pub fn runtime_row_capabilities(row: &crate::storage::entity::RowData) -> BTreeSet<String> {
1026 let mut capabilities = runtime_record_capability_list(["table", "structured"]);
1027 if runtime_row_is_kv(row) {
1028 capabilities.insert("kv".to_string());
1029 }
1030 if runtime_row_has_document_capability(row) {
1031 capabilities.insert("document".to_string());
1032 }
1033 capabilities
1034}
1035
1036pub fn runtime_row_capabilities_str(row: &crate::storage::entity::RowData) -> &'static str {
1039 let is_kv = runtime_row_is_kv(row);
1040 let is_doc = runtime_row_has_document_capability(row);
1041 match (is_kv, is_doc) {
1042 (false, false) => "structured,table",
1043 (true, false) => "kv,structured,table",
1044 (false, true) => "document,structured,table",
1045 (true, true) => "document,kv,structured,table",
1046 }
1047}
1048
1049pub fn runtime_row_entity_type(row: &crate::storage::entity::RowData) -> &'static str {
1050 if runtime_row_is_kv(row) {
1051 return "kv";
1052 }
1053
1054 if runtime_row_has_document_capability(row) {
1055 "document"
1056 } else {
1057 "table"
1058 }
1059}
1060
1061fn runtime_row_is_kv(row: &crate::storage::entity::RowData) -> bool {
1062 let Some(named) = row.named.as_ref() else {
1063 return false;
1064 };
1065
1066 if named.len() == 2 {
1067 named.contains_key("key") && named.contains_key("value")
1068 } else if named.len() == 1 {
1069 named.contains_key("key") || named.contains_key("value")
1070 } else {
1071 false
1072 }
1073}
1074
1075pub fn runtime_row_has_document_capability(row: &crate::storage::entity::RowData) -> bool {
1076 row.named
1077 .as_ref()
1078 .map(|named| named.values().any(runtime_documentish_value))
1079 .unwrap_or(false)
1080 || row.columns.iter().any(runtime_documentish_value)
1081}
1082
1083pub fn runtime_documentish_value(value: &Value) -> bool {
1084 matches!(value, Value::Json(_) | Value::Blob(_))
1085}
1086
1087pub fn runtime_entity_type_and_capabilities(
1088 entity: &UnifiedEntity,
1089) -> (&'static str, BTreeSet<String>) {
1090 match (&entity.kind, &entity.data) {
1091 (EntityKind::TableRow { .. }, EntityData::Row(row)) => {
1092 (runtime_row_entity_type(row), runtime_row_capabilities(row))
1093 }
1094 (EntityKind::GraphNode(_), EntityData::Node(_)) => (
1095 "graph_node",
1096 runtime_record_capability_list(["graph", "graph_node"]),
1097 ),
1098 (EntityKind::GraphEdge(_), EntityData::Edge(_)) => (
1099 "graph_edge",
1100 runtime_record_capability_list(["graph", "graph_edge"]),
1101 ),
1102 (EntityKind::Vector { .. }, EntityData::Vector(_)) => (
1103 "vector",
1104 runtime_record_capability_list(["vector", "similarity", "embedding"]),
1105 ),
1106 (EntityKind::TimeSeriesPoint(_), EntityData::TimeSeries(_)) => (
1107 "timeseries",
1108 runtime_record_capability_list(["document", "timeseries", "metric", "temporal"]),
1109 ),
1110 (EntityKind::QueueMessage { .. }, EntityData::QueueMessage(_)) => (
1111 "queue_message",
1112 runtime_record_capability_list(["document", "queue", "message"]),
1113 ),
1114 _ => ("unknown", BTreeSet::new()),
1115 }
1116}
1117
1118pub fn apply_runtime_identity_hints(record: &mut UnifiedRecord, entity: &UnifiedEntity) {
1119 for cross_ref in entity.cross_refs() {
1120 let value = match cross_ref.ref_type {
1121 RefType::VectorToRow | RefType::NodeToRow => Some(Value::RowRef(
1122 cross_ref.target_collection.clone(),
1123 cross_ref.target.raw(),
1124 )),
1125 RefType::VectorToNode | RefType::RowToNode => Some(Value::NodeRef(format!(
1126 "{}:{}",
1127 cross_ref.target_collection, cross_ref.target
1128 ))),
1129 RefType::RowToEdge | RefType::EdgeToVector => Some(Value::EdgeRef(format!(
1130 "{}:{}",
1131 cross_ref.target_collection, cross_ref.target
1132 ))),
1133 _ => Some(Value::text(format!(
1134 "{}:{}",
1135 cross_ref.target_collection, cross_ref.target
1136 ))),
1137 };
1138
1139 if let Some(value) = value {
1140 let link_key: std::sync::Arc<str> = std::sync::Arc::from("_linked_identity");
1141 match cross_ref.ref_type {
1142 RefType::VectorToRow | RefType::NodeToRow => {
1143 record.set_arc(std::sync::Arc::from("_source_row"), value.clone());
1144 record.overflow_entry_or_insert(link_key, value);
1145 }
1146 RefType::VectorToNode | RefType::RowToNode => {
1147 record.set_arc(std::sync::Arc::from("_source_node"), value.clone());
1148 record.overflow_entry_or_insert(link_key, value);
1149 }
1150 RefType::RowToEdge | RefType::EdgeToVector => {
1151 record.set_arc(std::sync::Arc::from("_source_edge"), value.clone());
1152 record.overflow_entry_or_insert(link_key, value);
1153 }
1154 _ => {
1155 record.overflow_entry_or_insert(
1156 std::sync::Arc::from("_source_entity"),
1157 value.clone(),
1158 );
1159 record.overflow_entry_or_insert(link_key, value);
1160 }
1161 }
1162 }
1163 }
1164}