use crate::error::{Error, TarantoolError, TarantoolErrorCode};
use crate::ffi::tarantool as ffi;
use crate::index::{Index, IndexIterator, IteratorType};
use crate::tuple::{Encode, ToTupleBuffer, Tuple, TupleBuffer};
use crate::util::Value;
use crate::{msgpack, tuple_from_box_api};
use crate::{set_error, unwrap_or};
use serde::{Deserialize, Serialize};
use serde_json::Map;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::ops::Range;
use std::os::raw::c_char;
pub const SYSTEM_ID_MAX: SpaceId = 511;
pub const SPACE_ID_MAX: SpaceId = (i32::MAX as SpaceId) - 1;
pub type SpaceId = u32;
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SystemSpace {
VinylDeferredDelete = 257,
Schema = 272,
Collation = 276,
VCollation = 277,
Space = 280,
VSpace = 281,
Sequence = 284,
SequenceData = 285,
VSequence = 286,
Index = 288,
VIndex = 289,
Func = 296,
VFunc = 297,
User = 304,
VUser = 305,
Priv = 312,
VPriv = 313,
Cluster = 320,
Trigger = 328,
Truncate = 330,
SpaceSequence = 340,
FkConstraint = 356,
CkConstraint = 364,
FuncIndex = 372,
SessionSettings = 380,
}
impl SystemSpace {
#[inline(always)]
pub fn as_space(&self) -> Space {
Space { id: *self as _ }
}
}
impl From<SystemSpace> for Space {
#[inline(always)]
fn from(ss: SystemSpace) -> Self {
Space { id: ss as _ }
}
}
crate::define_str_enum! {
#![coerce_from_str]
pub enum SpaceEngineType {
Memtx = "memtx",
Vinyl = "vinyl",
SysView = "sysview",
Service = "service",
Blackhole = "blackhole",
}
}
impl Default for SpaceEngineType {
#[inline(always)]
fn default() -> Self {
Self::Memtx
}
}
#[derive(Default, Clone, Debug)]
pub struct SpaceCreateOptions {
pub if_not_exists: bool,
pub engine: SpaceEngineType,
pub id: Option<SpaceId>,
pub field_count: u32,
pub user: Option<String>,
pub space_type: SpaceType,
pub format: Option<Vec<Field>>,
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub enum SpaceType {
#[default]
Normal,
DataTemporary,
Temporary,
DataLocal,
Synchronous,
}
#[deprecated = "Use `space::Field` instead"]
pub type SpaceFieldFormat = Field;
#[derive(Clone, Debug, Serialize, Deserialize, msgpack::Encode, msgpack::Decode, PartialEq, Eq)]
#[encode(tarantool = "crate", as_map)]
pub struct Field {
pub name: String, #[serde(alias = "type")]
pub field_type: FieldType,
pub is_nullable: bool,
}
impl<S> From<(S, FieldType, IsNullable)> for Field
where
String: From<S>,
{
#[inline(always)]
fn from(args: (S, FieldType, IsNullable)) -> Self {
let (name, field_type, is_nullable) = args;
let name = name.into();
let is_nullable = is_nullable.is_nullable();
Self {
name,
field_type,
is_nullable,
}
}
}
impl<S> From<(S, FieldType)> for Field
where
String: From<S>,
{
#[inline(always)]
fn from(args: (S, FieldType)) -> Self {
let (name, field_type) = args;
let name = name.into();
let is_nullable = false;
Self {
name,
field_type,
is_nullable,
}
}
}
macro_rules! define_constructors {
($($constructor:ident ($type:path))+) => {
$(
#[doc = ::std::concat!(
"Create a new field format specifier with the given `name` and ",
"type \"", ::std::stringify!($constructor), "\""
)]
#[inline(always)]
pub fn $constructor(name: impl Into<String>) -> Self {
Self {
name: name.into(),
field_type: $type,
is_nullable: false,
}
}
)+
}
}
impl Field {
#[deprecated = "Use one of `Field::any`, `Field::unsigned`, `Field::string`, etc. instead"]
#[inline(always)]
pub fn new(name: &str, ft: FieldType) -> Self {
Self {
name: name.to_string(),
field_type: ft,
is_nullable: false,
}
}
#[inline(always)]
pub fn is_nullable(mut self, is_nullable: bool) -> Self {
self.is_nullable = is_nullable;
self
}
define_constructors! {
any(FieldType::Any)
unsigned(FieldType::Unsigned)
string(FieldType::String)
number(FieldType::Number)
double(FieldType::Double)
integer(FieldType::Integer)
boolean(FieldType::Boolean)
varbinary(FieldType::Varbinary)
scalar(FieldType::Scalar)
decimal(FieldType::Decimal)
uuid(FieldType::Uuid)
datetime(FieldType::Datetime)
interval(FieldType::Interval)
array(FieldType::Array)
map(FieldType::Map)
}
}
#[deprecated = "use space::FieldType instead"]
pub type SpaceFieldType = FieldType;
crate::define_str_enum! {
#![coerce_from_str]
pub enum FieldType {
Any = "any",
Unsigned = "unsigned",
String = "string",
Number = "number",
Double = "double",
Integer = "integer",
Boolean = "boolean",
Varbinary = "varbinary",
Scalar = "scalar",
Decimal = "decimal",
Uuid = "uuid",
Datetime = "datetime",
Interval = "interval",
Array = "array",
Map = "map",
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum IsNullable {
NonNullalbe,
Nullable,
}
impl IsNullable {
#[inline(always)]
const fn is_nullable(&self) -> bool {
matches!(self, Self::Nullable)
}
}
#[derive(Clone, Debug, Serialize)]
pub struct FuncMetadata {
pub id: u32,
pub owner: u32,
pub name: String,
pub setuid: u32,
pub language: String,
pub body: String,
pub routine_type: String,
pub param_list: Vec<serde_json::Value>,
pub returns: String,
pub aggregate: String,
pub sql_data_access: String,
pub is_deterministic: bool,
pub is_sandboxed: bool,
pub is_null_call: bool,
pub exports: Vec<String>,
pub opts: Map<String, serde_json::Value>,
pub comment: String,
pub created: String,
pub last_altered: String,
}
impl Encode for FuncMetadata {}
#[derive(Clone, Debug, Serialize)]
pub struct Privilege {
pub grantor: u32,
pub grantee: u32,
pub object_type: String,
pub object_id: u32,
pub privilege: u32,
}
impl Encode for Privilege {}
struct SpaceCache {
spaces: RefCell<HashMap<String, Space>>,
indexes: RefCell<HashMap<(u32, String), Index>>,
}
impl SpaceCache {
fn new() -> Self {
Self {
spaces: RefCell::new(HashMap::new()),
indexes: RefCell::new(HashMap::new()),
}
}
fn clear(&self) {
self.spaces.borrow_mut().clear();
self.indexes.borrow_mut().clear();
}
fn space(&self, name: &str) -> Option<Space> {
let mut cache = self.spaces.borrow_mut();
cache.get(name).cloned().or_else(|| {
Space::find(name).inspect(|space| {
cache.insert(name.to_string(), space.clone());
})
})
}
fn index(&self, space: &Space, name: &str) -> Option<Index> {
let mut cache = self.indexes.borrow_mut();
cache
.get(&(space.id, name.to_string()))
.cloned()
.or_else(|| {
space.index(name).inspect(|index| {
cache.insert((space.id, name.to_string()), index.clone());
})
})
}
}
thread_local! {
static SPACE_CACHE: SpaceCache = SpaceCache::new();
}
pub fn clear_cache() {
SPACE_CACHE.with(SpaceCache::clear)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Space {
id: SpaceId,
}
impl Space {
#[inline(always)]
pub fn builder(name: &str) -> Builder<'_> {
Builder::new(name)
}
#[inline(always)]
pub fn create(name: &str, opts: &SpaceCreateOptions) -> Result<Space, Error> {
crate::schema::space::create_space(name, opts)
}
#[inline(always)]
pub fn drop(&self) -> Result<(), Error> {
crate::schema::space::drop_space(self.id)
}
#[inline(always)]
pub fn find(name: &str) -> Option<Self> {
let id =
unsafe { ffi::box_space_id_by_name(name.as_ptr() as *const c_char, name.len() as u32) };
if id == ffi::BOX_ID_NIL {
None
} else {
Some(Self { id })
}
}
#[inline(always)]
pub fn find_cached(name: &str) -> Option<Self> {
SPACE_CACHE.with(|cache| cache.space(name))
}
#[inline(always)]
pub const unsafe fn from_id_unchecked(id: SpaceId) -> Self {
Self { id }
}
#[inline(always)]
pub const fn id(&self) -> SpaceId {
self.id
}
#[inline(always)]
pub fn create_index(
&self,
name: &str,
opts: &crate::index::IndexOptions,
) -> Result<Index, Error> {
crate::schema::index::create_index(self.id, name, opts)
}
#[inline(always)]
pub fn index_builder<'a>(&self, name: &'a str) -> crate::index::Builder<'a> {
crate::index::Builder::new(self.id, name)
}
#[inline(always)]
pub fn index(&self, name: &str) -> Option<Index> {
let index_id = unsafe {
ffi::box_index_id_by_name(self.id, name.as_ptr() as *const c_char, name.len() as u32)
};
if index_id == ffi::BOX_ID_NIL {
None
} else {
Some(Index::new(self.id, index_id))
}
}
#[inline(always)]
pub fn index_cached(&self, name: &str) -> Option<Index> {
SPACE_CACHE.with(|cache| cache.index(self, name))
}
#[inline(always)]
pub fn primary_key(&self) -> Index {
Index::new(self.id, 0)
}
#[inline]
pub fn insert<T>(&self, value: &T) -> Result<Tuple, Error>
where
T: ToTupleBuffer + ?Sized,
{
let buf;
let data = unwrap_or!(value.tuple_data(), {
buf = value.to_tuple_buffer()?;
buf.as_ref()
});
let Range { start, end } = data.as_ptr_range();
tuple_from_box_api!(
ffi::box_insert[
self.id,
start as _,
end as _,
@out
]
)
.map(|t| t.expect("Returned tuple cannot be null"))
}
#[inline]
pub fn replace<T>(&self, value: &T) -> Result<Tuple, Error>
where
T: ToTupleBuffer + ?Sized,
{
let buf;
let data = unwrap_or!(value.tuple_data(), {
buf = value.to_tuple_buffer()?;
buf.as_ref()
});
let Range { start, end } = data.as_ptr_range();
tuple_from_box_api!(
ffi::box_replace[
self.id,
start as _,
end as _,
@out
]
)
.map(|t| t.expect("Returned tuple cannot be null"))
}
#[inline(always)]
pub fn put<T>(&self, value: &T) -> Result<Tuple, Error>
where
T: ToTupleBuffer + ?Sized,
{
self.replace(value)
}
#[inline(always)]
pub fn truncate(&self) -> Result<(), Error> {
if unsafe { ffi::box_truncate(self.id) } < 0 {
return Err(TarantoolError::last().into());
}
Ok(())
}
#[inline(always)]
pub fn len(&self) -> Result<usize, Error> {
self.primary_key().len()
}
#[inline(always)]
pub fn is_empty(&self) -> Result<bool, Error> {
self.len().map(|l| l == 0)
}
#[inline(always)]
pub fn bsize(&self) -> Result<usize, Error> {
let space = unsafe { ffi::space_by_id(self.id) };
if space.is_null() {
set_error!(
TarantoolErrorCode::NoSuchSpace,
"Space {} does not exist",
self.id
);
return Err(TarantoolError::last().into());
}
Ok(unsafe { ffi::space_bsize(space) })
}
#[inline(always)]
pub fn get<K>(&self, key: &K) -> Result<Option<Tuple>, Error>
where
K: ToTupleBuffer + ?Sized,
{
self.primary_key().get(key)
}
#[inline(always)]
pub fn select<K>(&self, iterator_type: IteratorType, key: &K) -> Result<IndexIterator, Error>
where
K: ToTupleBuffer + ?Sized,
{
self.primary_key().select(iterator_type, key)
}
#[inline(always)]
pub fn count<K>(&self, iterator_type: IteratorType, key: &K) -> Result<usize, Error>
where
K: ToTupleBuffer + ?Sized,
{
self.primary_key().count(iterator_type, key)
}
#[inline(always)]
pub fn delete<K>(&self, key: &K) -> Result<Option<Tuple>, Error>
where
K: ToTupleBuffer + ?Sized,
{
self.primary_key().delete(key)
}
#[inline(always)]
pub fn update<K, Op>(&self, key: &K, ops: impl AsRef<[Op]>) -> Result<Option<Tuple>, Error>
where
K: ToTupleBuffer + ?Sized,
Op: ToTupleBuffer,
{
self.primary_key().update(key, ops)
}
#[inline(always)]
#[deprecated = "use update_raw instead"]
pub unsafe fn update_mp<K>(&self, key: &K, ops: &[Vec<u8>]) -> Result<Option<Tuple>, Error>
where
K: ToTupleBuffer + ?Sized,
{
#[allow(deprecated)]
self.primary_key().update_mp(key, ops)
}
#[inline(always)]
pub unsafe fn update_raw(&self, key: &[u8], ops: &[u8]) -> Result<Option<Tuple>, Error> {
self.primary_key().update_raw(key, ops)
}
#[inline(always)]
pub fn upsert<T, Op>(&self, value: &T, ops: impl AsRef<[Op]>) -> Result<(), Error>
where
T: ToTupleBuffer + ?Sized,
Op: ToTupleBuffer,
{
self.primary_key().upsert(value, ops)
}
#[inline(always)]
#[deprecated = "use upsert_raw instead"]
pub unsafe fn upsert_mp<T>(&self, value: &T, ops: &[Vec<u8>]) -> Result<(), Error>
where
T: ToTupleBuffer + ?Sized,
{
#[allow(deprecated)]
self.primary_key().upsert_mp(value, ops)
}
#[inline(always)]
pub unsafe fn upsert_raw(&self, value: &[u8], ops: &[u8]) -> Result<(), Error> {
self.primary_key().upsert_raw(value, ops)
}
#[inline(always)]
pub fn meta(&self) -> Result<Metadata<'_>, Error> {
let sys_space: Space = SystemSpace::Space.into();
let tuple = sys_space.get(&(self.id,))?.ok_or(Error::MetaNotFound)?;
tuple.decode::<Metadata>()
}
}
#[derive(Default, serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Metadata<'a> {
pub id: u32,
pub user_id: u32,
pub name: Cow<'a, str>,
pub engine: SpaceEngineType,
pub field_count: u32,
pub flags: BTreeMap<Cow<'a, str>, Value<'a>>,
pub format: Vec<BTreeMap<Cow<'a, str>, Value<'a>>>,
}
impl Encode for Metadata<'_> {}
#[allow(dead_code)]
pub struct Builder<'a> {
name: &'a str,
opts: SpaceCreateOptions,
}
macro_rules! define_setters {
($( $setter:ident ( $field:ident : $ty:ty ) )+) => {
$(
#[inline(always)]
pub fn $setter(mut self, $field: $ty) -> Self {
self.opts.$field = $field.into();
self
}
)+
}
}
impl<'a> Builder<'a> {
#[inline(always)]
pub fn new(name: &'a str) -> Self {
Self {
name,
opts: Default::default(),
}
}
define_setters! {
if_not_exists(if_not_exists: bool)
engine(engine: SpaceEngineType)
id(id: SpaceId)
field_count(field_count: u32)
user(user: String)
space_type(space_type: SpaceType)
}
#[deprecated = "use Builder::space_type instead"]
#[inline(always)]
pub fn is_local(mut self, is_local: bool) -> Self {
if is_local {
self.opts.space_type = SpaceType::DataLocal
}
self
}
#[deprecated = "use Builder::space_type instead"]
#[inline(always)]
pub fn temporary(mut self, temporary: bool) -> Self {
if temporary {
self.opts.space_type = SpaceType::DataTemporary
}
self
}
#[deprecated = "use Builder::space_type instead"]
#[inline(always)]
pub fn is_sync(mut self, is_sync: bool) -> Self {
if is_sync {
self.opts.space_type = SpaceType::Synchronous
}
self
}
#[inline(always)]
pub fn field(mut self, field: impl Into<Field>) -> Self {
self.opts
.format
.get_or_insert_with(|| Vec::with_capacity(16))
.push(field.into());
self
}
#[inline]
pub fn format(mut self, format: impl IntoIterator<Item = impl Into<Field>>) -> Self {
let iter = format.into_iter();
let (size, _) = iter.size_hint();
self.opts
.format
.get_or_insert_with(|| Vec::with_capacity(size))
.extend(iter.map(Into::into));
self
}
#[inline(always)]
pub fn create(self) -> crate::Result<Space> {
crate::schema::space::create_space(self.name, &self.opts)
}
#[inline(always)]
pub fn into_parts(self) -> (&'a str, SpaceCreateOptions) {
(self.name, self.opts)
}
}
pub struct UpdateOps {
ops: Vec<TupleBuffer>,
}
macro_rules! define_bin_ops {
($( $(#[$meta:meta])* $op_name:ident, $op_code:literal; )+) => {
$(
$(#[$meta])*
#[inline(always)]
pub fn $op_name<K, V>(&mut self, field: K, value: V) -> crate::Result<&mut Self>
where
K: Serialize,
V: Serialize,
{
self.ops.push(($op_code, field, value).to_tuple_buffer()?);
Ok(self)
}
)+
}
}
impl UpdateOps {
#[inline]
pub fn new() -> Self {
Self { ops: Vec::new() }
}
#[inline(always)]
pub fn with_capacity(capacity: usize) -> Self {
Self {
ops: Vec::with_capacity(capacity),
}
}
define_bin_ops! {
assign, '=';
insert, '!';
add, '+';
sub, '-';
and, '&';
or, '|';
xor, '^';
}
#[inline]
pub fn delete<K>(&mut self, field: K, count: usize) -> crate::Result<&mut Self>
where
K: Serialize,
{
self.ops.push(('#', field, count).to_tuple_buffer()?);
Ok(self)
}
#[inline]
pub fn splice<K>(
&mut self,
field: K,
start: isize,
count: usize,
value: &str,
) -> crate::Result<&mut Self>
where
K: Serialize,
{
self.ops
.push((':', field, start, count, value).to_tuple_buffer()?);
Ok(self)
}
#[inline(always)]
pub fn as_slice(&self) -> &[TupleBuffer] {
&self.ops
}
#[inline(always)]
pub fn into_inner(self) -> Vec<TupleBuffer> {
self.ops
}
#[inline]
pub fn encode(&self) -> Vec<u8> {
let mut res = Vec::with_capacity(4 + 4 * self.ops.len());
self.encode_to(&mut res).expect("memory allocation failed");
res
}
#[inline]
pub fn encode_to(&self, w: &mut impl std::io::Write) -> crate::Result<()> {
crate::msgpack::write_array_len(w, self.ops.len() as _)?;
for op in &self.ops {
op.write_tuple_data(w)?;
}
Ok(())
}
}
impl Default for UpdateOps {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl AsRef<[TupleBuffer]> for UpdateOps {
#[inline(always)]
fn as_ref(&self) -> &[TupleBuffer] {
&self.ops
}
}
impl From<UpdateOps> for Vec<TupleBuffer> {
#[inline(always)]
fn from(ops: UpdateOps) -> Vec<TupleBuffer> {
ops.ops
}
}
impl IntoIterator for UpdateOps {
type Item = TupleBuffer;
type IntoIter = std::vec::IntoIter<TupleBuffer>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.ops.into_iter()
}
}
#[macro_export]
macro_rules! update {
($target:expr, $key:expr, $($op:expr),+ $(,)?) => {{
use $crate::tuple::ToTupleBuffer;
let f = || -> $crate::Result<Option<$crate::tuple::Tuple>> {
let key = $key;
let buf;
let key_data = $crate::unwrap_or!(key.tuple_data(), {
buf = key.to_tuple_buffer()?;
buf.as_ref()
});
const LEN: u32 = $crate::expr_count!($($op),+);
let mut ops_buf = Vec::with_capacity((4 + LEN * 4) as _);
$crate::msgpack::write_array_len(&mut ops_buf, LEN)?;
$( $op.write_tuple_data(&mut ops_buf)?; )+
#[allow(unused_unsafe)]
unsafe {
$target.update_raw(key_data, ops_buf.as_ref())
}
};
f()
}};
}
#[macro_export]
macro_rules! upsert {
($target:expr, $value: expr, $($op:expr),+ $(,)?) => {{
use $crate::tuple::ToTupleBuffer;
let f = || -> $crate::Result<()> {
let value = $value;
let buf;
let value_data = $crate::unwrap_or!(value.tuple_data(), {
buf = value.to_tuple_buffer()?;
buf.as_ref()
});
const LEN: u32 = $crate::expr_count!($($op),+);
let mut ops_buf = Vec::with_capacity((4 + LEN * 4) as _);
$crate::msgpack::write_array_len(&mut ops_buf, LEN)?;
$( $op.write_tuple_data(&mut ops_buf)?; )+
#[allow(unused_unsafe)]
unsafe {
$target.upsert_raw(value_data, ops_buf.as_ref())
}
};
f()
}};
}
pub fn space_id_temporary_min() -> Option<SpaceId> {
static mut VALUE: Option<Option<SpaceId>> = None;
unsafe {
if (*std::ptr::addr_of!(VALUE)).is_none() {
VALUE = Some(
crate::lua_state()
.eval("return box.schema.SPACE_ID_TEMPORARY_MIN")
.ok(),
)
}
VALUE.unwrap()
}
}
#[cfg(feature = "internal_test")]
mod test {
use super::*;
use crate::tuple::RawBytes;
#[crate::test(tarantool = "crate")]
fn insert_raw_bytes() {
let space_name = crate::temp_space_name!();
let space = Space::builder(&space_name).create().unwrap();
space.index_builder("pk").create().unwrap();
space.insert(RawBytes::new(b"\x93*\xa3foo\xa3bar")).unwrap();
let t = space.get(&(42,)).unwrap().unwrap();
let t: (u32, String, String) = t.decode().unwrap();
assert_eq!(t, (42, "foo".to_owned(), "bar".to_owned()));
space.drop().unwrap();
}
#[crate::test(tarantool = "crate")]
fn sys_space_metadata() {
let sys_space = Space::from(SystemSpace::Space);
for tuple in sys_space.select(IteratorType::All, &()).unwrap() {
let _meta: Metadata = tuple.decode().unwrap();
}
}
#[crate::test(tarantool = "crate")]
fn dont_decrease_max_id() {
let sys_schema = SystemSpace::Schema.as_space();
let mut spaces = vec![];
sys_schema.delete(&["max_id"]).unwrap();
let space = Space::builder(&crate::temp_space_name!()).create().unwrap();
assert!(sys_schema.get(&["max_id"]).unwrap().is_none());
spaces.push(space);
sys_schema
.put(&("max_id", "this is not a space id"))
.unwrap();
let space = Space::builder(&crate::temp_space_name!()).create().unwrap();
assert_eq!(space.id(), spaces.last().unwrap().id() + 1);
let not_max_id = sys_schema
.get(&["max_id"])
.unwrap()
.unwrap()
.field::<String>(1)
.unwrap()
.unwrap();
assert_eq!(not_max_id, "this is not a space id");
spaces.push(space);
let max_id_before = 0;
sys_schema.put(&("max_id", max_id_before)).unwrap();
let space = Space::builder(&crate::temp_space_name!()).create().unwrap();
let max_id = sys_schema
.get(&["max_id"])
.unwrap()
.unwrap()
.field::<SpaceId>(1)
.unwrap()
.unwrap();
assert_ne!(space.id(), max_id_before + 1);
assert_eq!(space.id(), max_id);
spaces.push(space);
let max_id_before = max_id + 13;
sys_schema.put(&("max_id", max_id_before)).unwrap();
let space = Space::builder(&crate::temp_space_name!()).create().unwrap();
let max_id = sys_schema
.get(&["max_id"])
.unwrap()
.unwrap()
.field::<SpaceId>(1)
.unwrap()
.unwrap();
assert_eq!(space.id(), spaces.last().unwrap().id() + 1);
assert!(space.id() < max_id);
assert_eq!(max_id, max_id_before);
spaces.push(space);
if crate::ffi::has_fully_temporary_spaces() {
let space = Space::builder(&crate::temp_space_name!())
.space_type(SpaceType::Temporary)
.create()
.unwrap();
let max_id = sys_schema
.get(&["max_id"])
.unwrap()
.unwrap()
.field::<SpaceId>(1)
.unwrap()
.unwrap();
assert!(space.id() > max_id);
assert_eq!(max_id, max_id_before);
spaces.push(space);
}
for space in spaces {
space.drop().unwrap();
}
}
}