use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::os::raw::c_char;
use num_derive::ToPrimitive;
use num_traits::ToPrimitive;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::{Map, Value};
use crate::error::{Error, TarantoolError};
use crate::ffi::tarantool as ffi;
use crate::index::{Index, IndexIterator, IteratorType};
#[cfg(feature = "schema")]
use crate::schema::space::SpaceMetadata;
use crate::tuple::{Encode, ToTupleBuffer, Tuple};
use crate::tuple_from_box_api;
pub const SYSTEM_ID_MAX: u32 = 511;
#[repr(u32)]
#[derive(Clone, Debug, PartialEq, Eq, ToPrimitive)]
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 From<SystemSpace> for Space {
fn from(ss: SystemSpace) -> Self {
Space {
id: ss.to_u32().unwrap(),
}
}
}
#[derive(Copy, Clone, Debug, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SpaceEngineType {
Memtx,
Vinyl,
}
impl<'de> Deserialize<'de> for SpaceEngineType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let str = String::deserialize(deserializer)?.trim().to_lowercase();
const MEMTX: &str = "memtx";
const VINYL: &str = "vinyl";
Ok(match str.as_str() {
MEMTX => Self::Memtx,
VINYL => Self::Vinyl,
_ => {
return Err(serde::de::Error::unknown_variant(
&str,
&[MEMTX, VINYL],
));
}
})
}
}
#[derive(Clone, Debug, Serialize)]
pub struct SpaceCreateOptions {
pub if_not_exists: bool,
pub engine: SpaceEngineType,
pub id: Option<u32>,
pub field_count: u32,
pub user: Option<String>,
pub is_local: bool,
pub is_temporary: bool,
pub is_sync: bool,
pub format: Option<Vec<Field>>,
}
impl Default for SpaceCreateOptions {
fn default() -> Self {
SpaceCreateOptions {
if_not_exists: false,
engine: SpaceEngineType::Memtx,
id: None,
field_count: 0,
user: None,
is_local: true,
is_temporary: true,
is_sync: false,
format: None,
}
}
}
#[deprecated = "Use `space::Field` instead"]
pub type SpaceFieldFormat = Field;
#[derive(Clone, Debug, Serialize)]
pub struct Field {
pub name: String, #[serde(alias = "type")]
pub field_type: SpaceFieldType,
pub is_nullable: bool,
}
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), "\""
)]
pub fn $constructor(name: &str) -> 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"]
pub fn new(name: &str, ft: SpaceFieldType) -> Self {
Self {
name: name.to_string(),
field_type: ft,
is_nullable: false,
}
}
pub fn is_nullable(mut self, is_nullable: bool) -> Self {
self.is_nullable = is_nullable;
self
}
define_constructors!{
any(SpaceFieldType::Any)
unsigned(SpaceFieldType::Unsigned)
string(SpaceFieldType::String)
number(SpaceFieldType::Number)
double(SpaceFieldType::Double)
integer(SpaceFieldType::Integer)
boolean(SpaceFieldType::Boolean)
decimal(SpaceFieldType::Decimal)
uuid(SpaceFieldType::Uuid)
array(SpaceFieldType::Array)
scalar(SpaceFieldType::Scalar)
}
}
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SpaceFieldType {
Any,
Unsigned,
String,
Number,
Double,
Integer,
Boolean,
Decimal,
Uuid,
Array,
Scalar,
}
const SPACE_FIELD_TYPE_ANY: &str = "any";
const SPACE_FIELD_TYPE_UNSIGNED: &str = "unsigned";
const SPACE_FIELD_TYPE_STRING: &str = "string";
const SPACE_FIELD_TYPE_NUMBER: &str = "number";
const SPACE_FIELD_TYPE_DOUBLE: &str = "double";
const SPACE_FIELD_TYPE_INTEGER: &str = "integer";
const SPACE_FIELD_TYPE_BOOLEAN: &str = "boolean";
const SPACE_FIELD_TYPE_DECIMAL: &str = "decimal";
const SPACE_FIELD_TYPE_UUID: &str = "uuid";
const SPACE_FIELD_TYPE_ARRAY: &str = "array";
const SPACE_FIELD_TYPE_SCALAR: &str = "scalar";
impl SpaceFieldType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Any => SPACE_FIELD_TYPE_ANY,
Self::Unsigned => SPACE_FIELD_TYPE_UNSIGNED,
Self::String => SPACE_FIELD_TYPE_STRING,
Self::Number => SPACE_FIELD_TYPE_NUMBER,
Self::Double => SPACE_FIELD_TYPE_DOUBLE,
Self::Integer => SPACE_FIELD_TYPE_INTEGER,
Self::Boolean => SPACE_FIELD_TYPE_BOOLEAN,
Self::Decimal => SPACE_FIELD_TYPE_DECIMAL,
Self::Uuid => SPACE_FIELD_TYPE_UUID,
Self::Array => SPACE_FIELD_TYPE_ARRAY,
Self::Scalar => SPACE_FIELD_TYPE_SCALAR,
}
}
}
impl<'de> Deserialize<'de> for SpaceFieldType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let str = String::deserialize(deserializer)?.trim().to_lowercase();
Ok(match str.as_str() {
SPACE_FIELD_TYPE_ANY => Self::Any,
SPACE_FIELD_TYPE_UNSIGNED => Self::Unsigned,
SPACE_FIELD_TYPE_STRING => Self::String,
SPACE_FIELD_TYPE_NUMBER => Self::Number,
SPACE_FIELD_TYPE_DOUBLE => Self::Double,
SPACE_FIELD_TYPE_INTEGER => Self::Integer,
SPACE_FIELD_TYPE_BOOLEAN => Self::Boolean,
SPACE_FIELD_TYPE_DECIMAL => Self::Decimal,
SPACE_FIELD_TYPE_UUID => Self::Uuid,
SPACE_FIELD_TYPE_ARRAY => Self::Array,
SPACE_FIELD_TYPE_SCALAR => Self::Scalar,
_ => {
return Err(serde::de::Error::unknown_variant(
&str,
&[
SPACE_FIELD_TYPE_ANY,
SPACE_FIELD_TYPE_UNSIGNED,
SPACE_FIELD_TYPE_STRING,
SPACE_FIELD_TYPE_NUMBER,
SPACE_FIELD_TYPE_DOUBLE,
SPACE_FIELD_TYPE_INTEGER,
SPACE_FIELD_TYPE_BOOLEAN,
SPACE_FIELD_TYPE_DECIMAL,
SPACE_FIELD_TYPE_UUID,
SPACE_FIELD_TYPE_ARRAY,
SPACE_FIELD_TYPE_SCALAR,
],
));
}
})
}
}
impl fmt::Display for SpaceFieldType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[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<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, 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).map(|space| {
cache.insert(name.to_string(), space.clone());
space
})
})
}
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).map(|index| {
cache.insert((space.id, name.to_string()), index.clone());
index
})
})
}
}
thread_local! {
static SPACE_CACHE: SpaceCache = SpaceCache::new();
}
pub fn clear_cache() {
SPACE_CACHE.with(SpaceCache::clear)
}
#[derive(Clone, Debug)]
pub struct Space {
id: u32,
}
impl Space {
pub fn builder(name: &str) -> Builder {
Builder::new(name)
}
#[cfg(feature = "schema")]
pub fn create(name: &str, opts: &SpaceCreateOptions) -> Result<Space, Error> {
crate::schema::space::create_space(name, opts)
}
#[cfg(feature = "schema")]
pub fn drop(&self) -> Result<(), Error> {
crate::schema::space::drop_space(self.id)
}
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 })
}
}
pub fn find_cached(name: &str) -> Option<Self> {
SPACE_CACHE.with(|cache| {
cache.space(name)
})
}
pub const fn id(&self) -> u32 {
self.id
}
#[cfg(feature = "schema")]
pub fn create_index(
&self,
name: &str,
opts: &crate::index::IndexOptions,
) -> Result<Index, Error> {
crate::schema::index::create_index(self.id, name, opts)
}
#[cfg(feature = "schema")]
pub fn index_builder<'a>(&self, name: &'a str) -> crate::index::Builder<'a> {
crate::index::Builder::new(self.id, name)
}
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))
}
}
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)
}
pub fn insert<T>(&mut self, value: &T) -> Result<Tuple, Error>
where
T: ToTupleBuffer,
{
let buf = value.to_tuple_buffer().unwrap();
let buf_ptr = buf.as_ptr() as *const c_char;
tuple_from_box_api!(
ffi::box_insert[
self.id,
buf_ptr,
buf_ptr.add(buf.len()),
@out
]
)
.map(|t| t.expect("Returned tuple cannot be null"))
}
pub fn replace<T>(&mut self, value: &T) -> Result<Tuple, Error>
where
T: ToTupleBuffer,
{
let buf = value.to_tuple_buffer().unwrap();
let buf_ptr = buf.as_ptr() as *const c_char;
tuple_from_box_api!(
ffi::box_replace[
self.id,
buf_ptr,
buf_ptr.add(buf.len()),
@out
]
)
.map(|t| t.expect("Returned tuple cannot be null"))
}
#[inline(always)]
pub fn put<T>(&mut self, value: &T) -> Result<Tuple, Error>
where
T: ToTupleBuffer,
{
self.replace(value)
}
pub fn truncate(&mut 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> {
self.primary_key().bsize()
}
#[inline(always)]
pub fn get<K>(&self, key: &K) -> Result<Option<Tuple>, Error>
where
K: ToTupleBuffer,
{
self.primary_key().get(key)
}
#[inline(always)]
pub fn select<K>(&self, iterator_type: IteratorType, key: &K) -> Result<IndexIterator, Error>
where
K: ToTupleBuffer,
{
self.primary_key().select(iterator_type, key)
}
pub fn count<K>(&self, iterator_type: IteratorType, key: &K) -> Result<usize, Error>
where
K: ToTupleBuffer,
{
self.primary_key().count(iterator_type, key)
}
#[inline(always)]
pub fn delete<K>(&mut self, key: &K) -> Result<Option<Tuple>, Error>
where
K: ToTupleBuffer,
{
self.primary_key().delete(key)
}
#[inline(always)]
pub fn update<K, Op>(&mut self, key: &K, ops: &[Op]) -> Result<Option<Tuple>, Error>
where
K: ToTupleBuffer,
Op: ToTupleBuffer,
{
self.primary_key().update(key, ops)
}
#[inline(always)]
pub fn update_mp<K>(&mut self, key: &K, ops: &[Vec<u8>]) -> Result<Option<Tuple>, Error>
where
K: ToTupleBuffer,
{
self.primary_key().update_mp(key, ops)
}
#[inline(always)]
pub fn upsert<T, Op>(&mut self, value: &T, ops: &[Op]) -> Result<(), Error>
where
T: ToTupleBuffer,
Op: ToTupleBuffer,
{
self.primary_key().upsert(value, ops)
}
#[inline(always)]
pub fn upsert_mp<K>(&mut self, key: &K, ops: &[Vec<u8>]) -> Result<(), Error>
where
K: ToTupleBuffer,
{
self.primary_key().upsert_mp(key, ops)
}
#[cfg(feature = "schema")]
pub fn meta(&self) -> Result<SpaceMetadata, Error> {
let sys_space: Space = SystemSpace::Space.into();
let tuple = sys_space.get(&(self.id,))?.ok_or(Error::MetaNotFound)?;
tuple.decode::<SpaceMetadata>()
}
}
#[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> {
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: u32)
field_count(field_count: u32)
user(user: String)
is_local(is_local: bool)
is_temporary(is_temporary: bool)
is_sync(is_sync: bool)
format(format: Vec<Field>)
}
pub fn field(mut self, field: Field) -> Self {
self.opts.format.get_or_insert_with(|| Vec::with_capacity(16))
.push(field);
self
}
#[cfg(feature = "schema")]
pub fn create(self) -> crate::Result<Space> {
crate::schema::space::create_space(self.name, &self.opts)
}
}
#[macro_export]
macro_rules! update {
($target:expr, $key: expr, $($op:expr),+ $(,)?) => {
{
use std::borrow::Borrow;
let mut f = || -> $crate::Result<::std::option::Option<$crate::tuple::Tuple>> {
let ops = [
$(
$crate::util::rmp_to_vec($op.borrow())?,
)+
];
$target.update_mp($key.borrow(), &ops)
};
f()
}
};
}
#[macro_export]
macro_rules! upsert {
($target:expr, $value: expr, $($op:expr),+ $(,)?) => {
{
use std::borrow::Borrow;
let mut f = || -> $crate::Result<()> {
let ops = [
$(
$crate::util::rmp_to_vec($op.borrow())?,
)+
];
$target.upsert_mp($value.borrow(), &ops)
};
f()
}
};
}