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 fill_document_body_fields(&mut record, None, document_row_body_bytes(row).as_deref());
197 Some(record)
198 } else if let Some(schema) = &row.schema {
199 let sys_schema = sys_schema_with_row_columns(schema);
203 let mut values: Vec<Value> = Vec::with_capacity(sys_schema.len());
204 values.extend(row.columns.iter().cloned());
205 values.push(Value::UnsignedInteger(entity.logical_id().raw()));
206 values.push(Value::text(entity.kind.collection().to_string()));
207 values.push(Value::text(public_row_kind(row).to_string()));
208 values.push(runtime_row_tenant_value(row));
209 values.push(Value::UnsignedInteger(created_at));
210 values.push(Value::UnsignedInteger(updated_at));
211 let mut record = UnifiedRecord::from_columnar(sys_schema, values);
212 fill_document_body_fields(&mut record, None, document_row_body_bytes(row).as_deref());
213 Some(record)
214 } else {
215 let mut record = UnifiedRecord::with_capacity(6 + row.columns.len());
216 for (i, value) in row.columns.iter().enumerate() {
217 record.set(&format!("c{i}"), value.clone());
218 }
219 set_public_row_envelope(&mut record, entity, row);
220 Some(record)
221 }
222}
223
224#[inline]
225pub fn runtime_table_record_lean(entity: UnifiedEntity) -> Option<UnifiedRecord> {
226 if !matches!(entity.data, EntityData::Row(_)) {
228 return runtime_any_record_from_entity(entity);
229 }
230 let created_at = entity.created_at;
231 let updated_at = entity.updated_at;
232 let logical_id = entity.logical_id().raw();
233 let row = match entity.data {
234 EntityData::Row(row) => row,
235 _ => unreachable!(),
236 };
237 let collection = entity.kind.collection().to_string();
238 let tenant = runtime_row_tenant_value(&row);
239 let kind = public_row_kind(&row).to_string();
240 let doc_body = document_row_body_bytes(&row);
242 if let Some(named) = row.named {
243 let mut record = UnifiedRecord::with_capacity(6 + named.len());
244 for (key, value) in named {
248 record.set_owned(key, value);
249 }
250 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
251 record.set_arc(sys_key_collection(), Value::text(collection));
252 record.set_arc(sys_key_kind(), Value::text(kind));
253 record.set_arc(sys_key_tenant(), tenant);
254 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
255 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
256 fill_document_body_fields(&mut record, None, doc_body.as_deref());
258 Some(record)
259 } else if let Some(ref schema) = row.schema {
260 let mut record = UnifiedRecord::with_capacity(6 + schema.len());
261 for (name, value) in schema.iter().zip(row.columns) {
262 record.set(name, value);
263 }
264 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
265 record.set_arc(sys_key_collection(), Value::text(collection));
266 record.set_arc(sys_key_kind(), Value::text(kind));
267 record.set_arc(sys_key_tenant(), tenant);
268 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
269 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
270 fill_document_body_fields(&mut record, None, doc_body.as_deref());
271 Some(record)
272 } else {
273 let mut record = UnifiedRecord::with_capacity(6 + row.columns.len());
274 for (i, value) in row.columns.into_iter().enumerate() {
275 record.set(&format!("c{i}"), value);
276 }
277 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
278 record.set_arc(sys_key_collection(), Value::text(collection));
279 record.set_arc(sys_key_kind(), Value::text(kind));
280 record.set_arc(sys_key_tenant(), tenant);
281 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
282 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
283 Some(record)
284 }
285}
286
287pub fn runtime_table_record_lean_in_collection(
288 entity: UnifiedEntity,
289 collection: &str,
290) -> Option<UnifiedRecord> {
291 let is_graph = matches!(entity.data, EntityData::Node(_) | EntityData::Edge(_));
292 let mut record = runtime_table_record_lean(entity)?;
293 if is_graph {
294 record.set_arc(sys_key_collection(), Value::text(collection.to_string()));
295 }
296 Some(record)
297}
298
299#[inline(never)]
300fn document_row_body_bytes(row: &crate::storage::entity::RowData) -> Option<Vec<u8>> {
306 match row.get_field("body") {
307 Some(Value::Json(bytes)) if document_body::is_binary_container(bytes) => {
308 Some(bytes.clone())
309 }
310 _ => None,
311 }
312}
313
314pub fn fill_document_body_fields(
320 record: &mut UnifiedRecord,
321 columns: Option<&[String]>,
322 body: Option<&[u8]>,
323) {
324 let Some(bytes) = body else {
325 return;
326 };
327 if let Some(body_json) = document_body::decode_container_to_json(bytes) {
328 if let Ok(json_bytes) = crate::json::to_vec(&body_json) {
329 record.set("body", Value::Json(json_bytes));
330 }
331 }
332 let fields: Vec<(String, Value)> = match columns {
333 None => match document_body::body_fields(bytes) {
334 Some(fields) => fields,
335 None => return,
336 },
337 Some(cols) => cols
338 .iter()
339 .filter_map(|col| {
340 document_body::read_body_field(bytes, col).map(|value| (col.clone(), value))
341 })
342 .collect(),
343 };
344 for (name, value) in fields {
345 record.set(&name, value);
346 }
347}
348
349pub fn runtime_table_record_from_entity(entity: UnifiedEntity) -> Option<UnifiedRecord> {
350 if !matches!(entity.data, EntityData::Row(_) | EntityData::TimeSeries(_)) {
352 return runtime_any_record_from_entity(entity);
353 }
354 let logical_id = entity.logical_id().raw();
355 match entity.data {
356 EntityData::Row(row) => {
357 let doc_body = document_row_body_bytes(&row);
359 let user_field_count = row
361 .named
362 .as_ref()
363 .map(|n| n.len())
364 .unwrap_or(row.columns.len());
365 let mut record = UnifiedRecord::with_capacity(6 + user_field_count);
366 let collection = entity.kind.collection().to_string();
367 let tenant = runtime_row_tenant_value(&row);
368 let kind = public_row_kind(&row).to_string();
369
370 if let Some(named) = row.named {
371 for (key, value) in named {
372 record.set(&key, value);
373 }
374 } else if let Some(ref schema) = row.schema {
375 for (name, value) in schema.iter().zip(row.columns) {
377 record.set(name, value);
378 }
379 } else {
380 for (index, value) in row.columns.into_iter().enumerate() {
381 record.set(&format!("c{index}"), value);
382 }
383 }
384 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
385 record.set_arc(sys_key_collection(), Value::text(collection));
386 record.set_arc(sys_key_kind(), Value::text(kind));
387 record.set_arc(sys_key_tenant(), tenant);
388 record.set_arc_if_absent(
389 sys_key_created_at(),
390 Value::UnsignedInteger(entity.created_at),
391 );
392 record.set_arc_if_absent(
393 sys_key_updated_at(),
394 Value::UnsignedInteger(entity.updated_at),
395 );
396
397 fill_document_body_fields(&mut record, None, doc_body.as_deref());
399
400 Some(record)
401 }
402 EntityData::TimeSeries(ts) => {
403 let mut record = UnifiedRecord::with_capacity(12 + ts.tags.len());
404 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
405 record.set(
406 "red_collection",
407 Value::text(entity.kind.collection().to_string()),
408 );
409 record.set(
410 "red_kind",
411 Value::text(entity.kind.storage_type().to_string()),
412 );
413 record.set_arc(
414 sys_key_created_at(),
415 Value::UnsignedInteger(entity.created_at),
416 );
417 record.set_arc(
418 sys_key_updated_at(),
419 Value::UnsignedInteger(entity.updated_at),
420 );
421 record.set(
422 "red_sequence_id",
423 Value::UnsignedInteger(entity.sequence_id),
424 );
425 record.set_arc(
426 sys_key_red_entity_type(),
427 Value::text("timeseries".to_string()),
428 );
429 record.set(
430 "red_capabilities",
431 Value::text("document,timeseries,metric,temporal".to_string()),
432 );
433 append_timeseries_record_fields(&mut record, &ts);
434 Some(record)
435 }
436 _ => None,
437 }
438}
439
440#[inline(never)]
443pub fn runtime_table_record_from_entity_ref(entity: &UnifiedEntity) -> Option<UnifiedRecord> {
444 runtime_table_record_from_entity_ref_with_schema(entity, None)
445}
446
447pub fn runtime_table_record_from_entity_ref_with_schema(
448 entity: &UnifiedEntity,
449 fallback_schema: Option<&std::sync::Arc<Vec<String>>>,
450) -> Option<UnifiedRecord> {
451 if !matches!(&entity.data, EntityData::Row(_) | EntityData::TimeSeries(_)) {
453 return runtime_any_record_from_entity_ref(entity);
454 }
455 match &entity.data {
456 EntityData::Row(row) => {
457 let user_field_count = row
458 .named
459 .as_ref()
460 .map(|n| n.len())
461 .unwrap_or(row.columns.len());
462 let mut record = UnifiedRecord::with_capacity(6 + user_field_count);
463
464 if let Some(named) = &row.named {
465 for (key, value) in named {
466 record.set(key, value.clone());
467 }
468 } else if let Some(schema) = row.schema.as_ref().or(fallback_schema) {
469 for (name, value) in schema.iter().zip(row.columns.iter()) {
470 record.set(name, value.clone());
471 }
472 } else {
473 for (index, value) in row.columns.iter().enumerate() {
474 record.set(&format!("c{index}"), value.clone());
475 }
476 }
477 set_public_row_envelope(&mut record, entity, row);
478
479 fill_document_body_fields(&mut record, None, document_row_body_bytes(row).as_deref());
481
482 Some(record)
483 }
484 EntityData::TimeSeries(ts) => {
485 let mut record = UnifiedRecord::with_capacity(12 + ts.tags.len());
486 record.set_arc(
487 sys_key_rid(),
488 Value::UnsignedInteger(entity.logical_id().raw()),
489 );
490 record.set(
491 "red_collection",
492 Value::text(entity.kind.collection().to_string()),
493 );
494 record.set(
495 "red_kind",
496 Value::text(entity.kind.storage_type().to_string()),
497 );
498 record.set_arc(
499 sys_key_created_at(),
500 Value::UnsignedInteger(entity.created_at),
501 );
502 record.set_arc(
503 sys_key_updated_at(),
504 Value::UnsignedInteger(entity.updated_at),
505 );
506 record.set(
507 "red_sequence_id",
508 Value::UnsignedInteger(entity.sequence_id),
509 );
510 record.set_arc(
511 sys_key_red_entity_type(),
512 Value::text("timeseries".to_string()),
513 );
514 record.set(
515 "red_capabilities",
516 Value::text("document,timeseries,metric,temporal".to_string()),
517 );
518 append_timeseries_record_fields(&mut record, ts);
519 Some(record)
520 }
521 _ => None,
522 }
523}
524
525#[inline(never)]
528pub fn runtime_table_record_from_entity_projected(
529 entity: UnifiedEntity,
530 columns: &[String],
531) -> Option<UnifiedRecord> {
532 if columns.is_empty() {
533 return runtime_table_record_from_entity(entity);
534 }
535 if !matches!(entity.data, EntityData::Row(_) | EntityData::TimeSeries(_)) {
539 return runtime_any_record_from_entity(entity);
540 }
541 let logical_id = entity.logical_id().raw();
542
543 match entity.data {
544 EntityData::Row(row) => {
545 let doc_body = document_row_body_bytes(&row);
547 let mut record = UnifiedRecord::with_capacity(6 + columns.len());
548 let collection = entity.kind.collection().to_string();
549 let tenant = runtime_row_tenant_value(&row);
550 let kind = public_row_kind(&row).to_string();
551
552 if let Some(named) = row.named {
553 for col in columns {
555 if let Some(value) = named.get(col) {
556 record.set(col, value.clone());
557 }
558 }
559 } else if let Some(ref schema) = row.schema {
560 for col in columns {
564 if let Some(idx) = schema.iter().position(|s| s == col) {
565 if let Some(value) = row.columns.get(idx) {
566 record.set(col, value.clone());
567 }
568 }
569 }
570 } else {
571 for col in columns {
573 if let Some(idx) = col.strip_prefix('c').and_then(|s| s.parse::<usize>().ok()) {
574 if let Some(value) = row.columns.get(idx) {
575 record.set(col, value.clone());
576 }
577 }
578 }
579 }
580 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
581 record.set_arc(sys_key_collection(), Value::text(collection));
582 record.set_arc(sys_key_kind(), Value::text(kind));
583 record.set_arc(sys_key_tenant(), tenant);
584 record.set_arc_if_absent(
585 sys_key_created_at(),
586 Value::UnsignedInteger(entity.created_at),
587 );
588 record.set_arc_if_absent(
589 sys_key_updated_at(),
590 Value::UnsignedInteger(entity.updated_at),
591 );
592
593 fill_document_body_fields(&mut record, Some(columns), doc_body.as_deref());
596
597 Some(record)
598 }
599 EntityData::TimeSeries(ts) => {
600 let mut record = UnifiedRecord::new();
601 record.set_arc(sys_key_rid(), Value::UnsignedInteger(logical_id));
602
603 for col in columns {
604 match col.as_str() {
605 "metric" => record.set("metric", Value::text(ts.metric.clone())),
606 "timestamp_ns" => {
607 record.set("timestamp_ns", Value::UnsignedInteger(ts.timestamp_ns))
608 }
609 "timestamp" => record.set("timestamp", Value::UnsignedInteger(ts.timestamp_ns)),
610 "time" => record.set("time", Value::UnsignedInteger(ts.timestamp_ns)),
611 "value" => record.set("value", Value::Float(ts.value)),
612 "tags" if !ts.tags.is_empty() => {
613 record.set("tags", timeseries_tags_value(&ts.tags));
614 }
615 _ => {
616 if let Some(value) = ts.fields.get(col) {
617 record.set(col, value.clone());
618 }
619 }
620 }
621 }
622
623 Some(record)
624 }
625 _ => None,
626 }
627}
628
629pub fn runtime_table_record_with_col_indices(
639 entity: &UnifiedEntity,
640 columns: &[String],
641 idx_map: &[(usize, usize)],
642) -> Option<UnifiedRecord> {
643 if idx_map.is_empty() {
644 return None;
645 }
646 let row = entity.data.as_row()?;
647 if row.named.is_some() || row.columns.is_empty() {
651 return None;
652 }
653 if row.schema.as_ref().is_some_and(|schema| {
654 idx_map
655 .iter()
656 .any(|&(ci, si)| schema.get(si) != columns.get(ci))
657 }) {
658 return None;
659 }
660 let mut record = UnifiedRecord::with_capacity(6 + idx_map.len());
661 for &(ci, si) in idx_map {
662 if let Some(value) = row.columns.get(si) {
663 record.set_owned(columns[ci].clone(), value.clone());
665 }
666 }
667 set_public_row_envelope(&mut record, entity, row);
668 Some(record)
669}
670
671pub fn runtime_table_record_from_entity_ref_projected(
675 entity: &UnifiedEntity,
676 columns: &[String],
677) -> Option<UnifiedRecord> {
678 if columns.is_empty() {
679 return None; }
681 if !matches!(&entity.data, EntityData::Row(_)) {
685 return runtime_any_record_from_entity_ref(entity);
686 }
687 let row = entity.data.as_row()?;
688
689 let mut record = UnifiedRecord::with_capacity(6 + columns.len());
690
691 if let Some(ref named) = row.named {
692 for col in columns {
693 if let Some(value) = named.get(col.as_str()) {
694 record.set(col, value.clone());
695 }
696 }
697 } else if let Some(ref schema) = row.schema {
698 for col in columns {
699 if let Some(idx) = schema.iter().position(|s| s == col) {
700 if let Some(value) = row.columns.get(idx) {
701 record.set(col, value.clone());
702 }
703 }
704 }
705 } else {
706 for col in columns {
707 if let Some(idx) = col.strip_prefix('c').and_then(|s| s.parse::<usize>().ok()) {
708 if let Some(value) = row.columns.get(idx) {
709 record.set(col, value.clone());
710 }
711 }
712 }
713 }
714 set_public_row_envelope(&mut record, entity, row);
715 fill_document_body_fields(
717 &mut record,
718 Some(columns),
719 document_row_body_bytes(row).as_deref(),
720 );
721 Some(record)
722}
723
724#[inline(never)]
725pub fn runtime_any_record_from_entity(entity: UnifiedEntity) -> Option<UnifiedRecord> {
726 let identity_entity = entity.clone();
727 let kind = entity.kind.clone();
728 let collection = kind.collection().to_string();
729 let storage_type = kind.storage_type().to_string();
730 let entity_id = entity.logical_id().raw();
731 let created_at = entity.created_at;
732 let updated_at = entity.updated_at;
733 let sequence_id = entity.sequence_id;
734
735 let (entity_type, capabilities, mut record) = match (kind, entity.data) {
736 (EntityKind::TableRow { row_id, .. }, EntityData::Row(row)) => {
737 let capabilities = runtime_row_capabilities(&row);
738 let entity_type = runtime_row_entity_type(&row);
739 let doc_body = document_row_body_bytes(&row);
740 let mut record = UnifiedRecord::new();
741 record.set_arc(sys_key_row_id(), Value::UnsignedInteger(row_id));
742 if let Some(named) = row.named {
743 for (key, value) in named {
744 record.set(&key, value);
745 }
746 } else {
747 for (index, value) in row.columns.into_iter().enumerate() {
748 record.set(&format!("c{index}"), value);
749 }
750 }
751 fill_document_body_fields(&mut record, None, doc_body.as_deref());
752 (entity_type, capabilities, record)
753 }
754 (EntityKind::GraphNode(node), EntityData::Node(node_data)) => {
755 let mut record = UnifiedRecord::new();
756 record.set("label", Value::text(node.label));
757 record.set("node_type", Value::text(node.node_type));
758 for (key, value) in node_data.properties {
759 record.set(&key, value);
760 }
761 set_public_graph_envelope(&mut record, &identity_entity, "node");
762 (
763 "graph_node",
764 runtime_record_capability_list(["graph", "graph_node"]),
765 record,
766 )
767 }
768 (EntityKind::GraphEdge(edge_kind), EntityData::Edge(edge)) => {
769 let mut record = UnifiedRecord::new();
770 record.set("label", Value::text(edge_kind.label));
771 record.set("from_rid", graph_endpoint_rid_value(&edge_kind.from_node));
772 record.set("to_rid", graph_endpoint_rid_value(&edge_kind.to_node));
773 record.set("weight", Value::Float(edge.weight as f64));
774 for (key, value) in edge.properties {
775 record.set(&key, value);
776 }
777 set_public_graph_envelope(&mut record, &identity_entity, "edge");
778 (
779 "graph_edge",
780 runtime_record_capability_list(["graph", "graph_edge"]),
781 record,
782 )
783 }
784 (EntityKind::Vector { .. }, EntityData::Vector(vector)) => {
785 let mut record = UnifiedRecord::new();
786 record.set(
787 "dimension",
788 Value::UnsignedInteger(vector.dense.len() as u64),
789 );
790 if let Some(content) = vector.content {
791 record.set("content", Value::text(content));
792 }
793 (
794 "vector",
795 runtime_record_capability_list(["vector", "similarity", "embedding"]),
796 record,
797 )
798 }
799 (EntityKind::TimeSeriesPoint(_), EntityData::TimeSeries(ts)) => {
800 let mut record = UnifiedRecord::new();
801 append_timeseries_record_fields(&mut record, &ts);
802 (
803 "timeseries",
804 runtime_record_capability_list(["document", "timeseries", "metric", "temporal"]),
805 record,
806 )
807 }
808 (EntityKind::QueueMessage { position, .. }, EntityData::QueueMessage(msg)) => {
809 let mut record = UnifiedRecord::new();
815 record.set("position", Value::UnsignedInteger(position));
816 record.set("payload", msg.payload);
817 record.set("attempts", Value::UnsignedInteger(msg.attempts as u64));
818 record.set("acked", Value::Boolean(msg.acked));
819 if let Some(priority) = msg.priority {
820 record.set("priority", Value::Integer(priority as i64));
821 }
822 (
823 "queue_message",
824 runtime_record_capability_list(["document", "queue", "message"]),
825 record,
826 )
827 }
828 _ => return None,
829 };
830
831 if let EntityData::Row(row) = &identity_entity.data {
832 set_public_row_envelope(&mut record, &identity_entity, row);
833 } else if matches!(
834 identity_entity.data,
835 EntityData::Node(_) | EntityData::Edge(_)
836 ) {
837 set_runtime_entity_metadata(&mut record, entity_type, capabilities);
838 apply_runtime_identity_hints(&mut record, &identity_entity);
839 } else {
840 record.set_arc(sys_key_rid(), Value::UnsignedInteger(entity_id));
844 record.set_arc(sys_key_red_collection(), Value::text(collection));
845 record.set_arc(sys_key_red_kind(), Value::text(storage_type));
846 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
847 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
848 record.set_arc(
849 sys_key_red_sequence_id(),
850 Value::UnsignedInteger(sequence_id),
851 );
852 set_runtime_entity_metadata(&mut record, entity_type, capabilities);
853 apply_runtime_identity_hints(&mut record, &identity_entity);
854 }
855
856 Some(record)
857}
858
859pub fn runtime_any_record_from_entity_ref(entity: &UnifiedEntity) -> Option<UnifiedRecord> {
860 let kind = &entity.kind;
861 let collection = kind.collection().to_string();
862 let storage_type = kind.storage_type().to_string();
863 let entity_id = entity.logical_id().raw();
864 let created_at = entity.created_at;
865 let updated_at = entity.updated_at;
866 let sequence_id = entity.sequence_id;
867
868 let (entity_type, capabilities, mut record) = match (kind, &entity.data) {
869 (EntityKind::TableRow { row_id, .. }, EntityData::Row(row)) => {
870 let capabilities = runtime_row_capabilities(row);
871 let entity_type = runtime_row_entity_type(row);
872 let doc_body = document_row_body_bytes(row);
873 let mut record = UnifiedRecord::new();
874 record.set_arc(sys_key_row_id(), Value::UnsignedInteger(*row_id));
875 if let Some(named) = row.named.as_ref() {
876 for (key, value) in named {
877 record.set(key, value.clone());
878 }
879 } else if let Some(schema) = row.schema.as_ref() {
880 for (name, value) in schema.iter().zip(row.columns.iter()) {
881 record.set(name, value.clone());
882 }
883 } else {
884 for (index, value) in row.columns.iter().enumerate() {
885 record.set(&format!("c{index}"), value.clone());
886 }
887 }
888 fill_document_body_fields(&mut record, None, doc_body.as_deref());
889 (entity_type, capabilities, record)
890 }
891 (EntityKind::GraphNode(node), EntityData::Node(node_data)) => {
892 let mut record = UnifiedRecord::new();
893 record.set("label", Value::text(node.label.clone()));
894 record.set("node_type", Value::text(node.node_type.clone()));
895 for (key, value) in &node_data.properties {
896 record.set(key, value.clone());
897 }
898 set_public_graph_envelope(&mut record, entity, "node");
899 (
900 "graph_node",
901 runtime_record_capability_list(["graph", "graph_node"]),
902 record,
903 )
904 }
905 (EntityKind::GraphEdge(edge_kind), EntityData::Edge(edge)) => {
906 let mut record = UnifiedRecord::new();
907 record.set("label", Value::text(edge_kind.label.clone()));
908 record.set("from_rid", graph_endpoint_rid_value(&edge_kind.from_node));
909 record.set("to_rid", graph_endpoint_rid_value(&edge_kind.to_node));
910 record.set("weight", Value::Float(edge.weight as f64));
911 for (key, value) in &edge.properties {
912 record.set(key, value.clone());
913 }
914 set_public_graph_envelope(&mut record, entity, "edge");
919 (
920 "graph_edge",
921 runtime_record_capability_list(["graph", "graph_edge"]),
922 record,
923 )
924 }
925 (EntityKind::Vector { .. }, EntityData::Vector(vector)) => {
926 let mut record = UnifiedRecord::new();
927 record.set(
928 "dimension",
929 Value::UnsignedInteger(vector.dense.len() as u64),
930 );
931 if let Some(content) = vector.content.as_ref() {
932 record.set("content", Value::text(content.clone()));
933 }
934 (
935 "vector",
936 runtime_record_capability_list(["vector", "similarity", "embedding"]),
937 record,
938 )
939 }
940 (EntityKind::TimeSeriesPoint(_), EntityData::TimeSeries(ts)) => {
941 let mut record = UnifiedRecord::new();
942 append_timeseries_record_fields(&mut record, ts);
943 (
944 "timeseries",
945 runtime_record_capability_list(["document", "timeseries", "metric", "temporal"]),
946 record,
947 )
948 }
949 (EntityKind::QueueMessage { position, .. }, EntityData::QueueMessage(msg)) => {
950 let mut record = UnifiedRecord::new();
951 record.set("position", Value::UnsignedInteger(*position));
952 record.set("payload", msg.payload.clone());
953 record.set("attempts", Value::UnsignedInteger(msg.attempts as u64));
954 record.set("acked", Value::Boolean(msg.acked));
955 if let Some(priority) = msg.priority {
956 record.set("priority", Value::Integer(priority as i64));
957 }
958 (
959 "queue_message",
960 runtime_record_capability_list(["document", "queue", "message"]),
961 record,
962 )
963 }
964 _ => return None,
965 };
966
967 if let EntityData::Row(row) = &entity.data {
968 set_public_row_envelope(&mut record, entity, row);
969 } else if matches!(entity.data, EntityData::Node(_) | EntityData::Edge(_)) {
970 set_runtime_entity_metadata(&mut record, entity_type, capabilities);
971 apply_runtime_identity_hints(&mut record, entity);
972 } else {
973 record.set_arc(sys_key_rid(), Value::UnsignedInteger(entity_id));
977 record.set_arc(sys_key_red_collection(), Value::text(collection));
978 record.set_arc(sys_key_red_kind(), Value::text(storage_type));
979 record.set_arc_if_absent(sys_key_created_at(), Value::UnsignedInteger(created_at));
980 record.set_arc_if_absent(sys_key_updated_at(), Value::UnsignedInteger(updated_at));
981 record.set_arc(
982 sys_key_red_sequence_id(),
983 Value::UnsignedInteger(sequence_id),
984 );
985 set_runtime_entity_metadata(&mut record, entity_type, capabilities);
986 apply_runtime_identity_hints(&mut record, entity);
987 }
988
989 Some(record)
990}
991
992fn append_timeseries_record_fields(
993 record: &mut UnifiedRecord,
994 ts: &crate::storage::entity::TimeSeriesData,
995) {
996 record.set("metric", Value::text(ts.metric.clone()));
997 record.set("timestamp_ns", Value::UnsignedInteger(ts.timestamp_ns));
998 record.set("timestamp", Value::UnsignedInteger(ts.timestamp_ns));
999 record.set("time", Value::UnsignedInteger(ts.timestamp_ns));
1000 record.set("value", Value::Float(ts.value));
1001 if !ts.tags.is_empty() {
1002 record.set("tags", timeseries_tags_value(&ts.tags));
1003 }
1004 for (key, value) in &ts.fields {
1005 record.set(key, value.clone());
1006 }
1007}
1008
1009fn timeseries_tags_value(tags: &std::collections::HashMap<String, String>) -> Value {
1010 let object = tags
1011 .iter()
1012 .map(|(key, value)| (key.clone(), decode_stored_tag_value(value)))
1013 .collect();
1014 let json = crate::json::Value::Object(object);
1015 Value::Json(crate::json::to_vec(&json).unwrap_or_default())
1016}
1017
1018#[inline(never)]
1019pub fn set_runtime_entity_metadata(
1020 record: &mut UnifiedRecord,
1021 entity_type: &str,
1022 capabilities: BTreeSet<String>,
1023) {
1024 let capabilities_text = capabilities.into_iter().collect::<Vec<_>>().join(",");
1025 record.set_arc(
1026 sys_key_red_entity_type(),
1027 Value::text(entity_type.to_string()),
1028 );
1029 record.set_arc(sys_key_red_capabilities(), Value::text(capabilities_text));
1030}
1031
1032pub fn runtime_record_capability_list<const N: usize>(values: [&str; N]) -> BTreeSet<String> {
1033 values.into_iter().map(|value| value.to_string()).collect()
1034}
1035
1036pub fn runtime_row_capabilities(row: &crate::storage::entity::RowData) -> BTreeSet<String> {
1037 let mut capabilities = runtime_record_capability_list(["table", "structured"]);
1038 if runtime_row_is_kv(row) {
1039 capabilities.insert("kv".to_string());
1040 }
1041 if runtime_row_has_document_capability(row) {
1042 capabilities.insert("document".to_string());
1043 }
1044 capabilities
1045}
1046
1047pub fn runtime_row_capabilities_str(row: &crate::storage::entity::RowData) -> &'static str {
1050 let is_kv = runtime_row_is_kv(row);
1051 let is_doc = runtime_row_has_document_capability(row);
1052 match (is_kv, is_doc) {
1053 (false, false) => "structured,table",
1054 (true, false) => "kv,structured,table",
1055 (false, true) => "document,structured,table",
1056 (true, true) => "document,kv,structured,table",
1057 }
1058}
1059
1060pub fn runtime_row_entity_type(row: &crate::storage::entity::RowData) -> &'static str {
1061 if runtime_row_is_kv(row) {
1062 return "kv";
1063 }
1064
1065 if runtime_row_has_document_capability(row) {
1066 "document"
1067 } else {
1068 "table"
1069 }
1070}
1071
1072fn runtime_row_is_kv(row: &crate::storage::entity::RowData) -> bool {
1073 let Some(named) = row.named.as_ref() else {
1074 return false;
1075 };
1076
1077 if named.len() == 2 {
1078 named.contains_key("key") && named.contains_key("value")
1079 } else if named.len() == 1 {
1080 named.contains_key("key") || named.contains_key("value")
1081 } else {
1082 false
1083 }
1084}
1085
1086pub fn runtime_row_has_document_capability(row: &crate::storage::entity::RowData) -> bool {
1087 row.named
1088 .as_ref()
1089 .map(|named| named.values().any(runtime_documentish_value))
1090 .unwrap_or(false)
1091 || row.columns.iter().any(runtime_documentish_value)
1092}
1093
1094pub fn runtime_documentish_value(value: &Value) -> bool {
1095 matches!(value, Value::Json(_) | Value::Blob(_))
1096}
1097
1098pub fn runtime_entity_type_and_capabilities(
1099 entity: &UnifiedEntity,
1100) -> (&'static str, BTreeSet<String>) {
1101 match (&entity.kind, &entity.data) {
1102 (EntityKind::TableRow { .. }, EntityData::Row(row)) => {
1103 (runtime_row_entity_type(row), runtime_row_capabilities(row))
1104 }
1105 (EntityKind::GraphNode(_), EntityData::Node(_)) => (
1106 "graph_node",
1107 runtime_record_capability_list(["graph", "graph_node"]),
1108 ),
1109 (EntityKind::GraphEdge(_), EntityData::Edge(_)) => (
1110 "graph_edge",
1111 runtime_record_capability_list(["graph", "graph_edge"]),
1112 ),
1113 (EntityKind::Vector { .. }, EntityData::Vector(_)) => (
1114 "vector",
1115 runtime_record_capability_list(["vector", "similarity", "embedding"]),
1116 ),
1117 (EntityKind::TimeSeriesPoint(_), EntityData::TimeSeries(_)) => (
1118 "timeseries",
1119 runtime_record_capability_list(["document", "timeseries", "metric", "temporal"]),
1120 ),
1121 (EntityKind::QueueMessage { .. }, EntityData::QueueMessage(_)) => (
1122 "queue_message",
1123 runtime_record_capability_list(["document", "queue", "message"]),
1124 ),
1125 _ => ("unknown", BTreeSet::new()),
1126 }
1127}
1128
1129pub fn apply_runtime_identity_hints(record: &mut UnifiedRecord, entity: &UnifiedEntity) {
1130 for cross_ref in entity.cross_refs() {
1131 let value = match cross_ref.ref_type {
1132 RefType::VectorToRow | RefType::NodeToRow => Some(Value::RowRef(
1133 cross_ref.target_collection.clone(),
1134 cross_ref.target.raw(),
1135 )),
1136 RefType::VectorToNode | RefType::RowToNode => Some(Value::NodeRef(format!(
1137 "{}:{}",
1138 cross_ref.target_collection, cross_ref.target
1139 ))),
1140 RefType::RowToEdge | RefType::EdgeToVector => Some(Value::EdgeRef(format!(
1141 "{}:{}",
1142 cross_ref.target_collection, cross_ref.target
1143 ))),
1144 _ => Some(Value::text(format!(
1145 "{}:{}",
1146 cross_ref.target_collection, cross_ref.target
1147 ))),
1148 };
1149
1150 if let Some(value) = value {
1151 let link_key: std::sync::Arc<str> = std::sync::Arc::from("_linked_identity");
1152 match cross_ref.ref_type {
1153 RefType::VectorToRow | RefType::NodeToRow => {
1154 record.set_arc(std::sync::Arc::from("_source_row"), value.clone());
1155 record.overflow_entry_or_insert(link_key, value);
1156 }
1157 RefType::VectorToNode | RefType::RowToNode => {
1158 record.set_arc(std::sync::Arc::from("_source_node"), value.clone());
1159 record.overflow_entry_or_insert(link_key, value);
1160 }
1161 RefType::RowToEdge | RefType::EdgeToVector => {
1162 record.set_arc(std::sync::Arc::from("_source_edge"), value.clone());
1163 record.overflow_entry_or_insert(link_key, value);
1164 }
1165 _ => {
1166 record.overflow_entry_or_insert(
1167 std::sync::Arc::from("_source_entity"),
1168 value.clone(),
1169 );
1170 record.overflow_entry_or_insert(link_key, value);
1171 }
1172 }
1173 }
1174 }
1175}