Implement all inspect queries

Also cleaned up space handling
next
Sayan Nandan 10 months ago
parent fce183afd9
commit 8d6a047f02
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -26,6 +26,10 @@ All changes in this project will be noted in this file.
- `USE <space>`:
- works just like SQL
- **does not work with DDL queries**: the reason it works in this way is to prevent accidental deletes
- `INSPECT ...`:
- `INSPECT global`: can be used to inspect the global state, seeing all spaces currently present on the system, users and other information. Some information is limited to the root account only (as JSON)
- `INSPECT space <space>`: can be used to inspect a single space, returning a list of models and relevant information (as JSON)
- `INSPECT model <model>`: can be used to inspect a single model, returning declaration and relevant information (as JSON)
- DML:
- **All actions removed**: All the prior `SET`, `GET` and other actions have been removed in favor of the new query language
- The following queries were added:

@ -80,6 +80,7 @@ pub enum ExecKind {
Standard(Query),
UseSpace(Query, String),
UseNull(Query),
PrintSpecial(Query),
}
impl Parameterizer {
@ -121,6 +122,12 @@ impl Parameterizer {
Ok(if qstr.eq_ignore_ascii_case("use null") {
ExecKind::UseNull(q)
} else {
if qstr.len() > 8 {
let qstr = &qstr[..8];
if qstr.eq_ignore_ascii_case("inspect ") {
return Ok(ExecKind::PrintSpecial(q));
}
}
let mut splits = qstr.split_ascii_whitespace();
let tok_use = splits.next();
let tok_name = splits.next();

@ -107,6 +107,7 @@ fn repl<C: IsConnection>(mut con: C) -> CliResult<()> {
match query::Parameterizer::new(line).parameterize() {
Ok(q) => {
let mut new_prompt = None;
let mut special = false;
let q = match q {
ExecKind::Standard(q) => q,
ExecKind::UseNull(q) => {
@ -117,8 +118,12 @@ fn repl<C: IsConnection>(mut con: C) -> CliResult<()> {
new_prompt = Some(format!("{space}> "));
q
}
ExecKind::PrintSpecial(q) => {
special = true;
q
}
};
if resp::format_response(con.execute_query(q)?)? {
if resp::format_response(con.execute_query(q)?, special) {
if let Some(pr) = new_prompt {
prompt = pr;
}

@ -25,51 +25,45 @@
*/
use {
crate::error::CliResult,
crossterm::{
style::{Color, ResetColor, SetForegroundColor},
ExecutableCommand,
},
crossterm::style::Stylize,
skytable::response::{Response, Row, Value},
std::io::{self, Write},
};
pub fn format_response(resp: Response) -> CliResult<bool> {
pub fn format_response(resp: Response, print_special: bool) -> bool {
match resp {
Response::Empty => print_cyan("(Okay)\n")?,
Response::Empty => println!("{}", "(Okay)".cyan()),
Response::Error(e) => {
print_red(&format!("(server error code: {e})\n"))?;
return Ok(false);
println!("{}", format!("(server error code: {e})").red());
return false;
}
Response::Value(v) => {
print_value(v)?;
print_value(v, print_special);
println!();
}
Response::Row(r) => {
print_row(r)?;
print_row(r);
println!();
}
};
Ok(true)
true
}
fn print_row(r: Row) -> CliResult<()> {
fn print_row(r: Row) {
print!("(");
let mut columns = r.into_values().into_iter().peekable();
while let Some(cell) = columns.next() {
print_value(cell)?;
print_value(cell, false);
if columns.peek().is_some() {
print!(", ");
}
}
print!(")");
Ok(())
}
fn print_value(v: Value) -> CliResult<()> {
fn print_value(v: Value, print_special: bool) {
match v {
Value::Null => print_gray("null")?,
Value::String(s) => print_string(&s),
Value::Null => print!("{}", "null".grey().italic()),
Value::String(s) => print_string(&s, print_special),
Value::Binary(b) => print_binary(&b),
Value::Bool(b) => print!("{b}"),
Value::UInt8(i) => print!("{i}"),
@ -86,7 +80,7 @@ fn print_value(v: Value) -> CliResult<()> {
print!("[");
let mut items = items.into_iter().peekable();
while let Some(item) = items.next() {
print_value(item)?;
print_value(item, print_special);
if items.peek().is_some() {
print!(", ");
}
@ -94,7 +88,6 @@ fn print_value(v: Value) -> CliResult<()> {
print!("]");
}
}
Ok(())
}
fn print_binary(b: &[u8]) {
@ -109,39 +102,22 @@ fn print_binary(b: &[u8]) {
print!("]");
}
fn print_string(s: &str) {
print!("\"");
for ch in s.chars() {
if ch == '"' || ch == '\'' {
print!("\\{ch}");
} else if ch == '\t' {
print!("\\t");
} else if ch == '\n' {
print!("\\n");
} else {
print!("{ch}");
fn print_string(s: &str, print_special: bool) {
if print_special {
print!("{}", s.italic().grey());
} else {
print!("\"");
for ch in s.chars() {
if ch == '"' {
print!("\\{ch}");
} else if ch == '\t' {
print!("\\t");
} else if ch == '\n' {
print!("\\n");
} else {
print!("{ch}");
}
}
print!("\"");
}
print!("\"");
}
fn print_gray(s: &str) -> std::io::Result<()> {
print_colored_text(s, Color::White)
}
fn print_red(s: &str) -> std::io::Result<()> {
print_colored_text(s, Color::Red)
}
fn print_cyan(s: &str) -> std::io::Result<()> {
print_colored_text(s, Color::Cyan)
}
fn print_colored_text(text: &str, color: Color) -> std::io::Result<()> {
let mut stdout = io::stdout();
stdout.execute(SetForegroundColor(color))?;
print!("{text}");
stdout.flush()?;
stdout.execute(ResetColor)?;
Ok(())
}

@ -0,0 +1,104 @@
/*
* Created on Thu Nov 30 2023
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use crate::engine::{
error::{QueryError, QueryResult},
fractal::GlobalInstanceLike,
net::protocol::{ClientLocalState, Response, ResponseType},
ql::ddl::Inspect,
};
pub fn inspect(
g: &impl GlobalInstanceLike,
c: &ClientLocalState,
stmt: Inspect,
) -> QueryResult<Response> {
let ret = match stmt {
Inspect::Global => {
// collect spaces
let spaces = g.namespace().idx().read();
let mut spaces_iter = spaces.iter().peekable();
let mut ret = format!("{{\"spaces\":[");
while let Some((space, _)) = spaces_iter.next() {
ret.push('"');
ret.push_str(&space);
ret.push('"');
if spaces_iter.peek().is_some() {
ret.push(',');
}
}
if c.is_root() {
// iff the user is root, show information about other users. if not, just show models and settings
ret.push_str("],\"users\":[");
drop(spaces_iter);
drop(spaces);
// collect users
let users = g.sys_store().system_store().auth_data().read();
let mut users_iter = users.users().iter().peekable();
while let Some((user, _)) = users_iter.next() {
ret.push('"');
ret.push_str(&user);
ret.push('"');
if users_iter.peek().is_some() {
ret.push(',');
}
}
}
ret.push_str("],\"settings\":{}}");
ret
}
Inspect::Model(m) => match g.namespace().idx_models().read().get(&m) {
Some(m) => format!(
"{{\"decl\":\"{}\",\"rows\":{},\"properties\":{{}}}}",
m.describe(),
m.primary_index().count()
),
None => return Err(QueryError::QExecObjectNotFound),
},
Inspect::Space(s) => match g.namespace().idx().read().get(s.as_str()) {
Some(s) => {
let mut ret = format!("{{\"models\":[");
let mut models_iter = s.models().iter().peekable();
while let Some(mdl) = models_iter.next() {
ret.push('\"');
ret.push_str(&mdl);
ret.push('\"');
if models_iter.peek().is_some() {
ret.push(',');
}
}
ret.push_str("]}}");
ret
}
None => return Err(QueryError::QExecObjectNotFound),
},
};
Ok(Response::Serialized {
ty: ResponseType::String,
size: ret.len(),
data: ret.into_bytes(),
})
}

@ -25,7 +25,7 @@
*/
use crate::engine::{
core::{dml, model::Model, space::Space},
core::{ddl_misc, dml, model::Model, space::Space},
error::{QueryError, QueryResult},
fractal::{Global, GlobalInstanceLike},
net::protocol::{ClientLocalState, Response, ResponseType, SQuery},
@ -63,7 +63,7 @@ pub async fn dispatch_to_executor<'a>(
}
#[inline(always)]
fn _call<A: ASTNode<'static> + core::fmt::Debug, T>(
fn _callgs<A: ASTNode<'static> + core::fmt::Debug, T>(
g: &Global,
state: &mut State<'static, InplaceData>,
f: impl FnOnce(&Global, A) -> Result<T, QueryError>,
@ -72,6 +72,17 @@ fn _call<A: ASTNode<'static> + core::fmt::Debug, T>(
f(&g, cs)
}
#[inline(always)]
fn _callgcs<A: ASTNode<'static> + core::fmt::Debug, T>(
g: &Global,
cstate: &ClientLocalState,
state: &mut State<'static, InplaceData>,
f: impl FnOnce(&Global, &ClientLocalState, A) -> Result<T, QueryError>,
) -> QueryResult<T> {
let a = ASTNode::parse_from_state_hardened(state)?;
f(&g, cstate, a)
}
async fn run_blocking_stmt(
global: &Global,
cstate: &mut ClientLocalState,
@ -109,12 +120,12 @@ async fn run_blocking_stmt(
) -> QueryResult<()>; 8] = [
|_, _, _| Err(QueryError::QLUnknownStatement),
blocking_exec_sysctl,
|g, _, t| _call(&g, t, Space::transactional_exec_create),
|g, _, t| _call(&g, t, Model::transactional_exec_create),
|g, _, t| _call(&g, t, Space::transactional_exec_alter),
|g, _, t| _call(&g, t, Model::transactional_exec_alter),
|g, _, t| _call(&g, t, Space::transactional_exec_drop),
|g, _, t| _call(&g, t, Model::transactional_exec_drop),
|g, _, t| _callgs(&g, t, Space::transactional_exec_create),
|g, _, t| _callgs(&g, t, Model::transactional_exec_create),
|g, _, t| _callgs(&g, t, Space::transactional_exec_alter),
|g, _, t| _callgs(&g, t, Model::transactional_exec_alter),
|g, _, t| _callgs(&g, t, Space::transactional_exec_drop),
|g, _, t| _callgs(&g, t, Model::transactional_exec_drop),
];
let r = unsafe {
// UNSAFE(@ohsayan): the only await is within this block
@ -192,13 +203,13 @@ fn run_nb(
&mut ClientLocalState,
&mut State<'static, InplaceData>,
) -> QueryResult<Response>; 8] = [
cstate_use, // use
|_, _, _| Err(QueryError::QLUnknownStatement), // inspect
cstate_use, // use
|g, c, s| _callgcs(g, c, s, ddl_misc::inspect),
|_, _, _| Err(QueryError::QLUnknownStatement), // describe
|g, _, s| _call(g, s, dml::insert_resp),
|g, _, s| _call(g, s, dml::select_resp),
|g, _, s| _call(g, s, dml::update_resp),
|g, _, s| _call(g, s, dml::delete_resp),
|g, _, s| _callgs(g, s, dml::insert_resp),
|g, _, s| _callgs(g, s, dml::select_resp),
|g, _, s| _callgs(g, s, dml::update_resp),
|g, _, s| _callgs(g, s, dml::delete_resp),
|_, _, _| Err(QueryError::QLUnknownStatement), // exists
];
{

@ -25,6 +25,7 @@
*/
pub(in crate::engine) mod dcl;
pub(super) mod ddl_misc;
pub(in crate::engine) mod dml;
pub(in crate::engine) mod exec;
pub(in crate::engine) mod index;
@ -58,7 +59,7 @@ type RWLIdx<K, V> = RwLock<IndexST<K, V>>;
#[cfg_attr(test, derive(Debug))]
pub struct GlobalNS {
idx_mdl: RWLIdx<EntityID, Model>,
idx: RWLIdx<Box<str>, RwLock<Space>>,
idx: RWLIdx<Box<str>, Space>,
}
impl GlobalNS {
@ -70,7 +71,7 @@ impl GlobalNS {
}
pub fn ddl_with_spaces_write<T>(
&self,
f: impl FnOnce(&mut HashMap<Box<str>, RwLock<Space>>) -> T,
f: impl FnOnce(&mut HashMap<Box<str>, Space>) -> T,
) -> T {
let mut spaces = self.idx.write();
f(&mut spaces)
@ -80,12 +81,11 @@ impl GlobalNS {
space: &str,
f: impl FnOnce(&mut Space) -> QueryResult<T>,
) -> QueryResult<T> {
let spaces = self.idx.read();
let Some(space) = spaces.get(space) else {
let mut spaces = self.idx.write();
let Some(space) = spaces.get_mut(space) else {
return Err(QueryError::QExecObjectNotFound);
};
let mut space = space.write();
f(&mut space)
f(space)
}
pub fn with_model_space_mut_for_ddl<'a, T, F>(
&self,
@ -100,8 +100,8 @@ impl GlobalNS {
return Err(QueryError::QExecObjectNotFound);
};
let space_read = self.idx.read();
let space = space_read.get(entity.space()).unwrap().read();
f(&space, model)
let space = space_read.get(entity.space()).unwrap();
f(space, model)
}
pub fn with_model<'a, T, F>(&self, entity: EntityIDRef<'a>, f: F) -> QueryResult<T>
where
@ -116,7 +116,7 @@ impl GlobalNS {
pub fn idx_models(&self) -> &RWLIdx<EntityID, Model> {
&self.idx_mdl
}
pub fn idx(&self) -> &RWLIdx<Box<str>, RwLock<Space>> {
pub fn idx(&self) -> &RWLIdx<Box<str>, Space> {
&self.idx
}
#[cfg(test)]

@ -121,7 +121,7 @@ impl Model {
&self.decl
}
fn redescribe(&self) -> String {
let mut ret = format!("{{ ");
let mut ret = format!("{{");
let mut it = self.fields().stseq_ord_kv().peekable();
while let Some((field_name, field_decl)) = it.next() {
// legend: * -> primary, ! -> not null, ? -> null
@ -153,7 +153,6 @@ impl Model {
ret.push(' ');
}
}
ret.push(' ');
ret.push('}');
ret
}

@ -34,7 +34,6 @@ use {
storage::v1::{loader::SEInitState, RawFSInterface},
txn::gns as gnstxn,
},
parking_lot::RwLock,
std::collections::HashSet,
};
@ -174,7 +173,7 @@ impl Space {
}
}
// update global state
let _ = spaces.st_insert(space_name, RwLock::new(space));
let _ = spaces.st_insert(space_name, space);
Ok(())
})
}
@ -228,7 +227,6 @@ impl Space {
let Some(space) = spaces.get(space_name.as_str()) else {
return Err(QueryError::QExecObjectNotFound);
};
let space = space.read();
if !space.models.is_empty() {
// nonempty, we can't do anything
return Err(QueryError::QExecDdlNotEmpty);
@ -245,8 +243,6 @@ impl Space {
space.get_uuid(),
)));
}
// good, we can get rid of this thing
drop(space);
let _ = spaces.st_delete(space_name.as_str());
Ok(())
})

@ -32,10 +32,13 @@ pub(in crate::engine) mod drop;
use {
super::{
ast::traits::ASTNode,
ast::{traits::ASTNode, QueryData, State},
lex::{Ident, Token},
},
crate::engine::error::QueryError,
crate::engine::{
core::EntityIDRef,
error::{QueryError, QueryResult},
},
};
#[derive(Debug, PartialEq)]
@ -48,9 +51,9 @@ pub enum Use<'a> {
impl<'a> ASTNode<'a> for Use<'a> {
const MUST_USE_FULL_TOKEN_RANGE: bool = true;
const VERIFIES_FULL_TOKEN_RANGE_USAGE: bool = false;
fn __base_impl_parse_from_state<Qd: super::ast::QueryData<'a>>(
state: &mut super::ast::State<'a, Qd>,
) -> crate::engine::error::QueryResult<Self> {
fn __base_impl_parse_from_state<Qd: QueryData<'a>>(
state: &mut State<'a, Qd>,
) -> QueryResult<Self> {
/*
should have either an ident or null
*/
@ -73,3 +76,40 @@ impl<'a> ASTNode<'a> for Use<'a> {
})
}
}
#[derive(Debug, PartialEq)]
pub enum Inspect<'a> {
Global,
Space(Ident<'a>),
Model(EntityIDRef<'a>),
}
impl<'a> ASTNode<'a> for Inspect<'a> {
const MUST_USE_FULL_TOKEN_RANGE: bool = true;
const VERIFIES_FULL_TOKEN_RANGE_USAGE: bool = false;
fn __base_impl_parse_from_state<Qd: QueryData<'a>>(
state: &mut State<'a, Qd>,
) -> QueryResult<Self> {
if state.exhausted() {
return Err(QueryError::QLUnexpectedEndOfStatement);
}
let me = match state.fw_read() {
Token::Ident(id) if id.eq_ignore_ascii_case("global") => Self::Global,
Token![space] => {
if state.exhausted() {
return Err(QueryError::QLUnexpectedEndOfStatement);
}
match state.fw_read() {
Token::Ident(space) => Self::Space(*space),
_ => return Err(QueryError::QLInvalidSyntax),
}
}
Token![model] => {
let entity = state.try_entity_ref_result()?;
Self::Model(entity)
}
_ => return Err(QueryError::QLInvalidSyntax),
};
Ok(me)
}
}

@ -27,7 +27,7 @@
use super::*;
use crate::engine::ql::{
ast::{traits::ASTNode, State},
ddl::Use,
ddl::{Inspect, Use},
};
/*
@ -83,3 +83,33 @@ fn use_current() {
Use::RefreshCurrent
);
}
#[test]
fn inspect_global() {
let t = lex_insecure(b"inspect global").unwrap();
let mut state = State::new_inplace(&t[1..]);
assert_eq!(
Inspect::test_parse_from_state(&mut state).unwrap(),
Inspect::Global
);
}
#[test]
fn inspect_space() {
let t = lex_insecure(b"inspect space myspace").unwrap();
let mut state = State::new_inplace(&t[1..]);
assert_eq!(
Inspect::test_parse_from_state(&mut state).unwrap(),
Inspect::Space("myspace".into())
);
}
#[test]
fn inspect_model() {
let t = lex_insecure(b"inspect model myspace.mymodel").unwrap();
let mut state = State::new_inplace(&t[1..]);
assert_eq!(
Inspect::test_parse_from_state(&mut state).unwrap(),
Inspect::Model(("myspace", "mymodel").into())
);
}

@ -79,7 +79,6 @@ impl SEInitState {
let mut models = gns.idx_models().write();
// this is an existing instance, so read in all data
for (space_name, space) in gns.idx().read().iter() {
let space = space.read();
let space_uuid = space.get_uuid();
for model_name in space.models().iter() {
let model = models

@ -0,0 +1,41 @@
/*
* Created on Thu Nov 30 2023
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use {sky_macros::dbtest, skytable::query};
#[dbtest]
fn inspect_global_as_root_returns_user_info() {
let mut db = db!();
let inspect: String = db.query_parse(&query!("inspect global")).unwrap();
assert!(inspect.contains("\"users\":"));
}
#[dbtest]
fn inspect_global_as_std_user_does_not_return_user_info() {
let mut db = db!();
let inspect: String = db.query_parse(&query!("inspect global")).unwrap();
assert!(!inspect.contains("\"users\":"));
}

@ -24,5 +24,6 @@
*
*/
mod ddl;
mod sec;
mod sysctl;

@ -164,7 +164,6 @@ fn with_space<T>(
let Some(space) = spaces.st_get(&space_id.name) else {
return Err(TransactionError::OnRestoreDataMissing.into());
};
let space = space.read();
if space.get_uuid() != space_id.uuid {
return Err(TransactionError::OnRestoreDataConflictMismatch.into());
}
@ -176,15 +175,14 @@ fn with_space_mut<T>(
space_id: &super::SpaceIDRes,
mut f: impl FnMut(&mut Space) -> RuntimeResult<T>,
) -> RuntimeResult<T> {
let spaces = gns.idx().read();
let Some(space) = spaces.st_get(&space_id.name) else {
let mut spaces = gns.idx().write();
let Some(space) = spaces.st_get_mut(&space_id.name) else {
return Err(TransactionError::OnRestoreDataMissing.into());
};
let mut space = space.write();
if space.get_uuid() != space_id.uuid {
return Err(TransactionError::OnRestoreDataConflictMismatch.into());
}
f(&mut space)
f(space)
}
fn with_model_mut<T>(
@ -318,12 +316,11 @@ impl<'a> GNSEvent for CreateModelTxn<'a> {
(or well magnetic stuff arounding spinning disks). But we just want to be extra sure. Don't let the aliens (or
rather, radiation) from the cosmos deter us!
*/
let spaces = gns.idx().write();
let mut spaces = gns.idx().write();
let mut models = gns.idx_models().write();
let Some(space) = spaces.get(&space_id.name) else {
let Some(space) = spaces.get_mut(&space_id.name) else {
return Err(TransactionError::OnRestoreDataMissing.into());
};
let mut space = space.write();
if space.models().contains(&model_name) {
return Err(TransactionError::OnRestoreDataConflictAlreadyExists.into());
}

@ -215,10 +215,9 @@ impl<'a> GNSEvent for AlterSpaceTxn<'a> {
}: Self::RestoreType,
gns: &crate::engine::core::GlobalNS,
) -> RuntimeResult<()> {
let gns = gns.idx().read();
match gns.st_get(&space_id.name) {
let mut gns = gns.idx().write();
match gns.st_get_mut(&space_id.name) {
Some(space) => {
let mut space = space.write();
if !crate::engine::data::dict::rmerge_metadata(space.props_mut(), space_meta) {
return Err(TransactionError::OnRestoreDataConflictMismatch.into());
}
@ -281,10 +280,8 @@ impl<'a> GNSEvent for DropSpaceTxn<'a> {
let mut wgns = gns.idx().write();
match wgns.entry(name) {
std::collections::hash_map::Entry::Occupied(oe) => {
let space = oe.get().read();
if space.get_uuid() == uuid {
if oe.get().get_uuid() == uuid {
// NB(@ohsayan): we do not need to remove models here since they must have been already removed for this query to have actually executed
drop(space);
oe.remove_entry();
Ok(())
} else {

@ -62,7 +62,6 @@ fn init_space(global: &impl GlobalInstanceLike, space_name: &str, env: &str) ->
.read()
.get(name.as_str())
.unwrap()
.read()
.get_uuid()
}
@ -78,7 +77,7 @@ fn create_space() {
multirun(|| {
let global = TestGlobal::new_with_vfs_driver(log_name);
let spaces = global.namespace().idx().read();
let space = spaces.get("myspace").unwrap().read();
let space = spaces.get("myspace").unwrap();
assert_eq!(
&*space,
&Space::new_restore_empty(
@ -108,7 +107,7 @@ fn alter_space() {
multirun(|| {
let global = TestGlobal::new_with_vfs_driver(log_name);
let spaces = global.namespace().idx().read();
let space = spaces.get("myspace").unwrap().read();
let space = spaces.get("myspace").unwrap();
assert_eq!(
&*space,
&Space::new_restore_empty(

Loading…
Cancel
Save