Merge branch 'empty-select' into 'main'
Empty select See merge request x433485/minisql!17
This commit is contained in:
commit
e53650d02e
6 changed files with 98 additions and 63 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -481,6 +481,24 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_pcg"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_seeder"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf2890aaef0aa82719a50e808de264f9484b74b442e1a3a0e5ee38243ac40bdb"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -550,6 +568,8 @@ dependencies = [
|
||||||
"parser",
|
"parser",
|
||||||
"proto",
|
"proto",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rand_pcg",
|
||||||
|
"rand_seeder",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::schema::{Column, TableName, TablePosition, TableSchema};
|
use crate::schema::{Column, TableName, TablePosition, TableSchema};
|
||||||
use crate::internals::table::Table;
|
use crate::internals::table::Table;
|
||||||
use crate::operation::{Operation, Condition};
|
use crate::operation::{Operation, Condition, ColumnSelection};
|
||||||
use crate::result::DbResult;
|
use crate::result::DbResult;
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -18,7 +18,7 @@ pub struct State {
|
||||||
|
|
||||||
// #[derive(Debug)]
|
// #[derive(Debug)]
|
||||||
pub enum Response<'a> {
|
pub enum Response<'a> {
|
||||||
Selected(&'a TableSchema, Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>),
|
Selected(&'a TableSchema, ColumnSelection, Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>),
|
||||||
Inserted,
|
Inserted,
|
||||||
Deleted(usize), // how many were deleted
|
Deleted(usize), // how many were deleted
|
||||||
TableCreated,
|
TableCreated,
|
||||||
|
|
@ -31,7 +31,7 @@ impl std::fmt::Debug for Response<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
use Response::*;
|
use Response::*;
|
||||||
match self {
|
match self {
|
||||||
Selected(_schema, _rows) =>
|
Selected(_schema, _columns, _rows) =>
|
||||||
// TODO: How can we iterate through the rows without having to take ownership of
|
// TODO: How can we iterate through the rows without having to take ownership of
|
||||||
// them?
|
// them?
|
||||||
f.write_str("Some rows... trust me"),
|
f.write_str("Some rows... trust me"),
|
||||||
|
|
@ -89,14 +89,14 @@ impl State {
|
||||||
|
|
||||||
let selected_rows = match maybe_condition {
|
let selected_rows = match maybe_condition {
|
||||||
None => {
|
None => {
|
||||||
let rows = table.select_all_rows(column_selection);
|
let rows = table.select_all_rows(column_selection.clone());
|
||||||
Box::new(rows) as Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>
|
Box::new(rows) as Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>
|
||||||
},
|
},
|
||||||
|
|
||||||
Some(Condition::Eq(eq_column, value)) => {
|
Some(Condition::Eq(eq_column, value)) => {
|
||||||
let x =
|
let x =
|
||||||
table.select_rows_where_eq(
|
table.select_rows_where_eq(
|
||||||
column_selection,
|
column_selection.clone(),
|
||||||
eq_column,
|
eq_column,
|
||||||
value,
|
value,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -104,7 +104,7 @@ impl State {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Response::Selected(table.schema(), selected_rows))
|
Ok(Response::Selected(table.schema(), column_selection, selected_rows))
|
||||||
},
|
},
|
||||||
Insert(table_position, values) => {
|
Insert(table_position, values) => {
|
||||||
let table: &mut Table = self.table_at_mut(table_position);
|
let table: &mut Table = self.table_at_mut(table_position);
|
||||||
|
|
@ -190,8 +190,8 @@ mod tests {
|
||||||
let response: Response = state
|
let response: Response = state
|
||||||
.interpret(Operation::Select(users_position, users_schema.all_selection(), None))
|
.interpret(Operation::Select(users_position, users_schema.all_selection(), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _, _)));
|
||||||
let Response::Selected(_schema, rows) = response else {
|
let Response::Selected(_, _, rows) = response else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
let rows: Vec<_> = rows.collect();
|
let rows: Vec<_> = rows.collect();
|
||||||
|
|
@ -232,8 +232,8 @@ mod tests {
|
||||||
.interpret(Operation::Select(users, users_schema.all_selection(), None))
|
.interpret(Operation::Select(users, users_schema.all_selection(), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _, _)));
|
||||||
let Response::Selected(_schema, rows) = response else {
|
let Response::Selected(_, _, rows) = response else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
let rows: Vec<_> = rows.collect();
|
let rows: Vec<_> = rows.collect();
|
||||||
|
|
@ -299,8 +299,8 @@ mod tests {
|
||||||
{
|
{
|
||||||
let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
|
let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
|
||||||
|
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _, _)));
|
||||||
let Response::Selected(_, rows) = response else {
|
let Response::Selected(_, _, rows) = response else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -328,8 +328,8 @@ mod tests {
|
||||||
Some(Eq(id_column, id0.clone())),
|
Some(Eq(id_column, id0.clone())),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _, _)));
|
||||||
let Response::Selected(_, rows) = response else {
|
let Response::Selected(_, _, rows) = response else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
let rows: Vec<_> = rows.collect();
|
let rows: Vec<_> = rows.collect();
|
||||||
|
|
@ -350,8 +350,8 @@ mod tests {
|
||||||
Some(Eq(id_column, id0.clone())),
|
Some(Eq(id_column, id0.clone())),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _, _)));
|
||||||
let Response::Selected(_, rows) = response else {
|
let Response::Selected(_, _, rows) = response else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
let rows: Vec<_> = rows.collect();
|
let rows: Vec<_> = rows.collect();
|
||||||
|
|
@ -425,8 +425,8 @@ mod tests {
|
||||||
|
|
||||||
let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
|
let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
|
||||||
|
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _, _)));
|
||||||
let Response::Selected(_, rows) = response else {
|
let Response::Selected(_, _, rows) = response else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
let rows: Vec<_> = rows.collect();
|
let rows: Vec<_> = rows.collect();
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,23 @@ impl DbType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn type_oid(&self) -> i32 {
|
||||||
|
match self {
|
||||||
|
Self::String => 25,
|
||||||
|
Self::Int => 23,
|
||||||
|
Self::Number => 701,
|
||||||
|
Self::Uuid => 2950,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_size(&self) -> i16 {
|
||||||
|
match self {
|
||||||
|
Self::String => -2, // null terminated string
|
||||||
|
Self::Int => 8,
|
||||||
|
Self::Number => 8,
|
||||||
|
Self::Uuid => 16,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
|
@ -54,29 +71,6 @@ impl Value {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn type_oid(&self) -> i32 {
|
|
||||||
match self {
|
|
||||||
Self::Number(_) => 701,
|
|
||||||
Self::Indexable(val) => match val {
|
|
||||||
IndexableValue::String(_) => 25,
|
|
||||||
IndexableValue::Int(_) => 23,
|
|
||||||
IndexableValue::Uuid(_) => 2950,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn type_size(&self) -> i16 {
|
|
||||||
match self {
|
|
||||||
Self::Number(_) => 8,
|
|
||||||
Self::Indexable(val) => match val {
|
|
||||||
IndexableValue::String(_) => -2, // null terminated string
|
|
||||||
IndexableValue::Int(_) => 8,
|
|
||||||
IndexableValue::Uuid(_) => 16,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_text_bytes(&self) -> Vec<u8> {
|
pub fn as_text_bytes(&self) -> Vec<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::Number(n) => format!("{n}").into_bytes(),
|
Self::Number(n) => format!("{n}").into_bytes(),
|
||||||
|
|
@ -123,8 +117,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_number() {
|
fn test_encode_number() {
|
||||||
let value = Value::Number(123.456);
|
let value = Value::Number(123.456);
|
||||||
let oid = value.type_oid();
|
let vtype = value.to_type();
|
||||||
let size = value.type_size();
|
let oid = vtype.type_oid();
|
||||||
|
let size = vtype.type_size();
|
||||||
|
|
||||||
let bytes = value.as_text_bytes();
|
let bytes = value.as_text_bytes();
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
@ -138,8 +133,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_string() {
|
fn test_encode_string() {
|
||||||
let value = Value::Indexable(IndexableValue::String("hello".to_string()));
|
let value = Value::Indexable(IndexableValue::String("hello".to_string()));
|
||||||
let oid = value.type_oid();
|
let vtype = value.to_type();
|
||||||
let size = value.type_size();
|
let oid = vtype.type_oid();
|
||||||
|
let size = vtype.type_size();
|
||||||
|
|
||||||
let bytes = value.as_text_bytes();
|
let bytes = value.as_text_bytes();
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
@ -153,8 +149,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_string_utf8() {
|
fn test_encode_string_utf8() {
|
||||||
let value = Value::Indexable(IndexableValue::String("#速度与激情9 早上好中国 现在我有冰激淋 我很喜欢冰激淋 但是《速度与激情9》比冰激淋 🍧🍦🍨".to_string()));
|
let value = Value::Indexable(IndexableValue::String("#速度与激情9 早上好中国 现在我有冰激淋 我很喜欢冰激淋 但是《速度与激情9》比冰激淋 🍧🍦🍨".to_string()));
|
||||||
let oid = value.type_oid();
|
let vtype = value.to_type();
|
||||||
let size = value.type_size();
|
let oid = vtype.type_oid();
|
||||||
|
let size = vtype.type_size();
|
||||||
|
|
||||||
let bytes = value.as_text_bytes();
|
let bytes = value.as_text_bytes();
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
@ -168,8 +165,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_int() {
|
fn test_encode_int() {
|
||||||
let value = Value::Indexable(IndexableValue::Int(123));
|
let value = Value::Indexable(IndexableValue::Int(123));
|
||||||
let oid = value.type_oid();
|
let vtype = value.to_type();
|
||||||
let size = value.type_size();
|
let oid = vtype.type_oid();
|
||||||
|
let size = vtype.type_size();
|
||||||
|
|
||||||
let bytes = value.as_text_bytes();
|
let bytes = value.as_text_bytes();
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
@ -183,8 +181,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_uuid() {
|
fn test_encode_uuid() {
|
||||||
let value = Value::Indexable(IndexableValue::Uuid(123));
|
let value = Value::Indexable(IndexableValue::Uuid(123));
|
||||||
let oid = value.type_oid();
|
let vtype = value.to_type();
|
||||||
let size = value.type_size();
|
let oid = vtype.type_oid();
|
||||||
|
let size = vtype.type_size();
|
||||||
|
|
||||||
let bytes = value.as_text_bytes();
|
let bytes = value.as_text_bytes();
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
@ -198,7 +197,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mismatched_size() {
|
fn test_mismatched_size() {
|
||||||
let value = Value::Indexable(IndexableValue::Uuid(123));
|
let value = Value::Indexable(IndexableValue::Uuid(123));
|
||||||
let oid = value.type_oid();
|
let vtype = value.to_type();
|
||||||
|
let oid = vtype.type_oid();
|
||||||
let size = 8;
|
let size = 8;
|
||||||
|
|
||||||
let bytes = value.as_text_bytes();
|
let bytes = value.as_text_bytes();
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ anyhow = "1.0.76"
|
||||||
clap = { version = "4.4.18", features = ["derive"] }
|
clap = { version = "4.4.18", features = ["derive"] }
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
rand_seeder = "0.2.3"
|
||||||
|
rand_pcg = "0.3.1"
|
||||||
serde_json = "1.0.112"
|
serde_json = "1.0.112"
|
||||||
minisql = { path = "../minisql" }
|
minisql = { path = "../minisql" }
|
||||||
proto = { path = "../proto" }
|
proto = { path = "../proto" }
|
||||||
|
|
|
||||||
|
|
@ -186,10 +186,10 @@ async fn handle_query<W>(writer: &mut W, state: &SharedDbState, query: String, t
|
||||||
writer.write_command_complete(CompleteStatus::Insert { oid: 0, rows: 1 }).await?;
|
writer.write_command_complete(CompleteStatus::Insert { oid: 0, rows: 1 }).await?;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Response::Selected(schema, mut rows) => {
|
Response::Selected(schema, columns, mut rows) => {
|
||||||
|
writer.write_table_header(&schema, &columns).await?;
|
||||||
match rows.next() {
|
match rows.next() {
|
||||||
Some(row) => {
|
Some(row) => {
|
||||||
writer.write_table_header(&schema, &row).await?;
|
|
||||||
writer.write_table_row(&row).await?;
|
writer.write_table_row(&row).await?;
|
||||||
|
|
||||||
let mut sent_rows = 1;
|
let mut sent_rows = 1;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use rand::Rng;
|
||||||
|
use rand_pcg::Pcg64;
|
||||||
|
use rand_seeder::Seeder;
|
||||||
|
use minisql::operation::ColumnSelection;
|
||||||
use minisql::restricted_row::RestrictedRow;
|
use minisql::restricted_row::RestrictedRow;
|
||||||
use minisql::schema::{Column, TableSchema};
|
use minisql::schema::{Column, TableSchema};
|
||||||
use minisql::type_system::Value;
|
|
||||||
use proto::message::backend::{BackendMessage, ColumnDescription, CommandCompleteData, DataRowData, ErrorResponseData, ReadyForQueryData, RowDescriptionData};
|
use proto::message::backend::{BackendMessage, ColumnDescription, CommandCompleteData, DataRowData, ErrorResponseData, ReadyForQueryData, RowDescriptionData};
|
||||||
use proto::message::primitive::pglist::PgList;
|
use proto::message::primitive::pglist::PgList;
|
||||||
use proto::writer::backend::BackendProtoWriter;
|
use proto::writer::backend::BackendProtoWriter;
|
||||||
|
|
@ -34,7 +37,7 @@ pub trait ServerProto {
|
||||||
async fn write_error_message(&mut self, error_message: &str) -> anyhow::Result<()>;
|
async fn write_error_message(&mut self, error_message: &str) -> anyhow::Result<()>;
|
||||||
async fn write_ready_for_query(&mut self) -> anyhow::Result<()>;
|
async fn write_ready_for_query(&mut self) -> anyhow::Result<()>;
|
||||||
async fn write_empty_query(&mut self) -> anyhow::Result<()>;
|
async fn write_empty_query(&mut self) -> anyhow::Result<()>;
|
||||||
async fn write_table_header(&mut self, table_schema: &TableSchema, row: &RestrictedRow) -> anyhow::Result<()>;
|
async fn write_table_header(&mut self, table_schema: &TableSchema, columns: &ColumnSelection) -> anyhow::Result<()>;
|
||||||
async fn write_table_row(&mut self, row: &RestrictedRow) -> anyhow::Result<()>;
|
async fn write_table_row(&mut self, row: &RestrictedRow) -> anyhow::Result<()>;
|
||||||
async fn write_command_complete(&mut self, status: CompleteStatus) -> anyhow::Result<()>;
|
async fn write_command_complete(&mut self, status: CompleteStatus) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
@ -60,9 +63,9 @@ impl<W> ServerProto for W where W: BackendProtoWriter + Send {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write_table_header(&mut self, table_schema: &TableSchema, row: &RestrictedRow) -> anyhow::Result<()> {
|
async fn write_table_header(&mut self, table_schema: &TableSchema, columns: &ColumnSelection) -> anyhow::Result<()> {
|
||||||
let columns = row.iter()
|
let columns = columns.iter()
|
||||||
.map(|(column, value)| value_to_column_description(table_schema, value, *column))
|
.map(|column| column_to_description(table_schema, *column))
|
||||||
.collect::<anyhow::Result<Vec<ColumnDescription>>>()?;
|
.collect::<anyhow::Result<Vec<ColumnDescription>>>()?;
|
||||||
|
|
||||||
self.write_proto(RowDescriptionData { columns: columns.into() }.into()).await?;
|
self.write_proto(RowDescriptionData { columns: columns.into() }.into()).await?;
|
||||||
|
|
@ -88,13 +91,16 @@ impl<W> ServerProto for W where W: BackendProtoWriter + Send {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value_to_column_description(schema: &TableSchema, value: &Value, column: Column) -> anyhow::Result<ColumnDescription> {
|
fn column_to_description(schema: &TableSchema, column: Column) -> anyhow::Result<ColumnDescription> {
|
||||||
|
let table_name = schema.table_name();
|
||||||
|
let table_oid = table_name_to_oid(table_name);
|
||||||
|
|
||||||
|
let column_type = schema.column_type(column);
|
||||||
let name = schema.column_name_from_column(column);
|
let name = schema.column_name_from_column(column);
|
||||||
|
|
||||||
let table_oid = schema.table_name().as_bytes().as_ptr() as i32;
|
|
||||||
let column_index = column.try_into()?;
|
let column_index = column.try_into()?;
|
||||||
let type_oid = value.type_oid();
|
let type_oid = column_type.type_oid();
|
||||||
let type_size = value.type_size();
|
let type_size = column_type.type_size();
|
||||||
|
|
||||||
Ok(ColumnDescription {
|
Ok(ColumnDescription {
|
||||||
name: name.to_string().into(),
|
name: name.to_string().into(),
|
||||||
|
|
@ -106,3 +112,10 @@ fn value_to_column_description(schema: &TableSchema, value: &Value, column: Colu
|
||||||
format_code: 0, // text format
|
format_code: 0, // text format
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hashes the table name into single i32 used as a substitute for the table OID
|
||||||
|
/// in the PostgreSQL protocol.
|
||||||
|
fn table_name_to_oid(table_name: &str) -> i32 {
|
||||||
|
let mut rng: Pcg64 = Seeder::from(table_name).make_rng();
|
||||||
|
rng.gen::<i32>()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue