Feat: Introduce Rebuild command (#3933)
This commit is contained in:
parent
8f6af53de6
commit
8172753ac4
16 changed files with 441 additions and 14 deletions
|
@ -157,9 +157,9 @@ async fn process_change_set_for_notifications(
|
||||||
"There are {} table mutations being prepared for notifications",
|
"There are {} table mutations being prepared for notifications",
|
||||||
table_mutations.1.len()
|
table_mutations.1.len()
|
||||||
);
|
);
|
||||||
for (i, mutation) in table_mutations.1.iter().enumerate() {
|
for (_i, mutation) in table_mutations.1.iter().enumerate() {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
trace!("[{} @ {:?}] Processing table mutation: {:?} Constructing document from mutation", i, change_vs, mutation);
|
trace!("[{} @ {:?}] Processing table mutation: {:?} Constructing document from mutation", _i, change_vs, mutation);
|
||||||
if let Some(doc) = construct_document(mutation)? {
|
if let Some(doc) = construct_document(mutation)? {
|
||||||
// We know we are only processing a single LQ at a time, so we can limit notifications to 1
|
// We know we are only processing a single LQ at a time, so we can limit notifications to 1
|
||||||
let notification_capacity = 1;
|
let notification_capacity = 1;
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::doc::CursorDoc;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::fmt::{is_pretty, pretty_indent, Fmt, Pretty};
|
use crate::sql::fmt::{is_pretty, pretty_indent, Fmt, Pretty};
|
||||||
use crate::sql::statements::info::InfoStructure;
|
use crate::sql::statements::info::InfoStructure;
|
||||||
|
use crate::sql::statements::rebuild::RebuildStatement;
|
||||||
use crate::sql::statements::{
|
use crate::sql::statements::{
|
||||||
BreakStatement, ContinueStatement, CreateStatement, DefineStatement, DeleteStatement,
|
BreakStatement, ContinueStatement, CreateStatement, DefineStatement, DeleteStatement,
|
||||||
ForeachStatement, IfelseStatement, InsertStatement, OutputStatement, RelateStatement,
|
ForeachStatement, IfelseStatement, InsertStatement, OutputStatement, RelateStatement,
|
||||||
|
@ -101,6 +102,9 @@ impl Block {
|
||||||
Entry::Define(v) => {
|
Entry::Define(v) => {
|
||||||
v.compute(stk, &ctx, opt, txn, doc).await?;
|
v.compute(stk, &ctx, opt, txn, doc).await?;
|
||||||
}
|
}
|
||||||
|
Entry::Rebuild(v) => {
|
||||||
|
v.compute(stk, &ctx, opt, txn, doc).await?;
|
||||||
|
}
|
||||||
Entry::Remove(v) => {
|
Entry::Remove(v) => {
|
||||||
v.compute(&ctx, opt, txn, doc).await?;
|
v.compute(&ctx, opt, txn, doc).await?;
|
||||||
}
|
}
|
||||||
|
@ -175,7 +179,7 @@ impl InfoStructure for Block {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 2)]
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -196,6 +200,8 @@ pub enum Entry {
|
||||||
Break(BreakStatement),
|
Break(BreakStatement),
|
||||||
Continue(ContinueStatement),
|
Continue(ContinueStatement),
|
||||||
Foreach(ForeachStatement),
|
Foreach(ForeachStatement),
|
||||||
|
#[revision(start = 2)]
|
||||||
|
Rebuild(RebuildStatement),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for Entry {
|
impl PartialOrd for Entry {
|
||||||
|
@ -220,6 +226,7 @@ impl Entry {
|
||||||
Self::Insert(v) => v.writeable(),
|
Self::Insert(v) => v.writeable(),
|
||||||
Self::Output(v) => v.writeable(),
|
Self::Output(v) => v.writeable(),
|
||||||
Self::Define(v) => v.writeable(),
|
Self::Define(v) => v.writeable(),
|
||||||
|
Self::Rebuild(v) => v.writeable(),
|
||||||
Self::Remove(v) => v.writeable(),
|
Self::Remove(v) => v.writeable(),
|
||||||
Self::Throw(v) => v.writeable(),
|
Self::Throw(v) => v.writeable(),
|
||||||
Self::Break(v) => v.writeable(),
|
Self::Break(v) => v.writeable(),
|
||||||
|
@ -243,6 +250,7 @@ impl Display for Entry {
|
||||||
Self::Insert(v) => write!(f, "{v}"),
|
Self::Insert(v) => write!(f, "{v}"),
|
||||||
Self::Output(v) => write!(f, "{v}"),
|
Self::Output(v) => write!(f, "{v}"),
|
||||||
Self::Define(v) => write!(f, "{v}"),
|
Self::Define(v) => write!(f, "{v}"),
|
||||||
|
Self::Rebuild(v) => write!(f, "{v}"),
|
||||||
Self::Remove(v) => write!(f, "{v}"),
|
Self::Remove(v) => write!(f, "{v}"),
|
||||||
Self::Throw(v) => write!(f, "{v}"),
|
Self::Throw(v) => write!(f, "{v}"),
|
||||||
Self::Break(v) => write!(f, "{v}"),
|
Self::Break(v) => write!(f, "{v}"),
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::ctx::Context;
|
||||||
use crate::dbs::{Options, Transaction};
|
use crate::dbs::{Options, Transaction};
|
||||||
use crate::doc::CursorDoc;
|
use crate::doc::CursorDoc;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
|
use crate::sql::statements::rebuild::RebuildStatement;
|
||||||
use crate::sql::{
|
use crate::sql::{
|
||||||
fmt::{Fmt, Pretty},
|
fmt::{Fmt, Pretty},
|
||||||
statements::{
|
statements::{
|
||||||
|
@ -53,7 +54,7 @@ impl Display for Statements {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 2)]
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -85,6 +86,8 @@ pub enum Statement {
|
||||||
Update(UpdateStatement),
|
Update(UpdateStatement),
|
||||||
Throw(ThrowStatement),
|
Throw(ThrowStatement),
|
||||||
Use(UseStatement),
|
Use(UseStatement),
|
||||||
|
#[revision(start = 2)]
|
||||||
|
Rebuild(RebuildStatement),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Statement {
|
impl Statement {
|
||||||
|
@ -118,6 +121,7 @@ impl Statement {
|
||||||
Self::Live(_) => true,
|
Self::Live(_) => true,
|
||||||
Self::Output(v) => v.writeable(),
|
Self::Output(v) => v.writeable(),
|
||||||
Self::Option(_) => false,
|
Self::Option(_) => false,
|
||||||
|
Self::Rebuild(_) => true,
|
||||||
Self::Relate(v) => v.writeable(),
|
Self::Relate(v) => v.writeable(),
|
||||||
Self::Remove(_) => true,
|
Self::Remove(_) => true,
|
||||||
Self::Select(v) => v.writeable(),
|
Self::Select(v) => v.writeable(),
|
||||||
|
@ -154,6 +158,7 @@ impl Statement {
|
||||||
Self::Live(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
Self::Live(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
||||||
Self::Output(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
Self::Output(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
||||||
Self::Relate(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
Self::Relate(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
||||||
|
Self::Rebuild(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
||||||
Self::Remove(v) => v.compute(ctx, opt, txn, doc).await,
|
Self::Remove(v) => v.compute(ctx, opt, txn, doc).await,
|
||||||
Self::Select(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
Self::Select(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
||||||
Self::Set(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
Self::Set(v) => v.compute(stk, ctx, opt, txn, doc).await,
|
||||||
|
@ -193,6 +198,7 @@ impl Display for Statement {
|
||||||
Self::Live(v) => write!(Pretty::from(f), "{v}"),
|
Self::Live(v) => write!(Pretty::from(f), "{v}"),
|
||||||
Self::Option(v) => write!(Pretty::from(f), "{v}"),
|
Self::Option(v) => write!(Pretty::from(f), "{v}"),
|
||||||
Self::Output(v) => write!(Pretty::from(f), "{v}"),
|
Self::Output(v) => write!(Pretty::from(f), "{v}"),
|
||||||
|
Self::Rebuild(v) => write!(Pretty::from(f), "{v}"),
|
||||||
Self::Relate(v) => write!(Pretty::from(f), "{v}"),
|
Self::Relate(v) => write!(Pretty::from(f), "{v}"),
|
||||||
Self::Remove(v) => write!(Pretty::from(f), "{v}"),
|
Self::Remove(v) => write!(Pretty::from(f), "{v}"),
|
||||||
Self::Select(v) => write!(Pretty::from(f), "{v}"),
|
Self::Select(v) => write!(Pretty::from(f), "{v}"),
|
||||||
|
|
|
@ -86,6 +86,7 @@ impl ForeachStatement {
|
||||||
stk.run(|stk| v.compute(stk, &ctx, opt, txn, doc)).await
|
stk.run(|stk| v.compute(stk, &ctx, opt, txn, doc)).await
|
||||||
}
|
}
|
||||||
Entry::Define(v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
Entry::Define(v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
||||||
|
Entry::Rebuild(v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
||||||
Entry::Remove(v) => v.compute(&ctx, opt, txn, doc).await,
|
Entry::Remove(v) => v.compute(&ctx, opt, txn, doc).await,
|
||||||
Entry::Output(v) => {
|
Entry::Output(v) => {
|
||||||
return stk.run(|stk| v.compute(stk, &ctx, opt, txn, doc)).await;
|
return stk.run(|stk| v.compute(stk, &ctx, opt, txn, doc)).await;
|
||||||
|
|
|
@ -15,6 +15,7 @@ pub(crate) mod kill;
|
||||||
pub(crate) mod live;
|
pub(crate) mod live;
|
||||||
pub(crate) mod option;
|
pub(crate) mod option;
|
||||||
pub(crate) mod output;
|
pub(crate) mod output;
|
||||||
|
pub(crate) mod rebuild;
|
||||||
pub(crate) mod relate;
|
pub(crate) mod relate;
|
||||||
pub(crate) mod remove;
|
pub(crate) mod remove;
|
||||||
pub(crate) mod select;
|
pub(crate) mod select;
|
||||||
|
|
118
core/src/sql/statements/rebuild.rs
Normal file
118
core/src/sql/statements/rebuild.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use crate::ctx::Context;
|
||||||
|
use crate::dbs::Options;
|
||||||
|
use crate::dbs::Transaction;
|
||||||
|
use crate::doc::CursorDoc;
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::iam::{Action, ResourceKind};
|
||||||
|
use crate::sql::ident::Ident;
|
||||||
|
use crate::sql::statements::RemoveIndexStatement;
|
||||||
|
use crate::sql::value::Value;
|
||||||
|
use crate::sql::Base;
|
||||||
|
use derive::Store;
|
||||||
|
use reblessive::tree::Stk;
|
||||||
|
use revision::revisioned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum RebuildStatement {
|
||||||
|
Index(RebuildIndexStatement),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RebuildStatement {
|
||||||
|
/// Check if we require a writeable transaction
|
||||||
|
pub(crate) fn writeable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
/// Process this type returning a computed simple Value
|
||||||
|
pub(crate) async fn compute(
|
||||||
|
&self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
opt: &Options,
|
||||||
|
txn: &Transaction,
|
||||||
|
doc: Option<&CursorDoc<'_>>,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
match self {
|
||||||
|
Self::Index(s) => s.compute(stk, ctx, opt, txn, doc).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RebuildStatement {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Index(v) => Display::fmt(v, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct RebuildIndexStatement {
|
||||||
|
pub name: Ident,
|
||||||
|
pub what: Ident,
|
||||||
|
pub if_exists: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RebuildIndexStatement {
|
||||||
|
/// Process this type returning a computed simple Value
|
||||||
|
pub(crate) async fn compute(
|
||||||
|
&self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
opt: &Options,
|
||||||
|
txn: &Transaction,
|
||||||
|
doc: Option<&CursorDoc<'_>>,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
let future = async {
|
||||||
|
// Allowed to run?
|
||||||
|
opt.is_allowed(Action::Edit, ResourceKind::Index, &Base::Db)?;
|
||||||
|
|
||||||
|
// Get the index definition
|
||||||
|
let ix = txn
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get_and_cache_tb_index(opt.ns(), opt.db(), self.what.as_str(), self.name.as_str())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Remove the index
|
||||||
|
let remove = RemoveIndexStatement {
|
||||||
|
name: self.name.clone(),
|
||||||
|
what: self.what.clone(),
|
||||||
|
if_exists: false,
|
||||||
|
};
|
||||||
|
remove.compute(ctx, opt, txn).await?;
|
||||||
|
|
||||||
|
// Rebuild the index
|
||||||
|
ix.compute(stk, ctx, opt, txn, doc).await?;
|
||||||
|
|
||||||
|
// Return the result object
|
||||||
|
Ok(Value::None)
|
||||||
|
}
|
||||||
|
.await;
|
||||||
|
match future {
|
||||||
|
Err(Error::IxNotFound {
|
||||||
|
..
|
||||||
|
}) if self.if_exists => Ok(Value::None),
|
||||||
|
v => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RebuildIndexStatement {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "REBUILD INDEX")?;
|
||||||
|
if self.if_exists {
|
||||||
|
write!(f, " IF EXISTS")?
|
||||||
|
}
|
||||||
|
write!(f, " {} ON {}", self.name, self.what)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ use crate::ctx::Context;
|
||||||
use crate::dbs::{Options, Transaction};
|
use crate::dbs::{Options, Transaction};
|
||||||
use crate::doc::CursorDoc;
|
use crate::doc::CursorDoc;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
|
use crate::sql::statements::rebuild::RebuildStatement;
|
||||||
use crate::sql::statements::{
|
use crate::sql::statements::{
|
||||||
CreateStatement, DefineStatement, DeleteStatement, IfelseStatement, InsertStatement,
|
CreateStatement, DefineStatement, DeleteStatement, IfelseStatement, InsertStatement,
|
||||||
OutputStatement, RelateStatement, RemoveStatement, SelectStatement, UpdateStatement,
|
OutputStatement, RelateStatement, RemoveStatement, SelectStatement, UpdateStatement,
|
||||||
|
@ -15,7 +16,7 @@ use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Subquery";
|
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Subquery";
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 2)]
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||||
#[serde(rename = "$surrealdb::private::sql::Subquery")]
|
#[serde(rename = "$surrealdb::private::sql::Subquery")]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
@ -32,6 +33,8 @@ pub enum Subquery {
|
||||||
Insert(InsertStatement),
|
Insert(InsertStatement),
|
||||||
Define(DefineStatement),
|
Define(DefineStatement),
|
||||||
Remove(RemoveStatement),
|
Remove(RemoveStatement),
|
||||||
|
#[revision(start = 2)]
|
||||||
|
Rebuild(RebuildStatement),
|
||||||
// Add new variants here
|
// Add new variants here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +60,7 @@ impl Subquery {
|
||||||
Self::Insert(v) => v.writeable(),
|
Self::Insert(v) => v.writeable(),
|
||||||
Self::Define(v) => v.writeable(),
|
Self::Define(v) => v.writeable(),
|
||||||
Self::Remove(v) => v.writeable(),
|
Self::Remove(v) => v.writeable(),
|
||||||
|
Self::Rebuild(v) => v.writeable(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Process this type returning a computed simple Value
|
/// Process this type returning a computed simple Value
|
||||||
|
@ -80,6 +84,7 @@ impl Subquery {
|
||||||
Self::Ifelse(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
Self::Ifelse(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
||||||
Self::Output(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
Self::Output(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
||||||
Self::Define(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
Self::Define(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
||||||
|
Self::Rebuild(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
||||||
Self::Remove(ref v) => v.compute(&ctx, opt, txn, doc).await,
|
Self::Remove(ref v) => v.compute(&ctx, opt, txn, doc).await,
|
||||||
Self::Select(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
Self::Select(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
||||||
Self::Create(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
Self::Create(ref v) => v.compute(stk, &ctx, opt, txn, doc).await,
|
||||||
|
@ -104,6 +109,7 @@ impl Display for Subquery {
|
||||||
Self::Insert(v) => write!(f, "({v})"),
|
Self::Insert(v) => write!(f, "({v})"),
|
||||||
Self::Define(v) => write!(f, "({v})"),
|
Self::Define(v) => write!(f, "({v})"),
|
||||||
Self::Remove(v) => write!(f, "({v})"),
|
Self::Remove(v) => write!(f, "({v})"),
|
||||||
|
Self::Rebuild(v) => write!(f, "({v})"),
|
||||||
Self::Ifelse(v) => Display::fmt(v, f),
|
Self::Ifelse(v) => Display::fmt(v, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub mod kill;
|
||||||
pub mod live;
|
pub mod live;
|
||||||
pub mod option;
|
pub mod option;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
|
pub mod rebuild;
|
||||||
pub mod relate;
|
pub mod relate;
|
||||||
pub mod remove;
|
pub mod remove;
|
||||||
pub mod select;
|
pub mod select;
|
||||||
|
@ -76,6 +77,7 @@ impl ser::Serializer for Serializer {
|
||||||
"Live" => Ok(Statement::Live(value.serialize(live::Serializer.wrap())?)),
|
"Live" => Ok(Statement::Live(value.serialize(live::Serializer.wrap())?)),
|
||||||
"Option" => Ok(Statement::Option(value.serialize(option::Serializer.wrap())?)),
|
"Option" => Ok(Statement::Option(value.serialize(option::Serializer.wrap())?)),
|
||||||
"Output" => Ok(Statement::Output(value.serialize(output::Serializer.wrap())?)),
|
"Output" => Ok(Statement::Output(value.serialize(output::Serializer.wrap())?)),
|
||||||
|
"Rebuild" => Ok(Statement::Rebuild(value.serialize(rebuild::Serializer.wrap())?)),
|
||||||
"Relate" => Ok(Statement::Relate(value.serialize(relate::Serializer.wrap())?)),
|
"Relate" => Ok(Statement::Relate(value.serialize(relate::Serializer.wrap())?)),
|
||||||
"Remove" => Ok(Statement::Remove(value.serialize(remove::Serializer.wrap())?)),
|
"Remove" => Ok(Statement::Remove(value.serialize(remove::Serializer.wrap())?)),
|
||||||
"Select" => Ok(Statement::Select(value.serialize(select::Serializer.wrap())?)),
|
"Select" => Ok(Statement::Select(value.serialize(select::Serializer.wrap())?)),
|
||||||
|
|
91
core/src/sql/value/serde/ser/statement/rebuild/index.rs
Normal file
91
core/src/sql/value/serde/ser/statement/rebuild/index.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::statements::rebuild::RebuildIndexStatement;
|
||||||
|
use crate::sql::value::serde::ser;
|
||||||
|
use crate::sql::Ident;
|
||||||
|
use ser::Serializer as _;
|
||||||
|
use serde::ser::Error as _;
|
||||||
|
use serde::ser::Impossible;
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Serializer;
|
||||||
|
|
||||||
|
impl ser::Serializer for Serializer {
|
||||||
|
type Ok = RebuildIndexStatement;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<RebuildIndexStatement, Error>;
|
||||||
|
type SerializeTuple = Impossible<RebuildIndexStatement, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<RebuildIndexStatement, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<RebuildIndexStatement, Error>;
|
||||||
|
type SerializeMap = Impossible<RebuildIndexStatement, Error>;
|
||||||
|
type SerializeStruct = SerializeRebuildIndexStatement;
|
||||||
|
type SerializeStructVariant = Impossible<RebuildIndexStatement, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "a struct `RebuildIndexStatement`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_struct(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStruct, Error> {
|
||||||
|
Ok(SerializeRebuildIndexStatement::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct SerializeRebuildIndexStatement {
|
||||||
|
name: Ident,
|
||||||
|
what: Ident,
|
||||||
|
if_exists: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::ser::SerializeStruct for SerializeRebuildIndexStatement {
|
||||||
|
type Ok = RebuildIndexStatement;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match key {
|
||||||
|
"name" => {
|
||||||
|
self.name = Ident(value.serialize(ser::string::Serializer.wrap())?);
|
||||||
|
}
|
||||||
|
"what" => {
|
||||||
|
self.what = Ident(value.serialize(ser::string::Serializer.wrap())?);
|
||||||
|
}
|
||||||
|
"if_exists" => {
|
||||||
|
self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?;
|
||||||
|
}
|
||||||
|
key => {
|
||||||
|
return Err(Error::custom(format!(
|
||||||
|
"unexpected field `RebuildIndexStatement::{key}`"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
|
Ok(RebuildIndexStatement {
|
||||||
|
name: self.name,
|
||||||
|
what: self.what,
|
||||||
|
if_exists: self.if_exists,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default() {
|
||||||
|
let stmt = RebuildIndexStatement::default();
|
||||||
|
let value: RebuildIndexStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(value, stmt);
|
||||||
|
}
|
||||||
|
}
|
59
core/src/sql/value/serde/ser/statement/rebuild/mod.rs
Normal file
59
core/src/sql/value/serde/ser/statement/rebuild/mod.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
mod index;
|
||||||
|
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::statements::rebuild::RebuildStatement;
|
||||||
|
use crate::sql::value::serde::ser;
|
||||||
|
use serde::ser::Error as _;
|
||||||
|
use serde::ser::Impossible;
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Serializer;
|
||||||
|
|
||||||
|
impl ser::Serializer for Serializer {
|
||||||
|
type Ok = RebuildStatement;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<RebuildStatement, Error>;
|
||||||
|
type SerializeTuple = Impossible<RebuildStatement, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<RebuildStatement, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<RebuildStatement, Error>;
|
||||||
|
type SerializeMap = Impossible<RebuildStatement, Error>;
|
||||||
|
type SerializeStruct = Impossible<RebuildStatement, Error>;
|
||||||
|
type SerializeStructVariant = Impossible<RebuildStatement, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "an enum `RebuildStatement`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_newtype_variant<T>(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Self::Ok, Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match variant {
|
||||||
|
"Index" => Ok(RebuildStatement::Index(value.serialize(index::Serializer.wrap())?)),
|
||||||
|
variant => {
|
||||||
|
Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::sql::statements::rebuild::RebuildStatement;
|
||||||
|
use ser::Serializer as _;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn index() {
|
||||||
|
let stmt = RebuildStatement::Index(Default::default());
|
||||||
|
let serialized = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(stmt, serialized);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ pub static RESERVED_KEYWORD: phf::Set<UniCase<&'static str>> = phf_set! {
|
||||||
UniCase::ascii("KILL"),
|
UniCase::ascii("KILL"),
|
||||||
UniCase::ascii("LIVE"),
|
UniCase::ascii("LIVE"),
|
||||||
UniCase::ascii("OPTION"),
|
UniCase::ascii("OPTION"),
|
||||||
|
UniCase::ascii("REBUILD"),
|
||||||
UniCase::ascii("RETURN"),
|
UniCase::ascii("RETURN"),
|
||||||
UniCase::ascii("RELATE"),
|
UniCase::ascii("RELATE"),
|
||||||
UniCase::ascii("REMOVE"),
|
UniCase::ascii("REMOVE"),
|
||||||
|
@ -162,6 +163,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
||||||
UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly),
|
UniCase::ascii("READONLY") => TokenKind::Keyword(Keyword::Readonly),
|
||||||
UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate),
|
UniCase::ascii("RELATE") => TokenKind::Keyword(Keyword::Relate),
|
||||||
UniCase::ascii("RELATION") => TokenKind::Keyword(Keyword::Relation),
|
UniCase::ascii("RELATION") => TokenKind::Keyword(Keyword::Relation),
|
||||||
|
UniCase::ascii("REBUILD") => TokenKind::Keyword(Keyword::Rebuild),
|
||||||
UniCase::ascii("REMOVE") => TokenKind::Keyword(Keyword::Remove),
|
UniCase::ascii("REMOVE") => TokenKind::Keyword(Keyword::Remove),
|
||||||
UniCase::ascii("REPLACE") => TokenKind::Keyword(Keyword::Replace),
|
UniCase::ascii("REPLACE") => TokenKind::Keyword(Keyword::Replace),
|
||||||
UniCase::ascii("RETURN") => TokenKind::Keyword(Keyword::Return),
|
UniCase::ascii("RETURN") => TokenKind::Keyword(Keyword::Return),
|
||||||
|
|
|
@ -85,7 +85,8 @@ impl Parser<'_> {
|
||||||
| t!("DELETE")
|
| t!("DELETE")
|
||||||
| t!("RELATE")
|
| t!("RELATE")
|
||||||
| t!("DEFINE")
|
| t!("DEFINE")
|
||||||
| t!("REMOVE") => {
|
| t!("REMOVE")
|
||||||
|
| t!("REBUILD") => {
|
||||||
self.parse_inner_subquery(ctx, None).await.map(|x| Value::Subquery(Box::new(x)))
|
self.parse_inner_subquery(ctx, None).await.map(|x| Value::Subquery(Box::new(x)))
|
||||||
}
|
}
|
||||||
t!("fn") => self.parse_custom_function(ctx).await.map(|x| Value::Function(Box::new(x))),
|
t!("fn") => self.parse_custom_function(ctx).await.map(|x| Value::Function(Box::new(x))),
|
||||||
|
@ -244,7 +245,8 @@ impl Parser<'_> {
|
||||||
| t!("DELETE")
|
| t!("DELETE")
|
||||||
| t!("RELATE")
|
| t!("RELATE")
|
||||||
| t!("DEFINE")
|
| t!("DEFINE")
|
||||||
| t!("REMOVE") => {
|
| t!("REMOVE")
|
||||||
|
| t!("REBUILD") => {
|
||||||
self.parse_inner_subquery(ctx, None).await.map(|x| Value::Subquery(Box::new(x)))?
|
self.parse_inner_subquery(ctx, None).await.map(|x| Value::Subquery(Box::new(x)))?
|
||||||
}
|
}
|
||||||
t!("fn") => {
|
t!("fn") => {
|
||||||
|
@ -411,6 +413,11 @@ impl Parser<'_> {
|
||||||
let stmt = self.parse_remove_stmt()?;
|
let stmt = self.parse_remove_stmt()?;
|
||||||
Subquery::Remove(stmt)
|
Subquery::Remove(stmt)
|
||||||
}
|
}
|
||||||
|
t!("REBUILD") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let stmt = self.parse_rebuild_stmt()?;
|
||||||
|
Subquery::Rebuild(stmt)
|
||||||
|
}
|
||||||
t!("+") | t!("-") => {
|
t!("+") | t!("-") => {
|
||||||
// handle possible coordinate in the shape of ([-+]?number,[-+]?number)
|
// handle possible coordinate in the shape of ([-+]?number,[-+]?number)
|
||||||
if let TokenKind::Number(kind) = self.peek_token_at(1).kind {
|
if let TokenKind::Number(kind) = self.peek_token_at(1).kind {
|
||||||
|
@ -557,6 +564,11 @@ impl Parser<'_> {
|
||||||
let stmt = self.parse_remove_stmt()?;
|
let stmt = self.parse_remove_stmt()?;
|
||||||
Subquery::Remove(stmt)
|
Subquery::Remove(stmt)
|
||||||
}
|
}
|
||||||
|
t!("REBUILD") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let stmt = self.parse_rebuild_stmt()?;
|
||||||
|
Subquery::Rebuild(stmt)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let value = ctx.run(|ctx| self.parse_value_field(ctx)).await?;
|
let value = ctx.run(|ctx| self.parse_value_field(ctx)).await?;
|
||||||
Subquery::Value(value)
|
Subquery::Value(value)
|
||||||
|
|
|
@ -2,6 +2,7 @@ use reblessive::Stk;
|
||||||
|
|
||||||
use crate::enter_query_recursion;
|
use crate::enter_query_recursion;
|
||||||
use crate::sql::block::Entry;
|
use crate::sql::block::Entry;
|
||||||
|
use crate::sql::statements::rebuild::{RebuildIndexStatement, RebuildStatement};
|
||||||
use crate::sql::statements::show::{ShowSince, ShowStatement};
|
use crate::sql::statements::show::{ShowSince, ShowStatement};
|
||||||
use crate::sql::statements::sleep::SleepStatement;
|
use crate::sql::statements::sleep::SleepStatement;
|
||||||
use crate::sql::statements::{
|
use crate::sql::statements::{
|
||||||
|
@ -83,12 +84,12 @@ impl Parser<'_> {
|
||||||
| t!("FOR") | t!("IF")
|
| t!("FOR") | t!("IF")
|
||||||
| t!("INFO") | t!("INSERT")
|
| t!("INFO") | t!("INSERT")
|
||||||
| t!("KILL") | t!("LIVE")
|
| t!("KILL") | t!("LIVE")
|
||||||
| t!("OPTION") | t!("RETURN")
|
| t!("OPTION") | t!("REBUILD")
|
||||||
| t!("RELATE") | t!("REMOVE")
|
| t!("RETURN") | t!("RELATE")
|
||||||
| t!("SELECT") | t!("LET")
|
| t!("REMOVE") | t!("SELECT")
|
||||||
| t!("SHOW") | t!("SLEEP")
|
| t!("LET") | t!("SHOW")
|
||||||
| t!("THROW") | t!("UPDATE")
|
| t!("SLEEP") | t!("THROW")
|
||||||
| t!("USE")
|
| t!("UPDATE") | t!("USE")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +166,10 @@ impl Parser<'_> {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
self.parse_option_stmt().map(Statement::Option)
|
self.parse_option_stmt().map(Statement::Option)
|
||||||
}
|
}
|
||||||
|
t!("REBUILD") => {
|
||||||
|
self.pop_peek();
|
||||||
|
self.parse_rebuild_stmt().map(Statement::Rebuild)
|
||||||
|
}
|
||||||
t!("RETURN") => {
|
t!("RETURN") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
ctx.run(|ctx| self.parse_return_stmt(ctx)).await.map(Statement::Output)
|
ctx.run(|ctx| self.parse_return_stmt(ctx)).await.map(Statement::Output)
|
||||||
|
@ -254,6 +259,10 @@ impl Parser<'_> {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
self.parse_insert_stmt(ctx).await.map(Entry::Insert)
|
self.parse_insert_stmt(ctx).await.map(Entry::Insert)
|
||||||
}
|
}
|
||||||
|
t!("REBUILD") => {
|
||||||
|
self.pop_peek();
|
||||||
|
self.parse_rebuild_stmt().map(Entry::Rebuild)
|
||||||
|
}
|
||||||
t!("RETURN") => {
|
t!("RETURN") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
self.parse_return_stmt(ctx).await.map(Entry::Output)
|
self.parse_return_stmt(ctx).await.map(Entry::Output)
|
||||||
|
@ -517,6 +526,31 @@ impl Parser<'_> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_rebuild_stmt(&mut self) -> ParseResult<RebuildStatement> {
|
||||||
|
let res = match self.next().kind {
|
||||||
|
t!("INDEX") => {
|
||||||
|
let if_exists = if self.eat(t!("IF")) {
|
||||||
|
expected!(self, t!("EXISTS"));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let name = self.next_token_value()?;
|
||||||
|
expected!(self, t!("ON"));
|
||||||
|
self.eat(t!("TABLE"));
|
||||||
|
let what = self.next_token_value()?;
|
||||||
|
|
||||||
|
RebuildStatement::Index(RebuildIndexStatement {
|
||||||
|
what,
|
||||||
|
name,
|
||||||
|
if_exists,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
x => unexpected!(self, x, "a rebuild statement keyword"),
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parsers a RETURN statement.
|
/// Parsers a RETURN statement.
|
||||||
///
|
///
|
||||||
/// # Parser State
|
/// # Parser State
|
||||||
|
|
|
@ -83,7 +83,8 @@ impl Parser<'_> {
|
||||||
| t!("DELETE")
|
| t!("DELETE")
|
||||||
| t!("RELATE")
|
| t!("RELATE")
|
||||||
| t!("DEFINE")
|
| t!("DEFINE")
|
||||||
| t!("REMOVE") => {
|
| t!("REMOVE")
|
||||||
|
| t!("REBUILD") => {
|
||||||
self.parse_inner_subquery(ctx, None).await.map(|x| Value::Subquery(Box::new(x)))
|
self.parse_inner_subquery(ctx, None).await.map(|x| Value::Subquery(Box::new(x)))
|
||||||
}
|
}
|
||||||
t!("IF") => {
|
t!("IF") => {
|
||||||
|
|
|
@ -122,6 +122,7 @@ keyword! {
|
||||||
PostingsOrder => "POSTINGS_ORDER",
|
PostingsOrder => "POSTINGS_ORDER",
|
||||||
Punct => "PUNCT",
|
Punct => "PUNCT",
|
||||||
Readonly => "READONLY",
|
Readonly => "READONLY",
|
||||||
|
Rebuild => "REBUILD",
|
||||||
Relate => "RELATE",
|
Relate => "RELATE",
|
||||||
Relation => "RELATION",
|
Relation => "RELATION",
|
||||||
Remove => "REMOVE",
|
Remove => "REMOVE",
|
||||||
|
|
85
lib/tests/rebuild.rs
Normal file
85
lib/tests/rebuild.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
mod parse;
|
||||||
|
use parse::Parse;
|
||||||
|
|
||||||
|
mod helpers;
|
||||||
|
use helpers::*;
|
||||||
|
|
||||||
|
use surrealdb::dbs::Session;
|
||||||
|
use surrealdb::err::Error;
|
||||||
|
use surrealdb::sql::Value;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn rebuild_index_statement() -> Result<(), Error> {
|
||||||
|
let sql = "
|
||||||
|
CREATE book:1 SET title = 'Rust Web Programming', isbn = '978-1803234694', author = 'Maxwell Flitton';
|
||||||
|
DEFINE INDEX uniq_isbn ON book FIELDS isbn UNIQUE;
|
||||||
|
REBUILD INDEX IF EXISTS uniq_isbn ON book;
|
||||||
|
INFO FOR TABLE book;
|
||||||
|
REBUILD INDEX IF EXISTS idx_author ON book;
|
||||||
|
REBUILD INDEX IF EXISTS ft_title ON book;
|
||||||
|
DEFINE INDEX idx_author ON book FIELDS author;
|
||||||
|
DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase;
|
||||||
|
DEFINE INDEX ft_title ON book FIELDS title SEARCH ANALYZER simple BM25 HIGHLIGHTS;
|
||||||
|
REBUILD INDEX uniq_isbn ON book;
|
||||||
|
REBUILD INDEX idx_author ON book;
|
||||||
|
REBUILD INDEX ft_title ON book;
|
||||||
|
INFO FOR TABLE book;
|
||||||
|
SELECT * FROM book WHERE title @@ 'Rust';
|
||||||
|
";
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 14);
|
||||||
|
for _ in 0..3 {
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
}
|
||||||
|
// Check infos output
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"{
|
||||||
|
events: {},
|
||||||
|
fields: {},
|
||||||
|
indexes: {
|
||||||
|
uniq_isbn: 'DEFINE INDEX uniq_isbn ON book FIELDS isbn UNIQUE'
|
||||||
|
},
|
||||||
|
lives: {},
|
||||||
|
tables: {}
|
||||||
|
}",
|
||||||
|
);
|
||||||
|
assert_eq!(format!("{tmp:#}"), format!("{val:#}"));
|
||||||
|
for _ in 0..8 {
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
}
|
||||||
|
// Check infos output
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"{
|
||||||
|
events: {},
|
||||||
|
fields: {},
|
||||||
|
indexes: {
|
||||||
|
ft_title: 'DEFINE INDEX ft_title ON book FIELDS title SEARCH ANALYZER simple BM25(1.2,0.75) DOC_IDS_ORDER 100 DOC_LENGTHS_ORDER 100 POSTINGS_ORDER 100 TERMS_ORDER 100 DOC_IDS_CACHE 100 DOC_LENGTHS_CACHE 100 POSTINGS_CACHE 100 TERMS_CACHE 100 HIGHLIGHTS',
|
||||||
|
idx_author: 'DEFINE INDEX idx_author ON book FIELDS author',
|
||||||
|
uniq_isbn: 'DEFINE INDEX uniq_isbn ON book FIELDS isbn UNIQUE'
|
||||||
|
},
|
||||||
|
lives: {},
|
||||||
|
tables: {}
|
||||||
|
}",
|
||||||
|
);
|
||||||
|
assert_eq!(format!("{tmp:#}"), format!("{val:#}"));
|
||||||
|
// Check record is found
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
author: 'Maxwell Flitton',
|
||||||
|
id: book:1,
|
||||||
|
isbn: '978-1803234694',
|
||||||
|
title: 'Rust Web Programming'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(format!("{tmp:#}"), format!("{val:#}"));
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue