Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,266 @@
|
||||
use dever_runtime::async_stream::AsyncStream;
|
||||
use dever_runtime::channel::Channel;
|
||||
use dever_runtime::task::{Group, Limits, Runtime, Scope, Task};
|
||||
|
||||
struct ConcurrencyCase {
|
||||
runtime: Runtime,
|
||||
values: Vec<i64>,
|
||||
limit: i64,
|
||||
}
|
||||
|
||||
fn concurrency_fault() -> Fault {
|
||||
&FaultInfo {
|
||||
message: "extended concurrency failed",
|
||||
source: "extended-concurrency-reference",
|
||||
line: 1,
|
||||
column: 1,
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelOutput = (Vec<Option<i64>>, bool);
|
||||
|
||||
fn channel_generated(values: &[ConcurrencyCase]) -> Result<ChannelOutput, Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok((Vec::new(), true));
|
||||
};
|
||||
value
|
||||
.runtime
|
||||
.run(async |scope| EXT_CHANNEL(&value.values, value.limit, scope).await)
|
||||
.map_err(|_| concurrency_fault())?
|
||||
.map_err(|_| concurrency_fault())
|
||||
}
|
||||
|
||||
async fn send_values(values: Vec<i64>, channel: Channel<i64>) -> Result<(), Fault> {
|
||||
for value in values {
|
||||
channel.send(value).await.map_err(|_| concurrency_fault())?;
|
||||
}
|
||||
channel.close().await.map_err(|_| concurrency_fault())
|
||||
}
|
||||
|
||||
fn channel_reference(values: &[ConcurrencyCase]) -> Result<ChannelOutput, Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok((Vec::new(), true));
|
||||
};
|
||||
value
|
||||
.runtime
|
||||
.run(async |scope| {
|
||||
let channel = Channel::new(value.limit).map_err(|_| concurrency_fault())?;
|
||||
let producer = channel.clone();
|
||||
let owned = value.values.clone();
|
||||
let pending = scope
|
||||
.spawn_leaf(move || send_values(owned, producer))
|
||||
.map_err(|_| concurrency_fault())?;
|
||||
let mut received = Vec::with_capacity(value.values.len());
|
||||
for _ in &value.values {
|
||||
received.push(channel.receive().await.map_err(|_| concurrency_fault())?);
|
||||
}
|
||||
pending.wait().await.map_err(|_| concurrency_fault())??;
|
||||
let ended = channel
|
||||
.receive()
|
||||
.await
|
||||
.map_err(|_| concurrency_fault())?
|
||||
.is_none();
|
||||
Ok((received, ended))
|
||||
})
|
||||
.map_err(|_| concurrency_fault())?
|
||||
}
|
||||
|
||||
fn channel_score(value: &ChannelOutput) -> u64 {
|
||||
value.0.iter().fold(u64::from(value.1), |score, item| {
|
||||
score
|
||||
.wrapping_mul(31)
|
||||
.wrapping_add(item.unwrap_or(0) as u64)
|
||||
})
|
||||
}
|
||||
|
||||
async fn stream_total(stream: &AsyncStream<i64>, limit: Option<i64>) -> Result<i64, Fault> {
|
||||
let _close = stream.close_on_drop();
|
||||
let mut total = 0i64;
|
||||
while let Some(value) = stream.next().await.map_err(|_| concurrency_fault())? {
|
||||
total = total.checked_add(value).ok_or_else(concurrency_fault)?;
|
||||
if limit.is_some_and(|limit| value >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn pull_generated(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok((0, 0));
|
||||
};
|
||||
value
|
||||
.runtime
|
||||
.run_leaf(EXT_PULL(&value.values))
|
||||
.map_err(|_| concurrency_fault())?
|
||||
.map_err(|_| concurrency_fault())
|
||||
}
|
||||
|
||||
fn prefix_generated(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok((0, 0));
|
||||
};
|
||||
value
|
||||
.runtime
|
||||
.run_leaf(EXT_PREFIX(&value.values))
|
||||
.map_err(|_| concurrency_fault())?
|
||||
.map_err(|_| concurrency_fault())
|
||||
}
|
||||
|
||||
fn stream_reference<const EARLY: bool>(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok((0, 0));
|
||||
};
|
||||
value
|
||||
.runtime
|
||||
.run_leaf(async {
|
||||
let stream = AsyncStream::values(value.values.clone());
|
||||
let alias = stream.clone();
|
||||
let total = stream_total(&stream, EARLY.then_some(8)).await?;
|
||||
let after = stream_total(&alias, None).await?;
|
||||
Ok((total, after))
|
||||
})
|
||||
.map_err(|_| concurrency_fault())?
|
||||
}
|
||||
|
||||
fn stream_score(value: &(i64, i64)) -> u64 {
|
||||
(value.0 as u64)
|
||||
.wrapping_mul(31)
|
||||
.wrapping_add(value.1 as u64)
|
||||
}
|
||||
|
||||
async fn parallel_reference_inner(value: &ConcurrencyCase, scope: &Scope<'_>) -> Result<i64, Fault> {
|
||||
let channel = Channel::new(1).map_err(|_| concurrency_fault())?;
|
||||
let consumer = channel.clone();
|
||||
let pending = scope
|
||||
.spawn_leaf(
|
||||
move || async move { stream_total(&AsyncStream::channel(consumer), None).await },
|
||||
)
|
||||
.map_err(|_| concurrency_fault())?;
|
||||
let mut items = value.values.iter().copied();
|
||||
let group: Group<Task<Result<(), Fault>>, Fault> =
|
||||
Group::new(value.limit).map_err(|_| concurrency_fault())?;
|
||||
group
|
||||
.each(
|
||||
|| std::future::ready(Ok(items.next())),
|
||||
|item| {
|
||||
let target = channel.clone();
|
||||
scope.spawn_leaf(move || async move {
|
||||
target.send(item).await.map_err(|_| concurrency_fault())
|
||||
})
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|_| concurrency_fault())??;
|
||||
channel.close().await.map_err(|_| concurrency_fault())?;
|
||||
pending.wait().await.map_err(|_| concurrency_fault())?
|
||||
}
|
||||
|
||||
fn parallel_generated(values: &[ConcurrencyCase]) -> Result<i64, Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok(0);
|
||||
};
|
||||
value
|
||||
.runtime
|
||||
.run(async |scope| EXT_PARALLEL(&value.values, value.limit, scope).await)
|
||||
.map_err(|_| concurrency_fault())?
|
||||
.map_err(|_| concurrency_fault())
|
||||
}
|
||||
|
||||
fn parallel_reference(values: &[ConcurrencyCase]) -> Result<i64, Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok(0);
|
||||
};
|
||||
value
|
||||
.runtime
|
||||
.run(async |scope| parallel_reference_inner(value, scope).await)
|
||||
.map_err(|_| concurrency_fault())?
|
||||
}
|
||||
|
||||
fn new_case(limit: i64) -> ConcurrencyCase {
|
||||
ConcurrencyCase {
|
||||
runtime: Runtime::new(Limits {
|
||||
threads: 2,
|
||||
workers: 2,
|
||||
tasks: 8,
|
||||
})
|
||||
.unwrap(),
|
||||
values: (1..=256).collect(),
|
||||
limit,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// 两侧共用运行库与固定限额;计时含复制、提交、领取及作用域清理。
|
||||
let mut case = new_case(1);
|
||||
assert_eq!(
|
||||
case.runtime.run_leaf(EXT_BOUNDARY()).unwrap().unwrap(),
|
||||
(15, Some(9), 0)
|
||||
);
|
||||
let expected = case.values.iter().sum::<i64>();
|
||||
println!("case,sample,implementation,elapsed_ns,checksum");
|
||||
for capacity in [1, 64] {
|
||||
case.limit = capacity;
|
||||
let values = std::slice::from_ref(&case);
|
||||
let received = case.values.iter().copied().map(Some).collect::<Vec<_>>();
|
||||
assert_eq!(channel_generated(values).unwrap(), (received.clone(), true));
|
||||
assert_eq!(channel_reference(values).unwrap(), (received, true));
|
||||
compare_named(
|
||||
&format!("channel_capacity_{capacity}_shared_runtime"),
|
||||
values,
|
||||
32,
|
||||
channel_generated,
|
||||
channel_reference,
|
||||
channel_score,
|
||||
"rust_runtime",
|
||||
);
|
||||
}
|
||||
case.limit = 8;
|
||||
let values = std::slice::from_ref(&case);
|
||||
assert_eq!(pull_generated(values).unwrap(), (expected, 0));
|
||||
assert_eq!(prefix_generated(values).unwrap(), (36, 0));
|
||||
compare_named(
|
||||
"async_stream_pull_shared_runtime",
|
||||
values,
|
||||
128,
|
||||
pull_generated,
|
||||
stream_reference::<false>,
|
||||
stream_score,
|
||||
"rust_runtime",
|
||||
);
|
||||
compare_named(
|
||||
"async_stream_prefix_shared_runtime",
|
||||
values,
|
||||
128,
|
||||
prefix_generated,
|
||||
stream_reference::<true>,
|
||||
stream_score,
|
||||
"rust_runtime",
|
||||
);
|
||||
for limit in [1, 4] {
|
||||
case.limit = limit;
|
||||
let values = std::slice::from_ref(&case);
|
||||
assert_eq!(parallel_generated(values).unwrap(), expected);
|
||||
assert_eq!(parallel_reference(values).unwrap(), expected);
|
||||
compare_named(
|
||||
&format!("parallel_each_limit_{limit}_shared_runtime"),
|
||||
values,
|
||||
32,
|
||||
parallel_generated,
|
||||
parallel_reference,
|
||||
|value| *value as u64,
|
||||
"rust_runtime",
|
||||
);
|
||||
}
|
||||
// 真正调用空输入 fixture,区别于 samples 的空用例切片。
|
||||
case.values.clear();
|
||||
let empty = std::slice::from_ref(&case);
|
||||
assert_eq!(channel_generated(empty).unwrap(), (Vec::new(), true));
|
||||
assert_eq!(channel_reference(empty).unwrap(), (Vec::new(), true));
|
||||
assert_eq!(pull_generated(empty).unwrap(), (0, 0));
|
||||
assert_eq!(prefix_generated(empty).unwrap(), (0, 0));
|
||||
assert_eq!(stream_reference::<false>(empty).unwrap(), (0, 0));
|
||||
assert_eq!(stream_reference::<true>(empty).unwrap(), (0, 0));
|
||||
assert_eq!(parallel_generated(empty).unwrap(), 0);
|
||||
assert_eq!(parallel_reference(empty).unwrap(), 0);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::path::Path;
|
||||
|
||||
pub fn run(core: &Path) -> bool {
|
||||
let all = std::env::args().any(|argument| argument == "--extended");
|
||||
let primitives = all || std::env::args().any(|argument| argument == "--extended-primitives");
|
||||
let concurrency = all || std::env::args().any(|argument| argument == "--extended-concurrency");
|
||||
if primitives {
|
||||
crate::compare(
|
||||
core,
|
||||
"extended-performance/primitives",
|
||||
"extended-primitives",
|
||||
&[include_str!("../samples.rs"), include_str!("primitives.rs")].concat(),
|
||||
&[
|
||||
("EXT_SHA256", "sha256Digest"),
|
||||
("EXT_HMAC", "hmacDigest"),
|
||||
("EXT_DATETIME", "datetimeBatch"),
|
||||
("EXT_DATE", "dateBatch"),
|
||||
("EXT_TIME", "timeBatch"),
|
||||
("EXT_UUID", "uuidBatch"),
|
||||
],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
if concurrency {
|
||||
crate::compare(
|
||||
core,
|
||||
"extended-performance/concurrency",
|
||||
"extended-concurrency",
|
||||
&[
|
||||
include_str!("../samples.rs"),
|
||||
include_str!("concurrency.rs"),
|
||||
]
|
||||
.concat(),
|
||||
&[
|
||||
("EXT_CHANNEL", "channelRoundtrip"),
|
||||
("EXT_PULL", "streamPull"),
|
||||
("EXT_PREFIX", "streamPrefix"),
|
||||
("EXT_BOUNDARY", "streamBoundary"),
|
||||
("EXT_PARALLEL", "parallelBounded"),
|
||||
],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
primitives || concurrency
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
use dever_runtime::time::calendar::{Date, DateTime, Time};
|
||||
use dever_runtime::value::secret::Secret;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
fn primitive_fault() -> Fault {
|
||||
&FaultInfo {
|
||||
message: "extended primitive failed",
|
||||
source: "extended-primitives-reference",
|
||||
line: 1,
|
||||
column: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn digest_score(bytes: &dever_runtime::Bytes) -> u64 {
|
||||
bytes.iter().fold(0u64, |score, byte| {
|
||||
score.wrapping_mul(31).wrapping_add(u64::from(*byte))
|
||||
})
|
||||
}
|
||||
|
||||
fn sha_reference(bytes: &dever_runtime::Bytes) -> Result<dever_runtime::Bytes, Fault> {
|
||||
Ok(Sha256::digest(bytes).to_vec().into())
|
||||
}
|
||||
|
||||
struct HmacCase {
|
||||
key: Secret,
|
||||
bytes: dever_runtime::Bytes,
|
||||
}
|
||||
|
||||
fn hmac_generated(values: &[HmacCase]) -> Result<dever_runtime::Bytes, Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok(dever_runtime::Bytes::new());
|
||||
};
|
||||
EXT_HMAC(&value.key, &value.bytes)
|
||||
}
|
||||
|
||||
// 共用批准的 Secret 接收点;不读取密钥内部字节。
|
||||
fn hmac_reference(values: &[HmacCase]) -> Result<dever_runtime::Bytes, Fault> {
|
||||
Ok(values
|
||||
.first()
|
||||
.map_or_else(Vec::new, |value| {
|
||||
dever_runtime::crypto::hmac_sha256(&value.key, &value.bytes)
|
||||
})
|
||||
.into())
|
||||
}
|
||||
|
||||
fn datetime_generated(values: &[String]) -> Result<Vec<String>, Fault> {
|
||||
EXT_DATETIME(values).map_err(|_| primitive_fault())
|
||||
}
|
||||
|
||||
fn datetime_reference(values: &[String]) -> Result<Vec<String>, Fault> {
|
||||
values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
DateTime::parse(value)
|
||||
.and_then(DateTime::format)
|
||||
.map_err(|_| primitive_fault())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn date_generated(values: &[String]) -> Result<Vec<String>, Fault> {
|
||||
EXT_DATE(values).map_err(|_| primitive_fault())
|
||||
}
|
||||
|
||||
fn date_reference(values: &[String]) -> Result<Vec<String>, Fault> {
|
||||
values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
Date::parse(value)
|
||||
.and_then(Date::format)
|
||||
.map_err(|_| primitive_fault())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn time_generated(values: &[String]) -> Result<Vec<String>, Fault> {
|
||||
EXT_TIME(values).map_err(|_| primitive_fault())
|
||||
}
|
||||
|
||||
fn time_reference(values: &[String]) -> Result<Vec<String>, Fault> {
|
||||
values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
Time::parse(value)
|
||||
.and_then(Time::format)
|
||||
.map_err(|_| primitive_fault())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn text_score(values: &Vec<String>) -> u64 {
|
||||
values
|
||||
.iter()
|
||||
.flat_map(|value| value.bytes())
|
||||
.fold(0u64, |score, byte| {
|
||||
score.wrapping_mul(31).wrapping_add(u64::from(byte))
|
||||
})
|
||||
}
|
||||
|
||||
fn uuid_generated(values: &[String]) -> Result<Vec<Option<String>>, Fault> {
|
||||
EXT_UUID(values)
|
||||
}
|
||||
|
||||
fn uuid_reference(values: &[String]) -> Result<Vec<Option<String>>, Fault> {
|
||||
Ok(values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
if value.len() != 36 {
|
||||
return None;
|
||||
}
|
||||
uuid::Uuid::try_parse(value)
|
||||
.ok()
|
||||
.map(|value| value.hyphenated().to_string())
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn uuid_score(values: &Vec<Option<String>>) -> u64 {
|
||||
values.iter().fold(0u64, |score, value| {
|
||||
value.as_ref().map_or(score.wrapping_add(1), |value| {
|
||||
value.bytes().fold(score.wrapping_add(2), |score, byte| {
|
||||
score.wrapping_mul(31).wrapping_add(u64::from(byte))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn check_primitives() {
|
||||
assert_eq!(
|
||||
EXT_SHA256(&dever_runtime::Bytes::from_static(b"abc"))
|
||||
.unwrap()
|
||||
.as_ref(),
|
||||
[
|
||||
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
|
||||
0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
|
||||
0xf2, 0x00, 0x15, 0xad,
|
||||
]
|
||||
);
|
||||
// 沿用已有密码学验收的公开向量,不使用真实凭据。
|
||||
let vector = [HmacCase {
|
||||
key: Secret::from_input(vec![0x0b; 20]),
|
||||
bytes: dever_runtime::Bytes::from_static(b"Hi There"),
|
||||
}];
|
||||
assert_eq!(
|
||||
hmac_generated(&vector).unwrap().as_ref(),
|
||||
[
|
||||
0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b,
|
||||
0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
|
||||
0x2e, 0x32, 0xcf, 0xf7,
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
hmac_generated(&vector).unwrap(),
|
||||
hmac_reference(&vector).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
datetime_generated(&["2024-02-29T12:34:56.12+08:00".into()]).unwrap(),
|
||||
["2024-02-29T04:34:56.120Z"]
|
||||
);
|
||||
assert_eq!(
|
||||
time_generated(&["12:34:56.000".into()]).unwrap(),
|
||||
["12:34:56"]
|
||||
);
|
||||
for value in ["2023-02-29", "2024-2-29"] {
|
||||
let values = [value.to_owned()];
|
||||
assert!(date_generated(&values).is_err());
|
||||
assert!(date_reference(&values).is_err());
|
||||
}
|
||||
for value in ["24:00:00", "12:34:56.1"] {
|
||||
let values = [value.to_owned()];
|
||||
assert!(time_generated(&values).is_err());
|
||||
assert!(time_reference(&values).is_err());
|
||||
}
|
||||
let values = ["2024-02-29T00:00:00.1234Z".to_owned()];
|
||||
assert!(datetime_generated(&values).is_err());
|
||||
assert!(datetime_reference(&values).is_err());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
check_primitives();
|
||||
println!("case,sample,implementation,elapsed_ns,checksum");
|
||||
for size in [0usize, 64, 4096] {
|
||||
let bytes = dever_runtime::Bytes::from(vec![0x61; size]);
|
||||
compare_bytes(
|
||||
&format!("crypto_sha256_{size}"),
|
||||
&bytes,
|
||||
2000,
|
||||
EXT_SHA256,
|
||||
sha_reference,
|
||||
digest_score,
|
||||
);
|
||||
let values = [HmacCase {
|
||||
key: Secret::from_input(vec![0x0b; 20]),
|
||||
bytes,
|
||||
}];
|
||||
compare_named(
|
||||
&format!("crypto_hmac_{size}_shared_runtime"),
|
||||
&values,
|
||||
2000,
|
||||
hmac_generated,
|
||||
hmac_reference,
|
||||
digest_score,
|
||||
"rust_runtime",
|
||||
);
|
||||
}
|
||||
// 日历参照保留相同精度、年份范围、非法输入和 UTC 规范化规则。
|
||||
let datetimes = [
|
||||
"1970-01-01T00:00:00Z",
|
||||
"2024-02-29T12:34:56.12+08:00",
|
||||
"1969-12-31T23:59:59.999Z",
|
||||
]
|
||||
.map(str::to_owned);
|
||||
let dates = ["2024-02-29", "2000-01-01", "9999-12-31"].map(str::to_owned);
|
||||
let times = ["00:00:00", "12:34:56.000", "23:59:59.999"].map(str::to_owned);
|
||||
compare_named(
|
||||
"calendar_datetime_shared_runtime",
|
||||
&datetimes,
|
||||
2000,
|
||||
datetime_generated,
|
||||
datetime_reference,
|
||||
text_score,
|
||||
"rust_runtime",
|
||||
);
|
||||
compare_named(
|
||||
"calendar_date_shared_runtime",
|
||||
&dates,
|
||||
2000,
|
||||
date_generated,
|
||||
date_reference,
|
||||
text_score,
|
||||
"rust_runtime",
|
||||
);
|
||||
compare_named(
|
||||
"calendar_time_shared_runtime",
|
||||
×,
|
||||
2000,
|
||||
time_generated,
|
||||
time_reference,
|
||||
text_score,
|
||||
"rust_runtime",
|
||||
);
|
||||
let valid = [
|
||||
"550E8400-E29B-41D4-A716-446655440000",
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
]
|
||||
.map(str::to_owned);
|
||||
assert_eq!(
|
||||
uuid_generated(&valid).unwrap()[0].as_deref(),
|
||||
Some("550e8400-e29b-41d4-a716-446655440000")
|
||||
);
|
||||
let invalid = [
|
||||
"",
|
||||
"550e8400e29b41d4a716446655440000",
|
||||
"00000000-0000-0000-0000-00000000000-",
|
||||
]
|
||||
.map(str::to_owned);
|
||||
assert_eq!(uuid_generated(&invalid).unwrap(), vec![None; invalid.len()]);
|
||||
compare(
|
||||
"uuid_parse_format",
|
||||
&valid,
|
||||
2000,
|
||||
uuid_generated,
|
||||
uuid_reference,
|
||||
uuid_score,
|
||||
);
|
||||
compare(
|
||||
"uuid_parse_invalid",
|
||||
&invalid,
|
||||
2000,
|
||||
uuid_generated,
|
||||
uuid_reference,
|
||||
uuid_score,
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user