use crate::error::CaResult;
use crate::types::{DbFieldType, EpicsValue, PvString, c_parse};
use super::scan::ScanType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RawSoftEntry {
InitConstant,
Read,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Special {
None,
NoMod,
DbAddr,
Scan,
AlarmAck,
As,
Attribute,
Mod,
Reset,
LinConv,
Calc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Asl {
Asl0,
Asl1,
}
#[derive(Debug, Clone)]
pub struct FieldDesc {
pub name: &'static str,
pub dbf_type: DbFieldType,
pub runtime_typed: bool,
pub read_only: bool,
pub special: Special,
pub pp: bool,
pub asl: Asl,
pub size: u16,
pub menu: Option<&'static [&'static str]>,
pub initial: Option<&'static str>,
pub interest: u8,
pub prop: bool,
}
impl FieldDesc {
pub const fn new(name: &'static str, dbf_type: DbFieldType, read_only: bool) -> Self {
Self {
name,
dbf_type,
runtime_typed: false,
read_only,
special: if read_only {
Special::NoMod
} else {
Special::None
},
pp: false,
asl: Asl::Asl1,
size: 0,
menu: None,
initial: None,
interest: 0,
prop: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConstantInitLink {
pub link_field: &'static str,
pub target_field: &'static str,
pub clears_udf: bool,
pub normalize_bool: bool,
}
impl ConstantInitLink {
pub const fn new(link_field: &'static str, target_field: &'static str) -> Self {
Self {
link_field,
target_field,
clears_udf: false,
normalize_bool: false,
}
}
pub const fn dol_to_val(link_field: &'static str, target_field: &'static str) -> Self {
Self {
link_field,
target_field,
clears_udf: true,
normalize_bool: false,
}
}
pub const fn dol_to_bool_val(link_field: &'static str, target_field: &'static str) -> Self {
Self {
link_field,
target_field,
clears_udf: true,
normalize_bool: true,
}
}
}
pub fn seed_input_links(pairs: &[(&'static str, &'static str)]) -> Vec<ConstantInitLink> {
pairs
.iter()
.map(|(link, value)| ConstantInitLink::new(link, value))
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutTarget {
pub field_type: Option<DbFieldType>,
pub element_count: i64,
pub is_ca_link: bool,
pub puts_as_string: bool,
}
impl OutTarget {
pub const UNRESOLVED: Self = Self {
field_type: None,
element_count: 1,
is_ca_link: false,
puts_as_string: false,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkReadAs {
Native,
String,
Double,
CharArrayAsString { max_elements: usize },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValuePostGate {
OnChange,
WithValue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CyclePostMask {
Value,
ValueLog,
MonitorValueLog,
}
pub(crate) fn value_gate(
value_masked: &'static [(&'static str, ValuePostGate)],
field: &str,
) -> Option<ValuePostGate> {
value_masked
.iter()
.find(|(name, _)| *name == field)
.map(|(_, gate)| *gate)
}
#[derive(Clone, Copy)]
pub(crate) struct AuxPostMask {
value_only: &'static [&'static str],
monitor_masked: &'static [&'static str],
no_alarm_bits: &'static [&'static str],
}
impl AuxPostMask {
pub(crate) fn of(record: &dyn Record) -> Self {
Self {
value_only: record.value_only_change_fields(),
monitor_masked: record.fields_posted_with_monitor_mask(),
no_alarm_bits: record.fields_posted_without_alarm_bits(),
}
}
pub(crate) fn mask_for(
&self,
field: &str,
alarm_bits: crate::server::recgbl::EventMask,
deadband_mask: crate::server::recgbl::EventMask,
) -> crate::server::recgbl::EventMask {
use crate::server::recgbl::EventMask;
if self.value_only.contains(&field) {
alarm_bits | EventMask::VALUE
} else if self.monitor_masked.contains(&field) {
deadband_mask | EventMask::VALUE
} else if self.no_alarm_bits.contains(&field) {
EventMask::VALUE | EventMask::LOG
} else {
alarm_bits | EventMask::VALUE | EventMask::LOG
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ArrayMonitorPost {
pub post_value: bool,
pub post_archive: bool,
pub hash_changed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RsetDefaultArm {
RecGblRange,
Seed,
}
pub fn control_default_arm(rtype: &str) -> RsetDefaultArm {
match rtype {
"acalcout" | "scalcout" => RsetDefaultArm::Seed,
_ => RsetDefaultArm::RecGblRange,
}
}
pub fn graphic_default_arm(rtype: &str) -> RsetDefaultArm {
match rtype {
"aSub" | "acalcout" | "scalcout" => RsetDefaultArm::Seed,
_ => RsetDefaultArm::RecGblRange,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlarmValArm {
Gated,
Unconditional,
}
pub fn alarm_explicit_fields(rtype: &str) -> &'static [&'static str] {
match rtype {
"seq" | "aSub" | "swait" => &[],
"motor" => &["VAL", "DVAL"],
_ => &["VAL"],
}
}
pub fn alarm_val_arm(rtype: &str) -> AlarmValArm {
match rtype {
"int64in" | "int64out" | "scalcout" | "acalcout" | "motor" | "epid" => {
AlarmValArm::Unconditional
}
_ => AlarmValArm::Gated,
}
}
pub fn default_property_support(rtype: &str) -> crate::server::snapshot::PropertySupport {
use crate::server::snapshot::PropertySupport as P;
match rtype {
"ai" | "ao" | "calc" | "calcout" | "sel" | "sub" | "dfanout" | "seq" => P::NUMERIC,
"longin" | "longout" | "int64in" | "int64out" => P {
precision: false,
..P::NUMERIC
},
"waveform" | "aai" | "aao" | "subArray" | "compress" | "histogram" => P {
alarm_double: false,
..P::NUMERIC
},
"stringin" | "stringout" | "lsi" | "lso" | "event" | "permissive" | "state" | "printf"
| "fanout" | "timestamp" => P::NONE,
"bi" | "mbbi" | "mbbo" => P {
enum_strs: true,
..P::NONE
},
"bo" => P {
units: true,
precision: true,
control_double: true,
enum_strs: true,
..P::NONE
},
"busy" => P {
precision: true,
enum_strs: true,
..P::NONE
},
"mbbiDirect" | "mbboDirect" => P {
precision: true,
..P::NONE
},
"scalcout" | "acalcout" | "motor" | "epid" | "aSub" => P::NUMERIC,
"scaler" => P {
precision: true,
..P::NONE
},
"table" => P {
alarm_double: false,
..P::NUMERIC
},
"swait" => P {
units: false,
control_double: false,
..P::NUMERIC
},
"transform" | "sseq" => P {
precision: true,
..P::NONE
},
"throttle" => P {
precision: true,
graphic_double: true,
..P::NONE
},
_ => P::NUMERIC,
}
}
#[derive(Debug, Clone, Default)]
pub struct FieldMetadataOverride {
pub units: Option<crate::types::PvString>,
pub precision: Option<i16>,
pub disp_limits: Option<(f64, f64)>,
pub ctrl_limits: Option<(f64, f64)>,
pub alarm_limits: Option<(f64, f64, f64, f64)>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ProcessAction {
WriteDbLink {
link_field: &'static str,
value: EpicsValue,
},
ResolveOutTarget { link_field: &'static str },
ReadDbLink {
link_field: &'static str,
target_field: &'static str,
},
ReprocessAfter(std::time::Duration),
ScanOnce,
DeviceCommand {
command: &'static str,
args: Vec<EpicsValue>,
},
WriteDbLinkNotify {
link_field: &'static str,
value: EpicsValue,
},
ArmWatchdog,
CancelReprocess,
}
#[derive(Clone, Debug, PartialEq)]
pub enum RecordProcessResult {
Complete,
AsyncPending,
AsyncPendingNotify(Vec<(String, EpicsValue)>),
CompleteNoEmit,
CompleteDeferOutput,
CompleteAlarmOnly,
}
#[derive(Clone, Debug)]
pub struct ProcessOutcome {
pub result: RecordProcessResult,
pub actions: Vec<ProcessAction>,
pub device_did_compute: bool,
}
impl ProcessOutcome {
pub fn complete() -> Self {
Self {
result: RecordProcessResult::Complete,
actions: Vec::new(),
device_did_compute: false,
}
}
pub fn complete_with(actions: Vec<ProcessAction>) -> Self {
Self {
result: RecordProcessResult::Complete,
actions,
device_did_compute: false,
}
}
pub fn complete_no_emit() -> Self {
Self {
result: RecordProcessResult::CompleteNoEmit,
actions: Vec::new(),
device_did_compute: false,
}
}
pub fn complete_alarm_only() -> Self {
Self {
result: RecordProcessResult::CompleteAlarmOnly,
actions: Vec::new(),
device_did_compute: false,
}
}
pub fn async_pending() -> Self {
Self {
result: RecordProcessResult::AsyncPending,
actions: Vec::new(),
device_did_compute: false,
}
}
}
impl Default for ProcessOutcome {
fn default() -> Self {
Self::complete()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CommonFieldPutResult {
NoChange,
ScanChanged {
old_scan: ScanType,
new_scan: ScanType,
phas: i16,
},
PhasChanged {
scan: ScanType,
old_phas: i16,
new_phas: i16,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct ProcessContext {
pub udf: bool,
pub udfs: crate::server::record::AlarmSeverity,
pub nsev: crate::server::record::AlarmSeverity,
pub phas: i16,
pub tse: i16,
pub time: std::time::SystemTime,
pub tsel: String,
pub dtyp: String,
}
pub const EPICS_TIME_EVENT_DEVICE_TIME: i16 = -2;
pub struct ProcessSnapshot {
pub changed_fields: Vec<(String, EpicsValue, crate::server::recgbl::EventMask)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputFetchPolicy {
ReadAll,
ReadAllGateOnFailure,
AbortOnFirstFailure,
}
pub trait FieldDeclaration {
fn field_list(&self) -> &'static [FieldDesc];
}
impl<R: Record + ?Sized> FieldDeclaration for R {
fn field_list(&self) -> &'static [FieldDesc] {
super::dbd_generated::record_fields(self.record_type())
.unwrap_or_else(|| self.declared_fields())
}
}
pub trait Record: Send + Sync + 'static {
fn record_type(&self) -> &'static str;
fn process(&mut self) -> CaResult<ProcessOutcome> {
Ok(ProcessOutcome::complete())
}
fn took_metadata_change(&mut self) -> bool {
false
}
fn get_field(&self, name: &str) -> Option<EpicsValue>;
fn put_field(&mut self, name: &str, value: EpicsValue) -> CaResult<()>;
fn declared_fields(&self) -> &'static [FieldDesc] {
&[]
}
fn implements_field(&self, name: &str) -> bool {
self.get_field(name).is_some()
}
fn field_no_mod(&self, _field: &str) -> bool {
false
}
fn menu_field_choices(&self, _field: &str) -> Option<&'static [&'static str]> {
None
}
fn field_metadata_override(&self, _field: &str) -> Option<FieldMetadataOverride> {
None
}
fn property_support(&self) -> crate::server::snapshot::PropertySupport {
default_property_support(self.record_type())
}
fn long_string_fields(&self) -> &'static [&'static str] {
&[]
}
fn process_passive_fields(&self) -> &'static [&'static str] {
super::process_passive::pp_fields_for(self.record_type())
}
fn processes_after_put(&self, field: &str) -> bool {
self.process_passive_fields()
.iter()
.any(|f| f.eq_ignore_ascii_case(field))
}
fn enum_state_strings(&self) -> Option<Vec<PvString>> {
None
}
fn enum_string_form(&self) -> Option<crate::server::snapshot::EnumStringForm> {
None
}
fn validate_put(&self, _field: &str, _value: &EpicsValue) -> CaResult<()> {
Ok(())
}
fn on_put(&mut self, _field: &str) {}
fn is_subroutine_name_field(&self, _field: &str) -> bool {
false
}
fn primary_field(&self) -> &'static str {
"VAL"
}
fn is_udf_defining_put(&self, field: &str) -> bool {
field == self.primary_field()
}
fn val(&self) -> Option<EpicsValue> {
self.get_field(self.primary_field())
}
fn set_val(&mut self, value: EpicsValue) -> CaResult<()> {
let field = self.primary_field();
self.put_field_internal(field, value)
}
fn input_read_by_device_support(&self) -> bool {
true
}
fn soft_input_dset_init(&mut self, loaded: bool) {
let _ = loaded;
}
fn raw_soft_input(&mut self, entry: RawSoftEntry, value: EpicsValue) -> Option<CaResult<()>> {
let _ = (entry, value);
None
}
fn raw_soft_output_value(&self) -> Option<EpicsValue> {
None
}
fn apply_raw_readback(&mut self, _raw: i32) -> bool {
false
}
fn apply_float64_readback(&mut self, _raw: f64) -> bool {
false
}
fn install_breaktable_registry(
&mut self,
_registry: std::sync::Arc<crate::server::cvt_bpt::BreakTableRegistry>,
) {
}
fn apply_invalid_output_value(&mut self, ivov: EpicsValue) -> CaResult<()> {
self.set_val(ivov)
}
fn can_device_write(&self) -> bool {
matches!(
self.record_type(),
"ao" | "bo"
| "longout"
| "int64out"
| "mbbo"
| "mbboDirect"
| "stringout"
| "lso"
| "printf"
| "aao"
)
}
fn is_put_complete(&self) -> bool {
true
}
fn should_fire_forward_link(&self) -> bool {
true
}
fn restamps_time_after_completion(&self) -> bool {
false
}
fn should_output(&self) -> bool {
true
}
fn on_output_complete(&mut self) {}
fn uses_monitor_deadband(&self) -> bool {
true
}
fn process_posts_value_monitor(&self) -> bool {
true
}
fn monitor_value_changed(&self) -> Option<bool> {
None
}
fn monitor_always_post(&self) -> (bool, bool) {
(false, false)
}
fn monitor_deadband_value(&self) -> Option<EpicsValue> {
self.val()
}
fn monitor_deadband_field(&self) -> &'static str {
self.primary_field()
}
fn alarm_cycle_monitored_fields(&self) -> &'static [&'static str] {
&[]
}
fn force_posted_fields(&self) -> &'static [&'static str] {
&[]
}
fn take_cycle_posted_fields(&mut self) -> Vec<(&'static str, CyclePostMask)> {
Vec::new()
}
fn fields_posted_only_when_marked(&self) -> &'static [&'static str] {
&[]
}
fn log_swept_fields(&self) -> &'static [&'static str] {
&[]
}
fn value_only_change_fields(&self) -> &'static [&'static str] {
&[]
}
fn fields_posted_with_value_mask(&self) -> &'static [(&'static str, ValuePostGate)] {
&[]
}
fn fields_posted_with_monitor_mask(&self) -> &'static [&'static str] {
&[]
}
fn fields_posted_without_alarm_bits(&self) -> &'static [&'static str] {
&[]
}
fn array_monitor_post(&mut self) -> Option<ArrayMonitorPost> {
None
}
fn process_posted_fields(&self) -> Option<&'static [&'static str]> {
None
}
fn event_posted_fields(&self) -> &'static [&'static str] {
&[]
}
fn init_record(&mut self, _pass: u8) -> CaResult<()> {
Ok(())
}
fn init_record_parks_pact(&self) -> bool {
false
}
fn post_init_finalize_undef(&mut self, _udf: &mut bool) -> CaResult<()> {
Ok(())
}
fn init_resets_alarms(&self) -> bool {
false
}
fn field_native_count(&self, _field: &str) -> Option<u32> {
None
}
fn seed_deadband_tracking(&mut self) {
let seed = match self.monitor_deadband_value().and_then(|v| v.to_f64()) {
Some(v) if v.is_finite() => v,
_ => return,
};
for field in ["MLST", "ALST", "LALM"] {
if self.get_field(field).is_some() {
let _ = self.put_field(field, EpicsValue::Double(seed));
}
}
}
fn set_resolved_input_links(&mut self, _resolved: &[&'static str]) {}
fn set_fetch_gate_failed(&mut self, _failed: bool) {}
fn set_subroutine_status(&mut self, _status: i64) {}
fn special(&mut self, _field: &str, _after: bool) -> CaResult<()> {
Ok(())
}
fn watchdog_interval(&self) -> Option<std::time::Duration> {
None
}
fn watchdog_fire(&mut self) -> &'static [&'static str] {
&[]
}
fn uses_recgbl_simm_helpers(&self) -> bool {
crate::server::recgbl::simm::record_type_has_sscn(self.record_type())
}
fn aborts_on_failed_siml_read(&self) -> bool {
false
}
fn set_io_intr_scan(&mut self, _active: bool) {}
fn take_special_actions(&mut self) -> Vec<ProcessAction> {
Vec::new()
}
fn monitor_side_effect_fields(&self, _put_field: &str) -> &'static [&'static str] {
&[]
}
fn special_commits_alarms(&self, _put_field: &str) -> bool {
false
}
fn special_checks_alarms(&self, _put_field: &str) -> bool {
false
}
fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
None
}
fn clears_udf(&self) -> bool {
true
}
fn clears_udf_unconditionally(&self) -> bool {
false
}
fn declares_inp_link(&self) -> bool {
true
}
fn read_constant_inp(&mut self, _value: Option<EpicsValue>) -> bool {
false
}
fn raises_udf_alarm(&self) -> bool {
true
}
fn udf_alarm_on_exact_one(&self) -> bool {
false
}
fn udf_alarm_message(&self) -> &str {
""
}
fn value_is_undefined(&self) -> bool {
match self.val() {
Some(EpicsValue::Double(v)) => v.is_nan(),
Some(EpicsValue::Float(v)) => v.is_nan(),
Some(_) => false,
None => true,
}
}
fn check_alarms(&mut self, _common: &mut crate::server::record::CommonFields) {}
fn multi_input_links(&self) -> &[(&'static str, &'static str)] {
&[]
}
fn special_reseed_input_links(&self) -> &[(&'static str, &'static str)] {
&[]
}
fn special_reseed_post_mask(&self) -> crate::server::recgbl::EventMask {
crate::server::recgbl::EventMask::VALUE
}
fn constant_init_links(&self) -> Vec<ConstantInitLink> {
Vec::new()
}
fn constant_ls_link(&self) -> Option<&'static str> {
None
}
fn apply_ls_load(&mut self, _load: crate::server::record::LsLoad) -> u32 {
0
}
fn constant_inputs_deliver_at_process(&self) -> bool {
false
}
fn select_input_links(
&self,
_selector: Option<u16>,
) -> Option<Vec<(&'static str, &'static str)>> {
None
}
fn simulation_substitutes_input_stage(&self) -> bool {
false
}
fn land_simulated_value(&mut self, value: EpicsValue) -> CaResult<()> {
self.set_val(value)
}
fn rejects_illegal_sim_mode(&self) -> bool {
true
}
fn set_simulation_active(&mut self, _active: bool) {}
fn input_fetch_policy(&self) -> InputFetchPolicy {
InputFetchPolicy::ReadAll
}
fn string_input_links(&self) -> &'static [(&'static str, &'static str)] {
&[]
}
fn output_time_input_links(&self) -> &'static [(&'static str, &'static str)] {
&[]
}
fn output_link_value(&self) -> Option<EpicsValue> {
self.get_field("OVAL").or_else(|| self.val())
}
fn multi_output_links(&self) -> &[(&'static str, &'static str)] {
&[]
}
fn multi_output_buffer(
&self,
link_field: &str,
staged: EpicsValue,
target: &OutTarget,
) -> EpicsValue {
let _ = (link_field, target);
staged
}
fn typed_output_buffer(&self, link_field: &str, target: &OutTarget) -> Option<EpicsValue> {
let _ = (link_field, target);
None
}
fn set_resolved_out_target(&mut self, link_field: &str, target: OutTarget) {
let _ = (link_field, target);
}
fn input_link_read_as(&self, link_field: &str, source: &OutTarget) -> Option<LinkReadAs> {
let _ = (link_field, source);
Some(LinkReadAs::Native)
}
fn output_event(&self) -> Option<String> {
None
}
fn put_field_internal(&mut self, name: &str, value: EpicsValue) -> CaResult<()> {
put_field_internal_default(self, name, value)
}
fn pre_process_actions(&mut self) -> Vec<ProcessAction> {
Vec::new()
}
fn pre_input_link_actions(&mut self) -> Vec<ProcessAction> {
Vec::new()
}
fn set_process_context(&mut self, _ctx: &ProcessContext) {}
fn set_async_context(&mut self, _name: String, _db: crate::server::database::AsyncDbHandle) {}
fn init_links(&mut self, _common: &crate::server::record::CommonFields) {}
fn set_device_did_compute(&mut self, _did_compute: bool) {}
fn soft_channel_skips_convert(&self) -> bool {
false
}
fn skips_forward_convert_when_undefined(&self) -> bool {
false
}
fn skips_timestamp_when_undefined(&self) -> bool {
false
}
}
pub fn put_field_internal_default<R: Record + ?Sized>(
record: &mut R,
name: &str,
value: EpicsValue,
) -> CaResult<()> {
let target_type = record
.get_field(name)
.map(|v| v.db_field_type())
.or_else(|| crate::server::record::record_instance::declared_field_type_of(record, name));
let dest_is_array = record.get_field(name).is_some_and(|v| v.is_array());
let is_char_string_view =
matches!(value, EpicsValue::CharArray(_)) && target_type == Some(DbFieldType::String);
let value = if !dest_is_array && value.is_array() && !is_char_string_view {
value.first_element().unwrap_or(value)
} else {
value
};
let is_enum_carrier = matches!(value, EpicsValue::EnumWithChoices { .. });
let value = match target_type {
Some(target)
if is_enum_carrier
|| ((value.db_field_type() != target || target == DbFieldType::String)
&& !value.is_empty_array()) =>
{
coerce_put_value(record, name, target, value)?
}
None if is_enum_carrier => value.convert_to(DbFieldType::Long),
_ => value,
};
record.put_field(name, value)
}
pub fn coerce_put_value<R: Record + ?Sized>(
record: &R,
field: &str,
target: DbFieldType,
value: EpicsValue,
) -> CaResult<EpicsValue> {
if let EpicsValue::String(s) = &value {
if field.eq_ignore_ascii_case("DTYP") {
let choices = super::merged_device_menu(record.record_type());
if !choices.is_empty() {
return super::resolve_menu_field_string(
field,
&choices,
target,
&s.as_str_lossy(),
);
}
} else if let Some(choices) = super::record_instance::menu_choices_of(record, field) {
return super::resolve_menu_field_string(field, choices, target, &s.as_str_lossy());
}
if target == DbFieldType::Enum {
return super::resolve_enum_state_string(
field,
record.enum_state_strings().as_deref(),
s,
);
}
let dest_is_array = record.get_field(field).is_some_and(|v| v.is_array());
if !dest_is_array {
if let Some(numeric) = c_parse::NumericField::of(target) {
return c_parse::put_string(field, numeric, &s.as_str_lossy());
}
if target == DbFieldType::String {
return Ok(EpicsValue::String(cap_string_to_field_size(
record, field, s,
)));
}
}
}
Ok(value.convert_to(target))
}
fn cap_string_to_field_size<R: Record + ?Sized>(record: &R, field: &str, s: &PvString) -> PvString {
match super::record_instance::field_desc_of(record, field) {
Some(desc) if desc.size > 0 => {
let cap = (desc.size as usize).saturating_sub(1);
let bytes = s.as_bytes();
if bytes.len() > cap {
PvString::from_bytes(bytes[..cap].to_vec())
} else {
s.clone()
}
}
_ => s.clone(),
}
}
pub type SubroutineFn = Box<dyn Fn(&mut dyn Record) -> CaResult<i64> + Send + Sync>;